<?php
/**
 * The WorkerPool Requires the following PHP extensions
 *    * pcntl
 *    * posix
 *    * sysvsem
 *    * sockets
 *    * proctitle (optional, PHP5.5+ comes with a builtin function)
 *
 * Use the following commands to install them on RHEL:
 *    yum install php-process php-pcntl
 *    yum install php-pear php-devel ; pecl install proctitle
 *    echo 'extension=proctitle.so' > /etc/php.d/proctitle.ini
 */

namespace QXS\WorkerPool;

/**
 * The Worker Pool class runs worker processes in parallel
 *
 */
class WorkerPool implements \Iterator, \Countable {

	/** Default child timeout in seconds */
	const CHILD_TIMEOUT_SEC = 10;

	/** @var array signals, that should be watched */
	protected $signals = array(
		SIGCHLD, SIGTERM, SIGHUP, SIGUSR1
	);

	/** @var bool is the pool created? (children forked) */
	private $created = FALSE;

	/** @var int number of children in the pool */
	private $workerPoolSize = 2;

	/** @var int number of children initially in the pool */
	private $initialPoolSize;

	/** @var int Current index for the last worker created in the pool */
	private $currentWorkerIndex = 0;

	/** @var int id of the parent */
	protected $parentPid = 0;

	/** @var \QXS\WorkerPool\WorkerInterface the worker class, that is used to run the tasks */
	protected $worker;

	/** @var \QXS\WorkerPool\Semaphore the semaphore, that is used to synchronizd tasks across all processes */
	protected $semaphore;

	/** @var ProcessDetailsCollection|ProcessDetails[] Collection of the worker processes */
	protected $workerProcesses;

	/** @var array received results from the workers */
	protected $results = array();

	/** @var int number of received results */
	protected $resultPosition = 0;

	/** @var string process title of the parent */
	protected $parentProcessTitleFormat = '%basename%: Parent';

	/** @var string process title of the children */
	protected $childProcessTitleFormat = '%basename%: Worker %i% of %class% [%state%]';

	/** @var boolean Respawn dead workers automatically if set to TRUE */
	private $respawnAutomatically = false;

	/** @var int Default child timeout in seconds */
	private $child_timeout_sec = self::CHILD_TIMEOUT_SEC;

	/**
	 * The constructor
	 */
	public function __construct() {
		$this->workerProcesses = new ProcessDetailsCollection();
		register_shutdown_function(array($this, 'onShutDown'));
	}

	/**
	 * The destructor
	 */
	public function __destruct() {
		if ($this->created) {
			$this->destroy();
		}
	}

	/**
	 * Returns the process title of the child
	 * @return string the process title of the child
	 */
	public function getChildProcessTitleFormat() {
		return $this->childProcessTitleFormat;
	}

	/**
	 * Sets the process title of the child
	 *
	 * Listing permitted replacments
	 *   %i%         The Child's Number
	 *   %basename%  The base name of PHPSELF
	 *   %fullname%  The value of PHPSELF
	 *   %class%     The Worker's Classname
	 *   %state%     The Worker's State
	 *
	 * @param string $string the process title of the child
	 * @return WorkerPool
	 * @throws \QXS\WorkerPool\WorkerPoolException in case the WorkerPool has already been created
	 * @throws \DomainException in case the $string value is not within the permitted range
	 */
	public function setChildProcessTitleFormat($string) {
		if ($this->created) {
			throw new WorkerPoolException('Cannot set the Parent\'s Process Title Format for a created pool.');
		}
		$this->childProcessTitleFormat = ProcessDetails::sanitizeProcessTitleFormat($string);
		return $this;
	}

	/**
	 * Returns the process title of the parent
	 * @return string the process title of the parent
	 */
	public function getParentProcessTitleFormat() {
		return $this->parentProcessTitleFormat;
	}

	/**
	 * Sets the process title of the parent
	 *
	 * Listing permitted replacments
	 *   %basename%  The base name of PHPSELF
	 *   %fullname%  The value of PHPSELF
	 *   %class%     The WorkerPool's Classname
	 *
	 * @param string $string the process title of the parent
	 * @return WorkerPool
	 * @throws \QXS\WorkerPool\WorkerPoolException in case the WorkerPool has already been created
	 * @throws \DomainException in case the $string value is not within the permitted range
	 */
	public function setParentProcessTitleFormat($string) {
		if ($this->created) {
			throw new WorkerPoolException('Cannot set the Children\'s Process Title Format for a created pool.');
		}
		$this->parentProcessTitleFormat = ProcessDetails::sanitizeProcessTitleFormat($string);
		return $this;
	}

	/**
	 * Returns the current size of the worker pool
	 *
	 * In case the pool hasn't yet been created, this method returns the value of the currently set pool size.
	 * In case of a created pool, this method reports the real pool size (number of alive worker processes).
	 * @return int the number of processes
	 */
	public function getWorkerPoolSize() {
		return $this->workerPoolSize;
	}

	/**
	 * Sets the current size of the worker pool
	 * @param int $size the new worker pool size
	 * @return WorkerPool
	 * @throws \QXS\WorkerPool\WorkerPoolException in case the WorkerPool has already been created
	 * @throws \InvalidArgumentException in case the $size value is not within the permitted range
	 */
	public function setWorkerPoolSize($size) {
		if ($this->created) {
			throw new WorkerPoolException('Cannot set the Worker Pool Size for a created pool.');
		}
		$size = (int)$size;
		if ($size <= 0) {
			throw new \InvalidArgumentException('"' . $size . '" is not an integer greater than 0.');
		}
		$this->workerPoolSize = $size;
		return $this;
	}

	/**
	 * Gets the Semaphore, that will be used within the worker processes
	 * @return null|\QXS\WorkerPool\Semaphore $semaphore the Semaphore, that should be used for the workers
	 */
	public function getSemaphore() {
		return $this->semaphore;
	}

	/**
	 * Sets the Semaphore, that will be used within the worker processes
	 * @param \QXS\WorkerPool\Semaphore $semaphore the Semaphore, that should be used for the workers
	 * @return WorkerPool
	 * @throws \QXS\WorkerPool\WorkerPoolException in case the WorkerPool has already been created
	 * @throws \InvalidArgumentException in case the semaphre hasn't been created
	 */
	public function setSemaphore(Semaphore $semaphore) {
		if ($this->created) {
			throw new WorkerPoolException('Cannot set the Worker Pool Size for a created pool.');
		}
		if (!$semaphore->isCreated()) {
			throw new \InvalidArgumentException('The Semaphore hasn\'t yet been created.');
		}
		$this->semaphore = $semaphore;
		return $this;
	}

	/**
	 * Disables the semaphore feature in the workerpool
	 *
	 * Attention: You will lose the possibility to synchronize worker processes
	 *
	 * @throws \QXS\WorkerPool\WorkerPoolException in case the WorkerPool has already been created
	 * @throws \InvalidArgumentException in case the semaphre hasn't been created
	 */
	public function disableSemaphore() {
		$sem = new NoSemaphore();
		$sem->create();
		$this->setSemaphore($sem);
		return $this;
	}

	/**
	 * Sets default child timeout in seconds.
	 * @param int $child_timeout_sec
	 * @return WorkerPool
	 */
	public function setChildTimeoutSec($child_timeout_sec) {
		$this->child_timeout_sec = $child_timeout_sec;
		return $this;
	}

	/**
	 * Terminates the current process
	 * @param int $code the exit code
	 */
	public function exitPhp($code) {
		exit($code);
	}

	/**
	 * Creates the worker pool (forks the children)
	 *
	 * Please close all open resources before running this function.
	 * Child processes are going to close all open resources uppon exit,
	 * leaving the parent process behind with invalid resource handles.
	 * @param \QXS\WorkerPool\WorkerInterface $worker the worker, that runs future tasks
	 * @throws \RuntimeException
	 * @throws WorkerPoolException
	 * @return WorkerPool
	 */
	public function create(WorkerInterface $worker) {
		$this->initialPoolSize = $this->workerPoolSize;
		$this->parentPid = getmypid();
		$this->worker = $worker;
		if ($this->created) {
			throw new WorkerPoolException('The pool has already been created.');
		}

		$this->created = TRUE;
		// when adding signals use pcntl_signal_dispatch(); or declare ticks
		foreach ($this->signals as $signo) {
			pcntl_signal($signo, array($this, 'signalHandler'));
		}

		// no Semaphore attached? -> create one
		if (!($this->semaphore instanceof Semaphore)) {
			$this->semaphore = new Semaphore();
			$this->semaphore->create(Semaphore::SEM_RAND_KEY);
		}
		elseif(!$this->semaphore->isCreated()) {
			$this->semaphore->create(Semaphore::SEM_RAND_KEY);
		}

		ProcessDetails::setProcessTitle(
			$this->parentProcessTitleFormat,
			array(
				'basename' => basename($_SERVER['PHP_SELF']),
				'fullname' => $_SERVER['PHP_SELF'],
				'class' => get_class($this)
			)
		);

		for ($this->currentWorkerIndex = 1; $this->currentWorkerIndex <= $this->workerPoolSize; $this->currentWorkerIndex++) {
			$this->createWorker($this->currentWorkerIndex);
		}

		return $this;
	}

	/**
	 * Creates the worker
	 * @param int $i
	 * @throws \RuntimeException
	 */
	private function createWorker($i) {
		$sockets = array();
		if (socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $sockets) === FALSE) {
			// clean_up using posix_kill & pcntl_wait
			throw new \RuntimeException('socket_create_pair failed.');
			return;
		}
		$processId = pcntl_fork();
		if ($processId < 0) {
			// cleanup using posix_kill & pcntl_wait
			throw new \RuntimeException('pcntl_fork failed.');
			return;
		}
		elseif ($processId === 0) {
			// WE ARE IN THE CHILD
			$this->workerProcesses = new ProcessDetailsCollection(); // we do not have any children
			$this->workerPoolSize = 0; // we do not have any children
			socket_close($sockets[1]); // close the parent socket
			$this->runWorkerProcess($this->worker, new SimpleSocket($sockets[0]), $i);
		}
		else {
			// WE ARE IN THE PARENT
			socket_close($sockets[0]); // close child socket
			// create the child
			$this->workerProcesses->addFree(new ProcessDetails($processId, new SimpleSocket($sockets[1])));
		}
	}

	/**
	 * Run the worker process
	 * @param \QXS\WorkerPool\WorkerInterface $worker the worker, that runs the tasks
	 * @param \QXS\WorkerPool\SimpleSocket $simpleSocket the simpleSocket, that is used for the communication
	 * @param int $i the number of the child
	 */
	protected function runWorkerProcess(WorkerInterface $worker, SimpleSocket $simpleSocket, $i) {
		$replacements = array(
			'basename' => basename($_SERVER['PHP_SELF']),
			'fullname' => $_SERVER['PHP_SELF'],
			'class' => get_class($worker),
			'i' => $i,
			'state' => 'free'
		);
		ProcessDetails::setProcessTitle($this->childProcessTitleFormat, $replacements);
		$this->worker->onProcessCreate($this->semaphore);
		while (TRUE) {
			$output = array('pid' => getmypid());
			try {
				$replacements['state'] = 'free';
				ProcessDetails::setProcessTitle($this->childProcessTitleFormat, $replacements);
				$cmd = $simpleSocket->receive();
				// invalid response from parent?
				if (!isset($cmd['cmd'])) {
					break;
				}
				$replacements['state'] = 'busy';
				ProcessDetails::setProcessTitle($this->childProcessTitleFormat, $replacements);
				if ($cmd['cmd'] == 'run') {
					try {
						$output['data'] = $this->worker->run($cmd['data']);
					} catch (\Exception $e) {
						$output['workerException'] = array(
							'class' => get_class($e),
							'message' => $e->getMessage(),
							'trace' => $e->getTraceAsString()
						);
					}
					// send back the output
					$simpleSocket->send($output);
				} elseif ($cmd['cmd'] == 'exit') {
					break;
				}
			} catch (SimpleSocketException $e) {
				break;
			} catch (\Exception $e) {
				// send Back the exception
				$output['poolException'] = array(
					'class' => get_class($e),
					'message' => $e->getMessage(),
					'trace' => $e->getTraceAsString()
				);
				$simpleSocket->send($output);
			}
		}
		$this->worker->onProcessDestroy();
		$this->exitPhp(0);
	}

	/**
	 * This runs on shutdown to prevent the system from semaphore leaks
	 */
	public function onShutDown() {
		// are we in the parent?
		if ($this->parentPid === getmypid()) {
			if($this->created) {
				$this->destroy();
			}
		}
	}

	/**
	 * Destroy the WorkerPool with all its children
	 * @param int $maxWaitSecs a timeout to wait for the children, before killing them
	 * @throws WorkerPoolException
	 * @return WorkerPool
	 */
	public function destroy($maxWaitSecs = null) {
		if ($maxWaitSecs === null) {
			$maxWaitSecs = $this->child_timeout_sec;
		}
		if (!$this->created) {
			throw new WorkerPoolException('The pool hasn\'t yet been created.');
		}
		$this->created = FALSE;

		if ($this->parentPid === getmypid()) {
			$maxWaitSecs = ((int)$maxWaitSecs) * 2;
			if ($maxWaitSecs <= 1) {
				$maxWaitSecs = 2;
			}
			// send the exit instruction
			foreach ($this->workerProcesses as $processDetails) {
				try {
					$processDetails->getSocket()->send(array('cmd' => 'exit'));
				} catch (\Exception $e) {
				}
			}
			// wait up to 10 seconds
			for ($i = 0; $i < $maxWaitSecs; $i++) {
				usleep(500000); // 0.5 seconds
				pcntl_signal_dispatch();
				if ($this->workerPoolSize == 0) {
					break;
				}
			}

			// reset signals
			foreach ($this->signals as $signo) {
				pcntl_signal($signo, SIG_DFL);
			}

			// kill all remaining processes
			$this->workerProcesses->killAllProcesses();

			usleep(500000); // 0.5 seconds
			// reap the remaining signals
			$this->reaper();
			// destroy the semaphore
			$this->semaphore->destroy();

			unset($this->workerProcesses);
		}

		return $this;
	}

	/**
	 * Receives signals
	 *
	 * DO NOT MANUALLY CALL THIS METHOD!
	 * pcntl_signal_dispatch() will be calling this method.
	 * @param int $signo the signal number
	 * @see pcntl_signal_dispatch
	 * @see pcntl_signal
	 */
	public function signalHandler($signo) {
		switch ($signo) {
			case SIGCHLD:
				$this->reaper();
				break;
			case SIGTERM:
				// handle shutdown tasks
				$this->exitPhp(0);
				break;
			case SIGHUP:
				// handle restart tasks
				break;
			case SIGUSR1:
				// handle sigusr
				break;
			default: // handle all other signals
		}
		// more signals to dispatch?
		pcntl_signal_dispatch();
	}

	/**
	 * Respawn workers automatically if they died
	 * @param boolean $respawn
	 * @return WorkerPool
	 */
	public function respawnAutomatically($respawn = true) {
		if ($this->respawnAutomatically = $respawn) {
			$this->child_timeout_sec = 1;
		}
		return $this;
	}

	private function respawnIfRequired() {
		if (!$this->respawnAutomatically) {
			return;
		}
		while ($this->workerPoolSize < $this->initialPoolSize) {
			$this->createWorker(++$this->currentWorkerIndex);
			$this->workerPoolSize++;
		}
	}

	/**
	 * Child process reaper
	 * @param int $pid the process id
	 * @see pcntl_waitpid
	 */
	protected function reaper($pid = -1) {
		if (!is_int($pid)) {
			$pid = -1;
		}
		$childpid = pcntl_waitpid($pid, $status, WNOHANG);
		while ($childpid > 0) {
			$stopSignal = pcntl_wstopsig($status);
			if (pcntl_wifexited($stopSignal) === FALSE) {
				array_push($this->results, array(
					'pid' => $childpid,
					'abnormalChildReturnCode' => $stopSignal
				));
			}

			$processDetails = $this->workerProcesses->getProcessDetails($childpid);
			if ($processDetails !== NULL) {
				$this->workerPoolSize--;
				$this->workerProcesses->remove($processDetails);
				unset($processDetails);
			}
			$childpid = pcntl_waitpid($pid, $status, WNOHANG);
		}
	}

	/**
	 * Trys to wait for one free worker within a fixed timeout
	 *
	 * This function blocks until a worker has finished its work or the timeout has been reached.
	 * You can kill hanging child processes, so that the parent will be unblocked.
	 * Note: the run method already blocks until a free worker is available.
	 * @param int $timeout  the timeout in seconds
	 * @return bool  true, in case there is a free worker  or  false, in case the timeout has been reached
	 */
	public function tryWaitForOneFreeWorker($timeout=10) {
		$this->collectWorkerResults((int)abs($timeout));
		return $this->getFreeWorkers() > 0;
	}
	/**
	 * Waits for one free worker
	 *
	 * This function blocks until a worker has finished its work.
	 * You can kill hanging child processes, so that the parent will be unblocked.
	 * Note: the run method already blocks until a free worker is available.
	 */
	public function waitForOneFreeWorker() {
		while ($this->getFreeWorkers() == 0) {
			$this->collectWorkerResults($this->child_timeout_sec);
		}
	}
	/**
	 * Waits for all children to finish their worker
	 *
	 * This function blocks until every worker has finished its work.
	 * You can kill hanging child processes, so that the parent will be unblocked.
	 */
	public function waitForAllWorkers() {
		while ($this->getBusyWorkers() > 0) {
			$this->collectWorkerResults($this->child_timeout_sec);
		}
	}

	/**
	 * Returns the number of busy and free workers
	 *
	 * This function collects all the information at once.
	 * @return array with the keys 'free', 'busy', 'total'
	 */
	public function getFreeAndBusyWorkers() {
		$free = $this->getFreeWorkers();
		return array(
			'free' => $free,
			'busy' => $this->workerPoolSize - $free,
			'total' => $this->workerPoolSize
		);
	}

	/**
	 * Returns the number of free workers
	 *
	 * PAY ATTENTION WHEN USING THIS FUNCTION WITH A SUBSEQUENT CALL OF getBusyWorkers().
	 * IN THIS CASE THE SUM MIGHT NOT EQUAL TO THE CURRENT POOL SIZE.
	 * USE getFreeAndBusyWorkers() TO GET CONSISTENT RESULTS.
	 * @return int number of free workers
	 */
	public function getFreeWorkers() {
		$this->collectWorkerResults();
		return $this->workerProcesses->getFreeProcessesCount();
	}

	/**
	 * Returns the number of busy workers
	 *
	 * PAY ATTENTION WHEN USING THIS FUNCTION WITH A SUBSEQUENT CALL OF getFreeWorkers().
	 * IN THIS CASE THE SUM MIGHT NOT EQUAL TO THE CURRENT POOL SIZE.
	 * USE getFreeAndBusyWorkers() TO GET CONSISTENT RESULTS.
	 * @return int number of free workers
	 */
	public function getBusyWorkers() {
		return $this->workerPoolSize - $this->getFreeWorkers();
	}

	/**
	 * Get the pid of the next free worker
	 *
	 * This function blocks until a worker has finished its work.
	 * You can kill all child processes, so that the parent will be unblocked.
	 * @throws WorkerPoolException
	 * @return ProcessDetails the pid of the next free child
	 */
	protected function getNextFreeWorker() {
		$sec = 0;
		while (TRUE) {
			$this->collectWorkerResults($sec);

			$freeProcess = $this->workerProcesses->takeFreeProcess();
			if ($freeProcess !== NULL) {
				return $freeProcess;
			}

			$sec = $this->child_timeout_sec;
			if ($this->workerPoolSize <= 0) {
				throw new WorkerPoolException('All workers were gone.');
			}
		}

		return NULL;
	}

	/**
	 * Collects the results form the workers and processes any pending signals
	 * @param int $sec timeout to wait for new results from the workers
	 * @throws WorkerPoolException
	 */
	protected function collectWorkerResults($sec = 0) {
		$this->respawnIfRequired();

		// dispatch signals
		pcntl_signal_dispatch();

		if (isset($this->workerProcesses) === FALSE) {
			throw new WorkerPoolException('There is no list of worker processes. Maybe you destroyed the worker pool?', 1401179881);
		}
		$result = SimpleSocket::select($this->workerProcesses->getSockets(), array(), array(), $sec);
		foreach ($result['read'] as $socket) {
			/** @var $socket SimpleSocket */
			$processId = $socket->annotation['pid'];
			$result = $socket->receive();

			$possibleArrayKeys = array('data', 'poolException', 'workerException');
			if (is_array($result) && count(($resultTypes = array_intersect(array_keys($result), $possibleArrayKeys))) === 1) {
				// If the result has the expected format, free the worker and store the result.
				// Otherwise, the worker may be abnormally terminated (fatal error, exit(), ...) and will
				// fall in the reapers arms.
				$this->workerProcesses->registerFreeProcessId($processId);
				$result['pid'] = $processId;
				$resultType = reset($resultTypes);
				// Do not store NULL
				if ($resultType !== 'data' || $result['data'] !== NULL) {
					array_push($this->results, $result);
				}
			}
		}
		// dispatch signals
		pcntl_signal_dispatch();

		$this->respawnIfRequired();
	}

	/**
	 * Sends the input to the next free worker process
	 *
	 * This function blocks until a worker has finished its work.
	 * You can kill all child processes, so that the parent will be unblocked.
	 * @param mixed $input any serializable value
	 * @throws WorkerPoolException
	 * @return int The PID of the processing worker process
	 */
	public function run($input) {
		while ($this->workerPoolSize > 0) {
			try {
				$processDetailsOfFreeWorker = $this->getNextFreeWorker();
				$processDetailsOfFreeWorker->getSocket()->send(array('cmd' => 'run', 'data' => $input));
				return $processDetailsOfFreeWorker->getPid();
			} catch (\Exception $e) {
				pcntl_signal_dispatch();
			}
		}
		throw new WorkerPoolException('Unable to run the task.');
	}

	/**
	 * Clear all the results
	 */
	public function clearResults() {
		$this->collectWorkerResults();
		$this->results = array();
		return $this;
	}

	/**
	 * Is there any result available?
	 * @return bool true, in case we have received some results
	 */
	public function hasResults() {
		$this->collectWorkerResults();
		return !empty($this->results);
	}

	/**
	 * How many results did we receive?
	 * @return int the number of results
	 */
	public function countResults() {
		$this->collectWorkerResults();
		return $this->count();
	}

	/**
	 * Shifts the next result from the result queue
	 * @return array gets the next result
	 */
	public function getNextResult() {
		$this->collectWorkerResults();
		return array_shift($this->results);
	}

	/**
	 * Countable Method count
	 * @return int the number of results
	 * @see \QXS\WorkerPool\WorkerPool::countResults()
	 */
	public function count() {
		$this->collectWorkerResults();
		return count($this->results);
	}

	/**
	 * Iterator Method current
	 * @return array gets the current result
	 */
	public function current() {
		return reset($this->results);
	}

	/**
	 * Iterator Method key
	 * @return string returns the current key
	 */
	public function key() {
		return $this->resultPosition;
	}

	/**
	 * Iterator Method next()
	 */
	public function next() {
		$this->collectWorkerResults();
		if (!empty($this->results)) {
			$this->resultPosition++;
		}
		array_shift($this->results);
	}

	/**
	 * Iterator Method rewind()
	 */
	public function rewind() {
	}

	/**
	 * Iterator Method valid()
	 * @return bool true = there is a pending result
	 */
	public function valid() {
		return !empty($this->results);
	}
}


<?php

namespace Todstoychev\CalendarEvents\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

/**
 * Calendar events form request
 *
 * @package Todstoychev\CalendarEvents\Http\Requests
 * @author Todor Todorov <todstoychev@gmail.com>
 */
class CalendarEventRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        $rules = [
            'title' => 'required|min:3|max:255',
            'description' => 'min:10|max:1000',
            'start.date' => 'required|date',
            'start.time' => 'required|time',
            'border_color' => 'sometimes|regex:/#([a-fA-F\d]){6}/',
            'background_color' => 'sometimes|regex:/#([a-fA-F\d]){6}/',
            'text_color' => 'sometimes|regex:/#([a-fA-F\d]){6}/',
        ];

        if (empty($this->get('all_day'))) {
            $rules['end.date'] = 'required|date';
            $rules['end.time'] = 'required|time';
        }

        if (!empty($this->get('repeat'))) {
            $rules['repeat_dates'] = 'dates_array';
        }

        if (!empty($this->get('use_event_location'))) {
            if (empty($this->get('address')) || (empty($this->get('longitude')) || empty($this->get('latitude')))) {
                $rules['longitude'] = 'required';
                $rules['latitude'] = 'required';
            }

            if (empty($this->get('longitude')) && empty($this->get('latitude'))) {
                $rules['address'] = 'required';
             }
        }

        return $rules;
    }
}


<?php

namespace Krenor\LdapAuth\Connections;

use ErrorException;
use Krenor\LdapAuth\Contracts\ConnectionInterface;
use Krenor\LdapAuth\Contracts\DomainController;
use Krenor\LdapAuth\Exceptions\ConnectionException;

class LdapConnection implements ConnectionInterface
{

    /**
     * Concrete strategy for getting the connection of the domain controllers
     *
     * @var DomainController
     */
    protected $domainController;

    /**
     * Indicates whether backup rebinding should be used.
     * If this is set to false load balancing is used instead.
     *
     * @var bool
     */
    protected $backup = false;

    /**
     * Indicates whether or not to use SSL
     *
     * @var bool
     */
    protected $ssl = false;

    /**
     * Indicates whether or not to use TLS
     *
     * @var bool
     */
    protected $tls = false;

    /**
     * The current LDAP Connection
     *
     * @var resource
     */
    protected $connection;

    /**
     * Indicates whether or not the current connection is bound
     *
     * @var bool
     */
    protected $bound = false;

    /**
     * LdapConnection constructor.
     *
     * @param array $config
     */
    public function __construct(array $config)
    {
        $this->backup = $config['backup_rebind'];
        $this->tls    = $config['tls'];
        $this->ssl    = $config['ssl'];

        $this->domainController = $this->getDomainControllerStrategy($config['domain_controller']);
    }

    /**
     * Initialises a Connection via hostname
     *
     * @return resource
     */
    public function connect()
    {
        $port = $this->ssl ? $this::PORT_SSL : $this::PORT;

        $hostname = $this->domainController->getHostname();

        return $this->connection = ldap_connect($hostname, $port);
    }

    /**
     * Binds LDAP connection to the server
     *
     * @param $username
     * @param $password
     *
     * @return bool
     *
     * @throws ConnectionException
     */
    public function bind($username, $password)
    {
        // Tries to run the LDAP Connection as TLS
        if ($this->tls) {
            if ( ! ldap_start_tls($this->connection)) {
                throw new ConnectionException('Unable to Connect to LDAP using TLS.');
            }
        }

        try {
            $this->bound = ldap_bind($this->connection, $username, $password);
        } catch (ErrorException $e) {
            $this->bound = false;
        }

        return $this->bound;
    }

    /**
     * @param $option
     * @param $value
     *
     * @return bool
     */
    public function option($option, $value)
    {
        return ldap_set_option($this->connection, $option, $value);
    }

    /**
     * @return string
     */
    public function error()
    {
        return ldap_error($this->connection);
    }

    /**
     * @param string $dn
     * @param string $identifier
     * @param array  $fields
     *
     * @return resource
     */
    public function search($dn, $identifier, array $fields)
    {
        return ldap_search($this->connection, $dn, $identifier, $fields);
    }

    /**
     * @param $result
     *
     * @return array
     */
    public function entry($result)
    {
        return ldap_get_entries($this->connection, $result);
    }

    /**
     * @return bool
     */
    public function bound()
    {
        return $this->bound;
    }

    /**
     * @return bool
     */
    public function ssl()
    {
        return $this->ssl;
    }

    /**
     * @return bool
     */
    public function tls()
    {
        return $this->tls;
    }

    /**
     * @return resource
     */
    public function connection()
    {
        return $this->connection;
    }

    /**
     * Get the concrete strategy class for retrieving the hostname.
     *
     * @param array $domain_controller
     *
     * @return \Krenor\LdapAuth\Connections\DomainController
     */
    private function getDomainControllerStrategy(array $domain_controller)
    {
        $protocol = $this->ssl ? $this::PROTOCOL_SSL : $this::PROTOCOL;

        if (count($domain_controller) === 1) {
            return new SingleDomainController($protocol, $domain_controller);
        }

        if ($this->backup === true) {
            return new RebindDomainController($protocol, $domain_controller);
        } else {
            return new LoadBalancingDomainController($protocol, $domain_controller);
        }
    }

}


<?php
/*
 * This file is part of the ShortCode project.
 *
 * (c) Anis Uddin Ahmad <anis.programmer@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace ShortCode;

/**
 * Code
 *
 * Abstract parent for all type of code generation classes.
 *
 * @author Anis Uddin Ahmad <anis.programmer@gmail.com>
 */
abstract class Code
{
    const FORMAT_NUMBER        = '0123456789';
    const FORMAT_ALNUM         = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    const FORMAT_ALNUM_SMALL   = '0123456789abcdefghijklmnopqrstuvwxyz';
    const FORMAT_ALNUM_CAPITAL = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    const FORMAT_CHAR_SMALL    = 'abcdefghijklmnopqrstwxyz';
    const FORMAT_CHAR_CAPITAL  = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

    /**
     * @see http://php.net/manual/en/function.base-convert.php#106546
     *
     * @param $numberInput
     * @param $fromBaseInput
     * @param $toBaseInput
     *
     * @return int|string
     */
    protected static function convertBase($numberInput, $fromBaseInput, $toBaseInput)
    {
        if ($fromBaseInput == $toBaseInput) {
            return $numberInput;
        }

        $fromBase  = str_split($fromBaseInput, 1);
        $toBase    = str_split($toBaseInput, 1);
        $number    = str_split($numberInput, 1);
        $fromLen   = strlen($fromBaseInput);
        $toLen     = strlen($toBaseInput);
        $numberLen = strlen($numberInput);
        $retval    = '';

        if ($toBaseInput == self::FORMAT_NUMBER) {
            $retval = 0;
            for ($i = 1; $i <= $numberLen; $i++) {
                $retval = bcadd($retval, bcmul(array_search($number[$i - 1], $fromBase), bcpow($fromLen, $numberLen - $i)));
            }
            return $retval;
        }
        if ($fromBaseInput != self::FORMAT_NUMBER) {
            $base10 = self::convertBase($numberInput, $fromBaseInput, self::FORMAT_NUMBER);
        } else {
            $base10 = $numberInput;
        }
        if ($base10 < strlen($toBaseInput)) {
            return $toBase[$base10];
        }
        while ($base10 != '0') {
            $retval = $toBase[bcmod($base10, $toLen)] . $retval;
            $base10 = bcdiv($base10, $toLen, 0);
        }

        return $retval;
    }

    protected static function getTypeName($value)
    {
        $class = new \ReflectionClass(__CLASS__);
        $constants = array_flip($class->getConstants());

        return $constants[$value];
    }
}


<?php

namespace Todstoychev\CalendarEvents;

use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider;
use Todstoychev\CalendarEvents\Engine\CalendarEventsEngine;
use Todstoychev\CalendarEvents\Models;
use Todstoychev\CalendarEvents\Services\CalendarEventsService;

/**
 * Service provider class
 *
 * @package Todstoychev\Calendar
 * @author Todor Todorov <todstoychev@gmail.com>
 */
class CalendarEventsServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->publishes(
            [
                __DIR__ . '/../database/migrations' => database_path('/migrations'),
                __DIR__ . '/../config/calendar_events.php' => config_path('calendar_events.php'),
                __DIR__ . '/../resources/lang' => base_path('/resources/lang'),
                __DIR__ . '/../resources/views' => base_path('/resources/vendor/calendar-events'),
                __DIR__ . '/../public/js' => public_path('/js'),
            ]
        );

        Validator::extend('time', '\Todstoychev\CalendarEvents\Validator\CalendarEventsValidator@validateTime');
        Validator::extend(
            'dates_array',
            '\Todstoychev\CalendarEvents\Validator\CalendarEventsValidator@validateDatesArray'
        );

        $this->loadTranslationsFrom(__DIR__ . '/../resources/lang/', 'calendar-events');
        $this->loadViewsFrom(__DIR__. '/../resources/views/', 'calendar-events');
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(
            'calendar_events_engine',
            function () {
                return new CalendarEventsEngine(new Carbon());
            }
        );

        $this->app->bind(
            'calendar_events_service',
            function () {
                new CalendarEventsService(
                    $this->app->make('calendar_events_engine'),
                    new Models\CalendarEvent(),
                    new Models\CalendarEventRepeatDate(),
                    new Cache()
                );
            }
        );
    }
}


<?php

namespace Krenor\LdapAuth;

use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
use Krenor\LdapAuth\Objects\Ldap;

class LdapAuthUserProvider implements UserProvider
{
    /**
     * LDAP Wrapper.
     *
     * @var Ldap
     */
    protected $ldap;

    /**
     * LDAP Auth User Class.
     *
     * @var string
     */
    protected $model;

    /**
     * @param Ldap $ldap
     * @param string $model
     */
    public function __construct(Ldap $ldap, $model)
    {
        $this->ldap = $ldap;
        $this->model = $model;
    }

    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier)
    {
        return $this->retrieveByCredentials(
            ['username' => $identifier]
        );
    }

    /**
     * Retrieve a user by their unique identifier and "remember me" token.
     *
     * @param  mixed $identifier
     * @param  string $token
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByToken($identifier, $token)
    {
        // this shouldn't be needed as user / password is in ldap
    }

    /**
     * Update the "remember me" token for the given user in storage.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable $user
     * @param  string $token
     * @return void
     */
    public function updateRememberToken(Authenticatable $user, $token)
    {
        // this shouldn't be needed as user / password is in ldap
    }

    /**
     * Retrieve a user by the given credentials.
     *
     * @param  array $credentials
     * @return \Krenor\LdapAuth\Objects\LdapUser|null
     */
    public function retrieveByCredentials(array $credentials)
    {
        $username = $credentials['username'];
        $result = $this->ldap->find($username);

        if( !is_null($result) ){
            $user = new $this->model;
            $user->build( $result );

            return $user;
        }

        return null;
    }

    /**
     * Validate a user against the given credentials.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable $user
     * @param  array $credentials
     * @return bool
     */
    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        return $this->ldap->auth(
            $user->dn,
            $credentials['password']
        );
    }

}


<?php

//----------------------------------------------------------------------
//
//  Copyright (C) 2015 Artem Rodygin
//
//  You should have received a copy of the MIT License along with
//  this file. If not, see <http://opensource.org/licenses/MIT>.
//
//----------------------------------------------------------------------

namespace Dictionary;

/**
 * Abstract static dictionary of key/value pairs.
 */
abstract class StaticDictionary implements StaticDictionaryInterface
{
    /** @var array Dictionary values (to be overloaded). */
    protected static $dictionary = [];

    /**
     * {@inheritdoc}
     */
    public static function all()
    {
        return static::dictionary();
    }

    /**
     * {@inheritdoc}
     */
    public static function get($key)
    {
        $dictionary = static::dictionary();

        if (array_key_exists($key, $dictionary)) {
            return $dictionary[$key];
        }

        return static::FALLBACK === null ? null : $dictionary[static::FALLBACK];

    }

    /**
     * {@inheritdoc}
     */
    public static function has($key)
    {
        return array_key_exists($key, static::dictionary());
    }

    /**
     * {@inheritdoc}
     */
    public static function find($value)
    {
        $key = array_search($value, static::dictionary(), true);

        return $key === false ? static::FALLBACK : $key;
    }

    /**
     * {@inheritdoc}
     */
    public static function keys()
    {
        return array_keys(static::dictionary());
    }

    /**
     * {@inheritdoc}
     */
    public static function values()
    {
        return array_values(static::dictionary());
    }

    /**
     * Returns dictionary.
     *
     * @return array
     */
    protected static function dictionary()
    {
        return static::$dictionary;
    }
}


<?php
/**
 * PHP-Firebase.
 *
 * @link      https://github.com/adrorocker/php-firebase
 *
 * @copyright Copyright (c) 2018 Adro Rocker
 * @author    Adro Rocker <mes@adro.rocks>
 */

namespace PhpFirebase\Clients;

use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use PhpFirebase\Interfaces\ClientInterface;
use function GuzzleHttp\Psr7\stream_for;

/**
 * Guzzle Client.
 *
 * @since 0.1.0
 */
class GuzzleClient implements ClientInterface
{
    /**
     * Guzzle client.
     *
     * @var \GuzzleHttp\Client
     */
    protected $guzzle;

    /**
     * Set the the guzzle client.
     *
     * @param array       $options The options to set the defaul
     * @param object|null $client  Client to make the requests
     */
    public function __construct(array $options = [], $client = null)
    {
        if (!$client) {
            $client = new HttpClient($options);
        }

        $this->guzzle = $client;
    }

    /**
     * Create a new GET reuest.
     *
     * @param string $endpoint The sub endpoint
     * @param array  $headers  Request headers
     *
     * @return array
     */
    public function get($endpoint, $headers = [])
    {
        $request = new Request('GET', $endpoint, $headers);

        $response = $this->guzzle->send($request);

        return $this->handle($response);
    }

    /**
     * Create a new POST reuest.
     *
     * @param string       $endpoint The sub endpoint
     * @param string|array $data     The data to be submited
     * @param array        $headers  Request headers
     *
     * @return array
     */
    public function post($endpoint, $data, $headers = [])
    {
        $request = new Request('POST', $endpoint, $headers, $data);

        $response = $this->guzzle->send($request);

        return $this->handle($response);
    }

    /**
     * Create a new PUT reuest.
     *
     * @param string       $endpoint The sub endpoint
     * @param string|array $data     The data to be submited
     * @param array        $headers  Request headers
     *
     * @return array
     */
    public function put($endpoint, $data, $headers = [])
    {
        $request = new Request('PUT', $endpoint, $headers, $data);

        $response = $this->guzzle->send($request);

        return $this->handle($response);
    }

    /**
     * Create a new PATCH reuest.
     *
     * @param string       $endpoint The sub endpoint
     * @param string|array $data     The data to be submited
     * @param array        $headers  Request headers
     *
     * @return array
     */
    public function patch($endpoint, $data, $headers = [])
    {
        $request = new Request('PATCH', $endpoint, $headers, $data);

        $response = $this->guzzle->send($request);

        return $this->handle($response);
    }

    /**
     * Create a new DELETE reuest.
     *
     * @param string $endpoint The sub endpoint
     * @param array  $headers  Request headers
     *
     * @return array
     */
    public function delete($endpoint, $headers = [])
    {
        $request = new Request('DELETE', $endpoint, $headers);

        $response = $this->guzzle->send($request);

        return $this->handle($response);
    }

    /**
     * Handle the response.
     *
     * @param \GuzzleHttp\Psr7\Response $response The response
     *
     * @return array
     */
    private function handle(Response $response)
    {
        $stream = stream_for($response->getBody());

        $data = json_decode($stream->getContents());

        return $data;
    }
}


<?php
/**
 * PHP-Firebase.
 *
 * @link      https://github.com/adrorocker/php-firebase
 *
 * @copyright Copyright (c) 2018 Adro Rocker
 * @author    Adro Rocker <mes@adro.rocks>
 */

namespace PhpFirebase;

use InvalidArgumentException;
use PhpFirebase\Clients\GuzzleClient;
use PhpFirebase\Interfaces\ClientInterface;
use PhpFirebase\Interfaces\FirebaseInterface;

/**
 * Firebase.
 *
 * @since 0.1.0
 */
class Firebase implements FirebaseInterface
{
    /**
     * Base endpoint.
     *
     * @var string
     */
    protected $base;

    /**
     * Token.
     *
     * @var string
     */
    protected $token;

    /**
     * Client.
     *
     * @var \PhpFirebase\Interfaces\ClientInterface
     */
    protected $client = null;

    /**
     * Response.
     *
     * @var mixed
     */
    protected $response = null;

    /**
     * Set the base path for Firebase endpont
     * and the token to authenticate.
     *
     * @param string                                       $base   The base endpoint
     * @param string                                       $token  The token
     * @param \PhpFirebase\Interfaces\ClientInterface|null $client Client to make the request
     */
    public function __construct($base, $token, ClientInterface $client = null)
    {
        if (!is_string($base)) {
            throw new InvalidArgumentException('Base parameter needs to be string', 1);
        }
        if (!is_string($token)) {
            throw new InvalidArgumentException('Token parameter needs to be string', 1);
        }

        $parts = parse_url($base);

        if (!isset($parts['scheme']) || !isset($parts['host'])) {
            throw new InvalidArgumentException("The base URL $base is not valid", 1);
        }

        $this->base = rtrim($base, '/');
        $this->token = (string) $token;

        if (!$client) {
            $client = new GuzzleClient([
                'base'  => $this->base,
                'token' => $this->token,
            ]);
        }

        $this->setClient($client);
    }

    /**
     * GET request.
     *
     * @param string $endpoint The sub endpoint
     * @param array  $query    Query parameters
     *
     * @return object
     */
    public function get($endpoint, $query = [])
    {
        $endpoint = $this->buildUri($endpoint, $query);
        $headers = $this->buildHeaders();

        $this->response = $this->client->get($endpoint, $headers);

        return $this->response;
    }

    /**
     * POST request.
     *
     * @param string       $endpoint The sub endpoint
     * @param string|array $data     The data to be submited
     * @param array        $query    Query parameters
     *
     * @return object
     */
    public function post($endpoint, $data, $query = [])
    {
        $endpoint = $this->buildUri($endpoint, $query);
        $headers = $this->buildHeaders();
        $data = $this->prepareData($data);

        $this->response = $this->client->post($endpoint, $data, $headers);

        return $this->response;
    }

    /**
     * PUT request.
     *
     * @param string       $endpoint The sub endpoint
     * @param string|array $data     The data to be submited
     * @param array        $query    Query parameters
     *
     * @return object
     */
    public function put($endpoint, $data, $query = [])
    {
        $endpoint = $this->buildUri($endpoint, $query);
        $headers = $this->buildHeaders();
        $data = $this->prepareData($data);

        $this->response = $this->client->put($endpoint, $data, $headers);

        return $this->response;
    }

    /**
     * PATCH request.
     *
     * @param string       $endpoint The sub endpoint
     * @param string|array $data     The data to be submited
     * @param array        $query    Query parameters
     *
     * @return object
     */
    public function patch($endpoint, $data, $query = [])
    {
        $endpoint = $this->buildUri($endpoint, $query);
        $headers = $this->buildHeaders();
        $data = $this->prepareData($data);

        $this->response = $this->client->patch($endpoint, $data, $headers);

        return $this->response;
    }

    /**
     * DELETE request.
     *
     * @param string $endpoint The sub endpoint
     * @param array  $query    Query parameters
     *
     * @return object
     */
    public function delete($endpoint, $query = [])
    {
        $endpoint = $this->buildUri($endpoint, $query);
        $headers = $this->buildHeaders();

        $this->response = $this->client->delete($endpoint, $headers);

        return $this->response;
    }

    /**
     * Get response.
     *
     * @return mixed
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * Get client.
     *
     * @return \PhpFirebase\Interfaces\ClientInterface
     */
    public function getClient()
    {
        return $this->client;
    }

    /**
     * Get base endpoint.
     *
     * @return string
     */
    public function getBase()
    {
        return $this->base;
    }

    /**
     * Set the client.
     *
     * @param \PhpFirebase\Interfaces\ClientInterface
     */
    protected function setClient(ClientInterface $client)
    {
        $this->client = $client;
    }

    /**
     * Convert array|string to json.
     *
     * @param array $data Data to be converted
     *
     * @return array
     */
    protected function prepareData($data = [])
    {
        return json_encode($data);
    }

    /**
     * Create a standard uri based on the end point
     * and add the auth token.
     *
     * @param string $endpoint The sub endpoint
     * @param array  $options  Extra options to be added
     *
     * @return string
     */
    protected function buildUri($endpoint, $options = [])
    {
        if ($this->token !== '') {
            $options['auth'] = $this->token;
        }

        return $this->base.'/'.ltrim($endpoint, '/').'.json?'.http_build_query($options, '', '&');
    }

    /**
     * Build all headers.
     *
     * @param array $extraHeaders Extra headers to be added
     *
     * @return array
     */
    protected function buildHeaders($extraHeaders = [])
    {
        $headers = [
            'Accept' => 'application/json',
            'Content-Type: application/json',
        ];

        return array_merge($headers, $extraHeaders);
    }
}


<?php

namespace ptejada\uFlex;

/**
 * Console to log reports and errors
 *
 * @package ptejada\uFlex
 * @author Pablo Tejada <pablo@ptejada.com>
 */
class Log
{
    /** @var array - Predefined list of errors, useful for locale messages */
    protected $errorList = array();
    /** @var string - Stores the Log instance internal namespace fro logs */
    protected $namespace = 'Log';
    /** @var string - Stores the current selected channel */
    protected $currentChannel = '_main';

    /** @var array The console to store all logs */
    protected $console = array(
        'errors'  => array(),
        'reports' => array(),
        'form'    => array(),
    );

    /**
     * Initializes a new log instance with the options to set an initial namespace
     *
     * @param string $namespace
     * @param Log    $logLink
     */
    public function __construct($namespace = null, Log $logLink=null)
    {
        if ($namespace) {
            // Sets the name
            $this->changeNamespace($namespace);
        }

        if (!is_null($logLink)) {

            // Links the console with an existing instance
            $this->console = &$logLink->getFullConsole();
        }
    }

    /**
     * Change the current namespace
     *
     * @param $namespace
     *
     * @return $this
     */
    public function changeNamespace($namespace)
    {
        $this->cleanConsole();

        $this->namespace = $namespace;
        // Changes current channel
        $this->channel('_main');
        return $this;
    }

    /**
     * Checks the current channel for errors
     *
     * @return bool
     */
    public function hasError()
    {
        $errors = $this->getErrors();

        return count($errors) > 0;
    }

    /**
     * Get the full console array
     * @return array
     */
    public function &getFullConsole()
    {
        return $this->console;
    }

    /**
     * Get all errors in a specified channel
     *
     * @param string $channel - Channel identifier
     *
     * @return array - returns the error stack by reference, even if empty
     */
    public function &getErrors($channel = null)
    {
        // Uses the passed channel or fallback to the current selected channel
        $channel = $this->namespaceChannel($channel);

        if ( ! isset($this->console['errors'][$channel]) ) {
            $this->console['errors'][$channel] = array();
        }

        return $this->console['errors'][$channel];
    }

    /**
     * Get all logged errors per channel
     *
     * @return array
     */
    public function getAllErrors()
    {
        return $this->console['errors'];
    }

    /**
     * Get all the report for the current channel or an specific channel
     *
     * @param string $channel - (optional) Channel name
     *
     * @return array
     */
    public function &getReports($channel = null)
    {
        // Uses the passed channel or fallback to the current selected channel
        $channel = $this->namespaceChannel($channel);

        if (!isset($this->console['reports'][$channel])) {
            // Create a new empty array to return as reference
            $this->console['reports'][$channel] = array();
        }

        return $this->console['reports'][$channel];
    }

    /**
     * Get all logged errors per channel
     *
     * @return array
     */
    public function getAllReports()
    {
        return $this->console['reports'];
    }

    /**
     * Log an error to a form field error
     * Note: Only one error per field in a channel namespace
     *
     * @param string $field   - The form field name
     * @param string|int $message - The error message to link to the field or an ID of a predefined error message
     *
     * @return $this
     */
    public function formError($field, $message = '')
    {
        $formErrors = &$this->getFormErrors();
        if ($message) {
            if (is_int($message) && isset($this->errorList[$message])) {
                // Use predefined error
                $formErrors[$field] = $this->errorList[$message];
            }else{
                // Use given error message
                $formErrors[$field] = $message;
            }
            $this->error($message);
        } else {
            // if the message if omitted use the field as a generic message
            $formErrors[$field] = $message;
            $this->error($field);
        }

        return $this;
    }

    /**
     * Log an error
     *
     * @param string|int $message - An error message to log or the index of a predefined error
     *
     * @return $this
     */
    public function error($message)
    {
        if ($message) {
            if (is_int($message) && isset($this->errorList[$message])) {
                /*
                 * If the message is of type integer use a predefine
                 * error message
                 */
                $errorMessage = $this->errorList[$message];
                $this->report("Error[{$message}]: {$errorMessage}"); //Report The error
            } else {
                $errorMessage = $message;
                $this->report("Error: {$errorMessage}"); //Report The error
            }

            $errors = &$this->getErrors();
            $errors[] = $errorMessage;
        }

        return $this;
    }

    /**
     * Logs a process report
     *
     * @param $message
     *
     * @return $this
     */
    public function report($message)
    {
        $channel = $this->currentChannel;
        if ($message) {
            // Log the report to the console
            $reports = &$this->getReports($channel);
            $reports[] = $message;
        }

        return $this;
    }

    /**
     * Get form errors by channel
     *
     * @param  string $channel - (optional) Channel to look for form errors in, if omitted the current channel is used
     *
     * @return array
     */
    public function &getFormErrors($channel = '')
    {
        // Uses the passed channel or fallback to the current selected channel
        $channel = $this->namespaceChannel($channel);

        if (!isset($this->console['form'][$channel])) {
            $this->console['form'][$channel] = array();
        }

        return $this->console['form'][$channel];
    }

    /**
     * Get all the form errors in the console groups by nameSpaced channels
     *
     * @return array
     */
    public function getAllFormErrors()
    {
        return $this->console['form'];
    }

    /**
     * Updates the predefined list of errors
     * Note: The method self::error() which uses the predefined error list only support numeric indexes
     * @param array $errors - Array of error messages
     */
    public function updateErrorList(array $errors)
    {
        $this->errorList = $errors + $this->errorList;
    }

    /**
     * Change the current channel
     * Note: note changing to a channel with existing errors
     * from previous calls will be cleared
     *
     * @param $channelName
     *
     * @return $this
     */
    public function channel($channelName)
    {
        $this->cleanConsole();
        $this->currentChannel = $this->namespaceChannel($channelName);
        // Mark start of a new start
        $this->report(">> New $channelName request");
        // Clear any errors on the channel
        $this->clearErrors();
        return $this;
    }

    /**
     * Clears existing errors for a channel
     *
     * @param string $channelName
     */
    public function clearErrors($channelName='')
    {
        $channel = $this->namespaceChannel($channelName);

        // Clear any existing errors for the channel
        $this->console['errors'][$channel] = array();
        $this->console['form'][$channel] = array();
    }

    /**
     * Get the current namespace
     * @return string
     */
    public function getNamespace()
    {
        return $this->namespace;
    }

    /**
     * Converts a plain channel name into a namespace channel identifier
     *
     * @param $channelName
     *
     * @return string
     */
    private function namespaceChannel($channelName)
    {
        if ($channelName) {
            if (strpos($channelName, '::')) {
                // If the channelName seems to be nameSpaced don't change it
                return $channelName;
            } else {
                // Prefixes the namespace to the channelName
                return "{$this->namespace}::{$channelName}";
            }
        } else {
            // Invalid channel name, return the current channel instead
            return $this->currentChannel;
        }
    }

    /**
     * Creates a new instance of Log with a linked console
     *
     * @param $namespace
     *
     * @return Log
     */
    public function newChildLog($namespace)
    {
        $child = new Log($namespace, $this);
        return $child;
    }

    /**
     * Adds a predefined error to the internal list
     *
     * @param int|array $id Must be numeric or associative array with numeric keys
     * @param string    $message
     */
    public function addPredefinedError($id, $message = '')
    {
        if (is_array($id)) {
            $this->errorList = array_diff_key($this->errorList, $id) + $id;
        } else {
            $this->errorList[$id] = $message;
        }
    }

    /**
     * Removes any empty namespace of the current channel from the console
     */
    private function cleanConsole()
    {
        $channel = $this->namespaceChannel($this->currentChannel);

        if (empty($this->console['errors'][$channel])) {
            unset($this->console['errors'][$channel]);
        }

        if (empty($this->console['form'][$channel])) {
            unset($this->console['form'][$channel]);
        }

        if (empty($this->console['reports'][$channel])) {
            unset($this->console['reports'][$channel]);
        }
    }
}


<?php

namespace ptejada\uFlex;

/**
 * Database Connection Manager
 *
 * @package ptejada\uFlex
 * @author  Pablo Tejada <pablo@ptejada.com>
 */
class DB
{
    /** @var  Log - Log errors and report */
    public $log;
    /** @var string - The server IP or host name */
    private $host = 'localhost';
    /** @var string - The server user to login as */
    private $user = 'root';
    /** @var string - The user password */
    private $password = '';
    /** @var string - The name of the database */
    private $dbName = '';
    /** @var string - Alternative DSN string */
    private $dsn = '';
    /** @var \PDO - The DB connection session */
    private $connection;

    /**
     * Initializes the Database object
     *
     * @param string $hostOrDSN|\PDO - The domain/IP of the DB, the PDO DSN string or PDO connection
     * @param string $dbName    - The name of the database
     */
    public function __construct($hostOrDSN = '', $dbName = '')
    {
        if (!$dbName) {
            if ($hostOrDSN instanceof \PDO) {
                // Saves the PDO connection
                $this->setConnection($hostOrDSN);
            } else {
                // add full DSN string
                $this->dsn = $hostOrDSN;
            }
        } else {
            // Add the default DB credentials for MySQL
            $this->host = $hostOrDSN;
            $this->dbName = $dbName;
        }

        $this->log = new Log('DB');
    }

    /**
     * Get table object
     *
     * @param $tableName
     *
     * @return DB_Table
     */
    public function getTable($tableName)
    {
        return new DB_Table($this, $tableName);
    }

    /**
     * Set the database username
     *
     * @param string $user
     */
    public function setUser($user)
    {
        $this->user = $user;
    }

    /**
     * Set the database user password
     *
     * @param string $password
     */
    public function setPassword($password)
    {
        $this->password = $password;
    }

    /**
     * Set the name of the Database to connect to
     * @param string $dbName
     */
    public function setDbName($dbName)
    {
        $this->dbName = $dbName;
    }

    /**
     * Get the record of the last inserted record
     *
     * @return int
     */
    public function getLastInsertedID()
    {
        return $this->getConnection()->lastInsertId();
    }

    /**
     * Gets the connecting to the database
     * Check if the database connection exists if not connects to the database
     *
     * @return \PDO | bool
     */
    public function getConnection()
    {
        if (!($this->log instanceof Log)) {
            $this->log = new Log('DB');
        }

        // Use cached connection if already connected to server
        if ($this->connection instanceof \PDO) {
            return $this->connection;
        }

        $this->log->report('Connecting to database...');

        try{
            $this->connection = new \PDO($this->generateDSN(), $this->user, $this->password);
            $this->log->report('Connected to database.');
        } catch ( \PDOException $e ){
            $this->log->error('Failed to connect to database, [SQLSTATE] ' . $e->getCode());
        }

        // Check is the connection to server succeed
        if ($this->connection instanceof \PDO) {
            return $this->connection;
        } else {
            // There was an error connecting to the DB server
            return false;
        }
    }

    /**
     * Generate the DSN string for the connection
     *
     * @return string
     */
    protected function generateDSN()
    {
        if (!$this->dsn) {
            $this->dsn = "mysql:dbname={$this->dbName};host={$this->host}";
        }

        return $this->dsn;
    }

    /**
     * Set the connection
     * @param \PDO $connection
     */
    public function setConnection(\PDO $connection)
    {
        $this->connection = $connection;
    }
}


<?php

namespace ptejada\uFlex;

/**
 * Class Hash
 *
 * @package ptejada\uFlex
 * @author  Pablo Tejada <pablo@ptejada.com>
 */
class Hash
{
    /** @var  Log - Log errors and report */
    public $log;

    /**
     * Required for the integer encoder and decoder functions
     *
     * @var array
     * @access protected
     * @ignore
     */
    static protected $encoder = array(
        // @formatter:off
        'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
        'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
        0,2,3,4,5,6,7,8,9
        // @formatter:on
    );

    /**
     * Initializes the hash object
     */
    public function __construct()
    {
        $this->log = new Log();
    }

    /**
     * Generate a password for a user
     *
     * @param User   $user
     * @param String $password - Clear text password
     * @param bool   $generateOld
     *
     * @return string
     */
    public function generateUserPassword(User $user, $password, $generateOld = false)
    {
        $registrationDate = $user->RegDate;

        $pre = $this->encode($registrationDate);
        $pos = substr($registrationDate, 5, 1);
        $post = $this->encode($registrationDate * (substr($registrationDate, $pos, 1)));

        $finalString = $pre . $password . $post;

        return $generateOld ? md5($finalString) : sha1($finalString);
    }

    /**
     * Encodes an integer
     *
     * @param int $number integer to encode
     *
     * @return string encoded integer string
     */
    static protected function encode($number)
    {
        $k = self::$encoder;
        preg_match_all("/[1-9][0-9]|[0-9]/", $number, $a);
        $n = '';
        $o = count($k);
        foreach ($a[0] as $i) {
            if ($i < $o) {
                $n .= $k[$i];
            } else {
                $n .= '1' . $k[$i - $o];
            }
        }
        return $n;
    }

    /**
     * Generates a unique hash
     *
     * @param int         $uid  user id
     * @param bool|string $hash optional hash to implement
     *
     * @return string
     */
    static public function generate($uid = 0, $hash = false)
    {
        if ($uid) {
            $e_uid = self::encode($uid);
            $e_uid_length = strlen($e_uid);
            $e_uid_length = str_pad($e_uid_length, 2, 0, STR_PAD_LEFT);
            $e_uid_pos = rand(10, 32 - $e_uid_length - 1);

            if (!$hash) {
                $hash = sha1(uniqid(rand(), true));
            }

            $code = $e_uid_pos . $e_uid_length;
            $code .= substr($hash, 0, $e_uid_pos - strlen($code));
            $code .= $e_uid;
            $code .= substr($hash, strlen($code));

            return $code;
        } else {
            return sha1(uniqid(rand(), true));
        }
    }

    /**
     * Checks and validates a confirmation hash
     *
     * @param string $hash hashed string to check
     *
     * @return array
     */
    static public function examine($hash)
    {
        if (strlen($hash) == 40 && preg_match("/^[0-9]{4}/", $hash)) {

            $e_uid_pos = substr($hash, 0, 2);
            $e_uid_length = substr($hash, 2, 2);
            $e_uid = substr($hash, $e_uid_pos, $e_uid_length);

            $uid = self::decode($e_uid);

            preg_match('/^([0-9]{4})(.{2,' . ($e_uid_pos - 4) . '})(' . $e_uid . ')/', $hash, $excerpt);
            $partial = $excerpt[2];

            return array($uid, $partial);
        }
        else
        {
            /*
             * The hash is not valid
             */
            return array(false, false);
        }
    }

    /**
     * Decodes a string into an integer
     *
     * @param string $number string to decode into an integer
     *
     * @return int
     */
    static public function decode($number)
    {
        $k = self::$encoder;
        preg_match_all('/[1][a-zA-Z]|[2-9]|[a-zA-Z]|[0]/', $number, $a);
        $n = '';
        $o = count($k);
        foreach ($a[0] as $i) {
            $f = preg_match('/1([a-zA-Z])/', $i, $v);
            if ($f == true) {
                $i = $o + array_search($v[1], $k);
            } else {
                $i = array_search($i, $k);
            }
            $n .= $i;
        }
        return $n;
    }
}


<?php

namespace ptejada\uFlex;

/**
 * An object oriented representation an associative array
 *
 * @package ptejada\uFlex
 * @author  Pablo Tejada <pablo@ptejada.com>
 */
class Collection
{
    /** @var array The underlying original array */
    protected $_data;

    /**
     * Copies an arrays to handle it as an object
     * @param array $info
     */
    public function __construct(array $info = array())
    {
        $this->_data = $info;
    }

    /**
     * Update the collection with a given array of updates
     *
     * @param array $updates
     */
    public function update(array $updates)
    {
        $this->_data = array_merge($this->_data, $updates);
    }

    /**
     * Return the raw underling array of the collection
     *
     * @return array
     */
    public function &toArray()
    {
        return $this->_data;
    }

    /**
     * Checks if the collection is empty
     *
     * @return bool
     */
    public function isEmpty()
    {
        return empty($this->_data);
    }

    /**
     * Reduces the collection to only include allowed fields
     * Every argument passed to this function is considered a field
     */
    public function filter()
    {
        $this->_data = array_intersect_key($this->toArray(), array_flip(func_get_args()));
    }

    /**
     * Get a value of an entry in the collection
     * Useful to get deep array elements without manually dealing with errors
     * During the process
     *
     * @example Consider the below examples:
     *
     *      // If the 'two' is not defined this code will trigger a PHP notice
     *      $list->one->two->three->four->five
     *      // This method will never trigger a PHP notice, safe to use at any depth
     *      $list->get('one.two.three.four.five')
     *
     * @param string $keyPath - the period delimited location
     *
     * @return mixed|null|Collection
     */
    public function get($keyPath)
    {
        $stops = explode('.', $keyPath);

        $value = $this;
        foreach ($stops as $key) {
            if ($value instanceof Collection) {
                // Move one step deeper into the collection
                $value = $value->$key;
            } else {
                /*
                 * One more stops still pending and the current
                 * value is not a collection, terminate iteration
                 * and set value to null
                 */
                $value = null;
                break;
            }
        }

        return $value;
    }

    /**
     * Set a value to an index in the collection
     * Used when the collection are nested
     *
     * @param string $keyPath
     * @param string $value
     */
    public function set($keyPath, $value)
    {
        $stops = explode('.', $keyPath);

        $currentLocation = $previousLocation = $this;
        foreach ($stops as $key) {
            if ($currentLocation instanceof Collection) {
                // Move one step deeper into the collection
                if (!($currentLocation->$key instanceof Collection)) {
                    $currentLocation->$key = array();
                }
            } else {
                $currentLocation = array();
                $currentLocation->$key = array();
            }
            $previousLocation = $currentLocation;
            $currentLocation = $currentLocation->$key;
        }

        // Set the value
        $previousLocation->$key = $value;
    }

    /**
     * Return the number of items in the collection
     *
     * @return int
     */
    public function count()
    {
        if (!$this->isEmpty()) {
            return count($this->_data);
        } else {
            return 0;
        }
    }

    /**
     * Magic getter for all first child properties
     *
     * @param string $name
     *
     * @return mixed|LinkedCollection
     */
    public function __get($name)
    {
        if (array_key_exists($name, $this->_data)) {
            $value =& $this->_data[$name];

            if (is_array($value)) {
                return new LinkedCollection($value);
            } else {
                return $value;
            }
        }
        return null;
    }

    /**
     * Magic setter for all first child properties
     *
     * @param string $name
     * @param string $value
     */
    public function __set($name, $value)
    {
        $this->_data[$name] = $value;
    }

    /**
     * Check a property exists
     *
     * @param string $name
     *
     * @return bool
     */
    public function __isset($name)
    {
        return isset($this->_data[$name]);
    }

    /**
     * Deletes a property from the collection
     *
     * @param string $name
     */
    public function __unset($name)
    {
        unset($this->_data[$name]);
    }
}


<?php

namespace ptejada\uFlex;

/**
 * Class handles a single cookie
 * Reads and writes the value of a cookie
 *
 * @package ptejada\uFlex
 * @author  Pablo Tejada <pablo@ptejada.com>
 */
class Cookie
{
    /** @var  Log - Log errors and report */
    public $log;

    /** @var  string The name of the cookie */
    private $name;
    /** @var  string The content of the cookie */
    private $value;
    /** @var  int The lifetime in days of the cookie */
    private $lifetime;
    /** @var  string The path of the cookie */
    private $path;
    /** @var  string The host for which the host belongs to */
    private $host;

    /**
     * Initializes a cookie
     *
     * @param string $name     The name of the cookie
     * @param string $value    _(optional)_ The content of the cookie
     * @param int    $lifetime _(optional)_ The lifetime in days of the cookie
     * @param string $path     _(optional)_ The URL path of the cookie
     * @param null   $host     _(optional)_ The host for which the host belongs to
     */
    public function __construct($name, $value = '', $lifetime = 15, $path = '/', $host = null)
    {
        $this->name = $name;

        //Defaults
        $this->value = $value;
        $this->setLifetime($lifetime);
        $this->setPath($path);
        if (!$host) {
            if (isset($_SERVER['SERVER_NAME'])) {
                $this->setHost($_SERVER['SERVER_NAME']);
            }
        } else {
            $this->setHost($host);
        }
    }

    /**
     * Set the lifetime of the cookie
     *
     * @param int $lifetime - The number of days to last
     */
    public function setLifetime($lifetime)
    {
        $this->lifetime = $lifetime;
    }

    /**
     * Set the path of the cookie relative to the site domain
     *
     * @param string $path - The path of the cookie
     */
    public function setPath($path)
    {
        $this->path = $path;
    }

    /**
     * Set the host to add the cookie for
     *
     * @param string $host
     */
    public function setHost($host)
    {
        $this->host = $host;
    }

    /**
     * Sends the cookie to the browser
     *
     * @return bool
     */
    public function add()
    {
        if (!headers_sent()) {
            // Set the cookie via PHP headers
            $added = setcookie(
                $this->name,
                $this->value,
                round(time() + 60 * 60 * 24 * $this->lifetime),
                $this->path,
                $this->host
            );
        } else {
            //Headers have been sent use JavaScript to set the cookie
            echo "<script>";
            echo '
              function setCookie(c_name,value,expiredays){
                var exdate=new Date();
                exdate.setDate(exdate.getDate()+expiredays);
                document.cookie=c_name+ "=" +escape(value)+((expiredays==null) ? "" : "; expires="+exdate.toUTCString()) + "; domain="+ escape("' . $this->host . '") + "; path=" + escape("' . $this->path . '");
              }
            ';
            echo "setCookie('{$this->name}','{$this->value}',{$this->lifetime})";
            echo "</script>";
            $added = true;
        }

        return $added;
    }

    /**
     * Destroys the cookie
     *
     * @return bool
     */
    public function destroy()
    {
        if (!is_null($this->getValue())) {
            if (!headers_sent()) {
                return setcookie(
                    $this->name,
                    '',
                    time() - 3600,
                    $this->path,
                    $this->host
                ); //Deletes Cookie
            } else {
                return false;
            }
        } else {
            // The cookie does not exists, there is nothing to destroy
            return true;
        }
    }

    /**
     * Get the value of the cookie
     *
     * @return null|mixed - Returns null if the cookie does not exists
     */
    public function getValue()
    {
        if (isset($_COOKIE[$this->name])) {
            return $_COOKIE[$this->name];
        } else {
            return null;
        }
    }

    /**
     * Sets the value for
     *
     * @param string $value
     */
    public function setValue($value)
    {
        $this->value = $value;
    }
}


<?php

namespace ptejada\uFlex;

/**
 * Class to handle the PHP session
 * The entire PHP session can be handled
 * Or a direct member of the main session array
 * which is refer as a namespace
 *
 * @package ptejada\uFlex
 * @author Pablo Tejada <pablo@ptejada.com>
 */
class Session extends LinkedCollection
{
    /** @var  Log - Log errors and report */
    public $log;

    /** @var null|string Session index to manage */
    protected $namespace;

    /**
     * Initialize a session handler by namespace
     *
     * @param string $namespace - Session namespace to manage
     * @param   Log  $log
     */
    public function __construct($namespace = null, Log $log = null)
    {
        $this->log = $log instanceof Log ? $log : new Log('Session');
        $this->namespace = $namespace;

        // Starts the session if it has not been started yet
        if (!isset($_SESSION) && !headers_sent()) {
            session_start();
            $this->log->report('Session is been started...');
        } elseif (isset($_SESSION)) {
            $this->log->report('Session has already been started');
        } else {
            $this->log->error('Session could not be started');
        }

        if (is_null($namespace)) {
            // Manage the whole session
            parent::__construct($_SESSION);
        } else {
            if (!isset($_SESSION[$namespace])) {
                // Initialize the session namespace if does not exists yet
                $_SESSION[$namespace] = array();
            }

            // Link the SESSION namespace to the local $data variable
            parent::__construct($_SESSION[$namespace]);
        }

        $this->validate();
    }

    /**
     * Validates the session
     */
    private function validate()
    {
        /*
         * Get the correct IP
         */
        $server = new Collection($_SERVER);
        $ip = $server->HTTP_X_FORWARDED_FOR;

        if (is_null($ip) && $server->REMOTE_ADDR)
        {
            $ip = $server->REMOTE_ADDR;
        }

        if (!is_null($this->_ip)) {
            if ($this->_ip != $ip) {
                /*
                 * Destroy the session in the IP stored in the session is different
                 * then the IP of the current request
                 */
                $this->destroy();
            }
        } else {
            /*
             * Save the current request IP in the session
             */
            $this->_ip = $ip;
        }
    }

    /**
     * Get current session ID identifier
     *
     * @return string
     */
    public function getID()
    {
        return session_id();
    }

    /**
     * Empty the session namespace
     */
    public function destroy()
    {
        if (is_null($this->namespace)) {
            // Destroy the whole session
            session_destroy();
        } else {
            // Just empty the current session namespace
            $_SESSION[$this->namespace] = array();
            unset($_SESSION[$this->namespace]);
        }
    }
}


<?php

namespace ptejada\uFlex;

/**
 * Class UserBase
 *
 * @package ptejada\uFlex
 * @author  Pablo Tejada <pablo@ptejada.com>
 */
abstract class UserBase
{
    /** @var  Log - Log errors and report */
    public $log;
    /** @var Collection - Class configuration options */
    public $config = array(
        'cookieTime'      => '30',
        'cookieName'      => 'auto',
        'cookiePath'      => '/',
        'cookieHost'      => false,
        'userTableName'   => 'Users',
        'userSession'     => 'userData',
        'userDefaultData' => array(
            'Username' => 'Guess',
            'ID'       => 0,
            'Password' => 0,
        ),
        'database'        => array(
            'host'     => 'localhost',
            'name'     => '',
            'user'     => '',
            'password' => '',
            'dsn'      => '',
            'pdo'      => null,
        )
    );
    /** @var  Hash - Use to generate hashes */
    protected $hash;
    /** @var array - The user information object */
    protected $_data;
    /** @var Collection - Updates for the user information object */
    protected $_updates;
    /** @var Collection - default field validations */
    protected $_validations = array(
        'Username' => array(
            'limit' => '3-15',
            'regEx' => '/^([a-zA-Z0-9_])+$/'
        ),
        'Password' => array(
            'limit' => '3-30',
            'regEx' => ''
        ),
        'Email'    => array(
            'limit' => '4-254',
            'regEx' => '/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,63})$/i'
        )
    );

    /**
     * Initializes the the User object
     *
     * @param array $userData
     */
    public function __construct(array $userData = array())
    {
        // Instantiate the logger
        $this->log = new Log('User');

        // Instantiate the hash generator
        $this->hash = new Hash();

        // Convert the configurations to a collection
        $this->config = new Collection((array) $this->config);

        // Convert the validations rule to a collection
        $this->_validations = new Collection((array) $this->_validations);

        // Hydrate the model with user information
        $this->_data = $userData;

        // Initializes the updates collection
        $this->_updates = new Collection();
    }

    /**
     * Adds validation to queue for either the Registration or Update Method
     * Single Entry:
     *      Requires the first two parameters
     *            $name  = string (name of the field to be validated)
     *            $limit = string (range of the accepted value length in the format of "5-10")
     *                    - to make a field optional start with 0 (Ex. "0-10")
     *       Optional third parameter
     *            $regEx = string (Regular Expression to test the field)
     * _____________________________________________________________________________________________________
     * Multiple Entry:
     *
     *    Takes only the first argument
     *        $name = Array Object (takes an object in the following format:
     *            array(
     *                "Username" => array(
     *                        "limit" => "3-15",
     *                        "regEx" => "/^([a-zA-Z0-9_])+$/"
     *                        ),
     *                "Password" => array(
     *                        "limit" => "3-15",
     *                        "regEx" => false
     *                        )
     *                );
     *
     * @access public
     * @api
     *
     * @param string|array $name  Name of the field to validate or an array of all the fields and their validations
     * @param string       $limit A range of the accepted value length in the format of "5-10", to make a field optional
     *                            start with 0 (Ex. "0-10")
     * @param string|bool  $regEx Regular expression to the test the field with
     *
     * @return null
     */
    public function addValidation($name, $limit = '0-1', $regEx = false)
    {
        $this->log->channel('validation');
        if (is_array($name)) {
            $this->_validations->update($name);
            $this->log->report('New Validation Object added');
        } else {
            $this->_validations->$name = array(
                'limit' => $limit,
                'regEx' => $regEx,
            );
            $this->log->report("The $name field has been added for validation");
        }
    }

    /**
     * Get the value of a user property
     *
     * @param $name
     *
     * @return mixed
     */
    public function getProperty($name)
    {
        return $this->__get($name);
    }

    /**
     * Get the value of a user property
     *
     * @param $name
     *
     * @return mixed|null
     */
    public function __get($name)
    {
        if (isset($this->_data[$name])) {
            return $this->_data[$name];
        } else {
            if ($this->_updates->$name) {
                return $this->_updates->$name;
            }
        }

        $trace = debug_backtrace();
        trigger_error(
            'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'],
            E_USER_NOTICE
        );

        return null;
    }

    /**
     * Queues any updates to user properties
     *
     * @param $name
     * @param $value
     */
    public function __set($name, $value)
    {
        $this->_updates->$name = $value;
    }

    /**
     * Get all the user fields as an array
     *
     * @return array
     */
    public function toArray()
    {
        return $this->_data;
    }

    /**
     * Check if the data is a Collection if an array convert it to a Collection
     *
     * @param $data
     *
     * @return Collection
     */
    protected function toCollection($data)
    {
        if (is_array($data)) {
            return new Collection($data);
        } else {
            if (!($data instanceof Collection)) {
                // Invalid input type, return empty collection
                $data = new Collection();
            }
        }
        return $data;
    }

    /**
     * Validates All fields in _updates queue
     *
     * @param bool $includeAllRules - Will also run rules not validated
     *
     * @return bool
     */
    protected function validateAll($includeAllRules = false)
    {
        if ($includeAllRules) {
            /*
             * Include fields that might not have been included
             */
            $fieldData = new Collection(array_fill_keys(array_keys($this->_validations->toArray()), null));
            $fieldData->update($this->_updates->toArray());
        }
        else
        {
            $fieldData = clone $this->_updates;
        }

        foreach ($fieldData->toArray() as $field => $val) {
            //Match double fields
            $field2 = $field . '2';
            if (!is_null($fieldData->$field2)) {
                // Compared the two double fields
                if ($val != $fieldData->$field2) {
                    $this->log->formError($field, ucfirst($field) . 's did not match');
                } else {
                    $this->log->report(ucfirst($field) . 's matched');
                }
            }

            // Trim white spaces at end and start
            if ($this->_updates->$field) {
                $this->_updates->$field = trim($val);
            }

            // Check if a validation rule exists for the field
            if ($validation = $this->_validations->$field) {
                $this->validate($field, $validation->limit, $validation->regEx);
            }
        }
        return !$this->log->hasError();
    }

    /**
     * Validates a field in tmp_data
     *
     * @param string      $name  field name
     * @param string      $limit valid value length range, Ex: '0-10'
     * @param bool|string $regEx regular expression to test the field against
     *
     * @return bool
     */
    protected function validate($name, $limit, $regEx = false)
    {
        $Name = ucfirst($name);
        $value = $this->_updates->$name;
        $length = explode('-', $limit);
        $min = intval($length[0]);
        $max = intval($length[1]);

        if (!$max and !$min) {
            $this->log->error("Invalid second parameter for the $name validation");
            return false;
        }

        if (!$value) {
            if (is_null($value)) {
                $this->log->report("Missing index $name from the input");
            }
            if (strlen($value) == $min) {
                $this->log->report("$Name is blank and optional - skipped");
                return true;
            }
            $this->log->formError($name, "$Name is required.");
            return false;
        }

        // Validate the value maximum length
        if (strlen($value) > $max) {
            $this->log->formError($name, "The $Name is larger than $max characters.");
            return false;
        }

        // Validate the value minimum length
        if (strlen($value) < $min) {
            $this->log->formError($name, "The $Name is too short. It should at least be $min characters long");
            return false;
        }

        // Validate the value pattern
        if ($regEx) {
            preg_match($regEx, $value, $match);
            if (preg_match($regEx, $value, $match) === 0) {
                $this->log->formError($name, "The $Name \"{$value}\" is not valid");
                return false;
            }
        }

        /*
         * If the execution reaches this point then the field value
         * is considered to be valid
         */
        $this->log->report("The $name is Valid");
        return true;
    }
}


<?php

namespace ptejada\uFlex;

/**
 * Class DB_Table
 *
 * @package ptejada\uFlex
 * @author  Pablo Tejada <pablo@ptejada.com>
 */
class DB_Table
{
    /** @var  Log - Log errors and report */
    public $log;
    /** @var string - The table name */
    private $tableName = '';
    /** @var DB - The DB connection session */
    private $db;

    /**
     * Initializes a database table object
     *
     * @param DB     $db
     * @param string $table
     */
    public function __construct(DB $db, $table)
    {
        $this->db = $db;
        $this->log = $db->log;
        $this->tableName = $table;
    }

    /**
     * Test field value in database
     * Check for the uniqueness of a value in a specified field/column.
     * For example could be use to check for the uniqueness of a Username
     * or email prior to registration
     *
     * @param string      $field       The name of the field
     * @param string|int  $val         The value for the field to check
     * @param bool|string $customError Error string to log if field value is not unique
     *
     * @return bool
     */
    public function isUnique($field, $val, $customError = false)
    {
        $row = $this->getRow(array($field => $val));

        if ($row) {
            $this->log->report("There was a match for $field = $val");
            $this->log->formError($field, $customError ? $customError : "The {$field} {$val} exists in database");
            return true;
        } else {
            $this->log->report("No Match for $field = $val");
            return false;
        }
    }

    /**
     * Get a single row from the table depending on arguments
     *
     * @param array $arguments -  field and value pair set to look up user for
     *
     * @return bool|Collection
     */
    public function getRow($arguments)
    {
        $sql = 'SELECT * FROM _table_ WHERE _arguments_ LIMIT 1';

        if (!$stmt = $this->getStatement($sql, $arguments)) {
            // Something went wrong executing the SQL statement
            return false;
        } else {
            return $stmt->fetch();
        }

    }

    /**
     * Get a PDO statement
     *
     * @param string       $sql  SQL query string
     * @param bool|mixed[] $args argument to execute the statement with
     *
     * @return bool|\PDOStatement
     */
    public function getStatement($sql, $args = false)
    {
        // The parsed sql statement
        $query = $this->buildQuery($sql, $args);

        if ($connection = $this->db->getConnection()) {
            //Prepare the statement
            if ($stmt = $connection->prepare($query)) {
                //Log the SQL Query first
                $this->log->report("SQL Statement: {$query}");

                // When fetched return an object
                $stmt->setFetchMode(\PDO::FETCH_INTO, new Collection());

                // If arguments were passed execute the statement
                if ($args) {
                    $this->log->report("SQL Data Sent: [" . implode(', ', $args) . "]");
                    $stmt->execute($args);
                }

                // Handles any error during execution
                if ($stmt->errorCode() > 0) {
                    $error = $stmt->errorInfo();
                    $this->log->error("PDO({$error[0]})[{$error[1]}] {$error[2]}");
                    return false;
                }

                return $stmt;
            } else {
                $this->log->error('Failed to create a PDO statement with: ' . $query);
                return false;
            }
        } else {
            // Failed to connect to the database
            return false;
        }
    }

    /**
     * Builds a query string with the passed arguments
     *
     * @param string $sql
     * @param array  $arguments - Associative array of fields and values
     *
     * @return string
     */
    private function buildQuery($sql, $arguments = null)
    {
        if (is_array($arguments)) {
            $finalArgs = array();
            foreach ($arguments as $field => $val) {
                // Parametrize the arguments
                $finalArgs[] = " {$field}=:{$field}";
            }

            // Join all the arguments as a string
            $finalArgs = implode(' AND', $finalArgs);

            if (strpos($sql, ' _arguments_')) {
                // Place the arguments string in the placeholder
                $sql = str_replace(' _arguments_', $finalArgs, $sql);
            } else {
                // Appends the parameters string the sql query
                // $sql .= $finalArgs; TODO: Watch this expression if it is on use
            }
        }

        //Replace the _table_ placeholder
        $sql = str_replace(' _table_', " {$this->tableName} ", $sql);

        return $sql;
    }

    /**
     * Query the table
     *
     * @param      $sql
     * @param bool $arguments
     *
     * @return bool|\PDOStatement
     */
    public function query($sql, $arguments = false)
    {
        if (!$stmt = $this->getStatement($sql, $arguments)) {
            // Something went wrong executing the SQL statement
            return false;
        } else {
            return $stmt;
        }
    }

    /**
     * Executes SQL query and checks for success
     *
     * @param string     $sql       -  SQL query string
     * @param array|bool $arguments -  Array of arguments to execute $sql with
     *
     * @return bool
     */
    public function runQuery($sql, $arguments = false)
    {
        if (!$stmt = $this->getStatement($sql, $arguments)) {
            // Something went wrong executing the SQL statement
            return false;
        }

        // If there are no arguments, execute the statement
        if (!$arguments) {
            $stmt->execute();
        }

        $rows = $stmt->rowCount();

        if ($rows > 0) {
            //Good, Rows where affected
            $this->log->report("$rows row(s) where Affected");
            return true;
        } else {
            //Bad, No Rows where Affected
            $this->log->report('No rows were Affected');
            return false;
        }
    }

    /**
     * Get the ID of the last inserted record
     *
     * @return int
     */
    public function getLastInsertedID()
    {
        return $this->db->getLastInsertedID();
    }
}