<?php
/**
 * PHP client for IronMQ
 * IronMQ is a scalable, reliable, high performance message queue in the cloud.
 *
 * @link https://github.com/iron-io/iron_mq_php
 * @link http://www.iron.io/products/mq
 * @link http://dev.iron.io/
 * @version 4.0.1
 * @package IronMQPHP
 * @copyright Feel free to copy, steal, take credit for, or whatever you feel like doing with this code. ;)
 */

namespace IronMQ;

use IronCore\IronCore;

class IronMQ extends IronCore
{

    protected $client_version = '4.0.1';
    protected $client_name    = 'iron_mq_php';
    protected $product_name   = 'iron_mq';
    protected $default_values = array(
        'protocol'    => 'https',
        'host'        => 'mq-aws-us-east-1-1.iron.io',
        'port'        => '443',
        'api_version' => '3',
    );

    const LIST_QUEUES_PER_PAGE = 30;
    const GET_MESSAGE_TIMEOUT  = 60;
    const GET_MESSAGE_WAIT     = 0;

    /**
     * @param string|array $config
     *        Array of options or name of config file.
     * Fields in options array or in config:
     *
     * Required:
     * - token
     * - project_id
     * Optional:
     * - protocol
     * - host
     * - port
     * - api_version
     */
    public function __construct($config = null)
    {
        $this->getConfigData($config);
        $this->url = "{$this->protocol}://{$this->host}:{$this->port}/{$this->api_version}/";
    }

    /**
     * Switch active project
     *
     * @param string $project_id Project ID
     * @throws \InvalidArgumentException
     */
    public function setProjectId($project_id)
    {
        if (!empty($project_id)) {
            $this->project_id = $project_id;
        }
        if (empty($this->project_id)) {
            throw new \InvalidArgumentException("Please set project_id");
        }
    }

    /**
     * Get list of message queues
     *
     * @param int $previous
     *        Zero-indexed page to view
     * @param int $per_page
     *        Number of queues per page
     */
    public function getQueues($previous = null, $per_page = self::LIST_QUEUES_PER_PAGE)
    {
        $url = "projects/{$this->project_id}/queues";
        $params = array();
        if (!is_null($previous)) {
            $params['previous'] = $previous;
        }
        if ($per_page !== self::LIST_QUEUES_PER_PAGE) {
            $params['per_page'] = (int) $per_page;
        }
        $this->setJsonHeaders();
        return self::json_decode($this->apiCall(self::GET, $url, $params))->queues;
    }

    /**
     * Get information about queue.
     * Also returns queue size.
     *
     * @param string $queue_name
     * @return mixed
     */
    public function getQueue($queue_name)
    {
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue";
        $this->setJsonHeaders();
        return self::json_decode($this->apiCall(self::GET, $url))->queue;
    }

    /**
     * Clear all messages from queue.
     *
     * @param string $queue_name
     * @return mixed
     */
    public function clearQueue($queue_name)
    {
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/messages";
        $this->setJsonHeaders();
        return self::json_decode($this->apiCall(self::DELETE, $url));
    }

    /**
     * Push a message on the queue
     *
     * Examples:
     *
     * $ironmq->postMessage("test_queue", "Hello world");
     *
     *
     * $ironmq->postMessage("test_queue", "Test Message", array(
     *   'timeout' => 120,
     *   'delay' => 2,
     *   'expires_in' => 2*24*3600 # 2 days
     * ));
     *
     *
     * @param string $queue_name Name of the queue.
     * @param string $message
     * @param array $properties
     * @return mixed
     */
    public function postMessage($queue_name, $message, $properties = array())
    {
        $msg = new IronMQMessage($message, $properties);
        $req = array(
            "messages" => array($msg->asArray()),
        );
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/messages";
        $res = $this->apiCall(self::POST, $url, $req);
        $decoded = self::json_decode($res);
        $decoded->id = $decoded->ids[0];
        return $decoded;
    }

    /**
     * Push multiple messages on the queue
     *
     * Example:
     *
     * $ironmq->postMessages("test_queue", array("Lorem", "Ipsum"), array(
     *   'timeout' => 120,
     *   'delay' => 2,
     *   'expires_in' => 2*24*3600 # 2 days
     * ));
     *
     *
     * @param string $queue_name Name of the queue.
     * @param array $messages array of messages, each message same as for postMessage() method
     * @param array $properties array of message properties, applied to each message in $messages
     * @return mixed
     */
    public function postMessages($queue_name, $messages, $properties = array())
    {
        $req = array(
            "messages" => array(),
        );
        foreach ($messages as $message) {
            $msg = new IronMQMessage($message, $properties);
            array_push($req['messages'], $msg->asArray());
        }
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/messages";
        $res = $this->apiCall(self::POST, $url, $req);
        return self::json_decode($res);
    }

    /**
     * Reserve multiplie messages from queue
     *
     * @param string $queue_name Queue name
     * @param int $count
     * @param int $timeout
     * @return array|null array of messages or null
     * @deprecated Use reserveMessages instead
     */
    public function getMessages($queue_name, $count = 1, $timeout = self::GET_MESSAGE_TIMEOUT, $wait = self::GET_MESSAGE_WAIT)
    {
        return $this->reserveMessages($queue_name, $count, $timeout, $wait);
    }

    /**
     * Reserve multiplie messages from queue
     *
     * @param string $queue_name Queue name
     * @param int $count
     * @param int $timeout
     * @return array|null array of messages or null
     */
    public function reserveMessages($queue_name, $count = 1, $timeout = self::GET_MESSAGE_TIMEOUT, $wait = self::GET_MESSAGE_WAIT)
    {
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/reservations";
        $params = array();
        if ($count !== 1) {
            $params['n'] = (int) $count;
        }
        if ($timeout !== self::GET_MESSAGE_TIMEOUT) {
            $params['timeout'] = (int) $timeout;
        }
        if ($wait !== self::GET_MESSAGE_WAIT) {
            $params['wait'] = (int) $wait;
        }
        $this->setJsonHeaders();
        $response = $this->apiCall(self::POST, $url, $params);
        $result = self::json_decode($response);
        if (count($result->messages) < 1) {
            return null;
        } else {
            return $result->messages;
        }
    }

    /**
     * Reserve single message from queue
     *
     * @param string $queue_name Queue name
     * @param int $timeout
     * @return mixed|null single message or null
     * @deprecated Use reserveMessages instead
     */
    public function getMessage($queue_name, $timeout = self::GET_MESSAGE_TIMEOUT, $wait = self::GET_MESSAGE_WAIT)
    {
        return $this->reserveMessage($queue_name, $timeout, $wait);
    }

    /**
     * Reserve single message from queue
     *
     * @param string $queue_name Queue name
     * @param int $timeout
     * @return mixed|null single message or null
     */
    public function reserveMessage($queue_name, $timeout = self::GET_MESSAGE_TIMEOUT, $wait = self::GET_MESSAGE_WAIT)
    {
        $messages = $this->reserveMessages($queue_name, 1, $timeout, $wait);
        if ($messages) {
            return $messages[0];
        } else {
            return null;
        }
    }

    /**
     * Get the message with the given id.
     * @param string $queue_name Queue name
     * @param string $message_id Message ID
     * @return mixed
     */
    public function getMessageById($queue_name, $message_id)
    {
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/messages/{$message_id}";
        return self::json_decode($this->apiCall(self::GET, $url))->message;
    }

    /**
     * Delete a Message from a Queue
     * This call will delete the message. Be sure you call this after you’re done with a message,
     * or it will be placed back on the queue.
     *
     * @param $queue_name
     * @param $message_id
     * @return mixed
     */
    public function deleteMessage($queue_name, $message_id, $reservation_id = null)
    {
        $req = array(
            "reservation_id" => $reservation_id,
        );
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/messages/{$message_id}";
        if (is_null($reservation_id)) {
            return $this->apiCall(self::DELETE, $url);
        } else {
            return $this->apiCall(self::DELETE, $url, $req);
        }
    }

    /**
     * Delete Messages from a Queue
     * This call will delete the messages. Be sure you call this after you’re done with a message,
     * or it will be placed back on the queue.
     *
     * @param $queue_name
     * @param $messages
     * @return mixed
     */
    public function deleteMessages($queue_name, $messages)
    {
        $req = array(
            "ids" => array(),
        );
        foreach ($messages as $message) {
            if (is_string($message)) {
                array_push($req['ids'], $message);
            } else if (is_object($message)) {
                array_push($req['ids'], array('id' => $message->id, 'reservation_id' => $message->reservation_id));
            } else if (is_array($message)) {
                array_push($req['ids'], array('id' => $message['id'], 'reservation_id' => $message['reservation_id']));
            }
        }
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/messages";
        $result = $this->apiCall(self::DELETE, $url, $req);
        return self::json_decode($result);
    }

    /**
     * Peek Messages on a Queue
     * Peeking at a queue returns the next messages on the queue, but it does not reserve them.
     *
     * @param string $queue_name
     * @return object|null  message or null if queue is empty
     */
    public function peekMessage($queue_name)
    {
        $messages = $this->peekMessages($queue_name, 1);
        if ($messages == null) {
            return null;
        } else {
            return $messages[0];
        }
    }

    /**
     * Peek Messages on a Queue
     * Peeking at a queue returns the next messages on the queue, but it does not reserve them.
     *
     * @param string $queue_name
     * @param int $count The maximum number of messages to peek. Maximum is 100.
     * @return array|null array of messages or null if queue is empty
     */
    public function peekMessages($queue_name, $count)
    {
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/messages";
        $params = array();
        if ($count !== 1) {
            $params['n'] = (int) $count;
        }
        $this->setJsonHeaders();
        $response = self::json_decode($this->apiCall(self::GET, $url, $params));
        return $response->messages;
    }

    /**
     * Touch a Message on a Queue
     * Touching a reserved message extends its timeout by the duration specified when the message was created,
     * which is 60 seconds by default.
     *
     * @param string $queue_name
     * @param string $message_id
     * @param int $timeout
     * @return mixed
     */
    public function touchMessage($queue_name, $message_id, $reservation_id, $timeout)
    {
        $req = array(
            "reservation_id" => $reservation_id,
        );
        if ($timeout !== 0) {
            $req['timeout'] = (int) $timeout;
        }
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/messages/{$message_id}/touch";
        return self::json_decode($this->apiCall(self::POST, $url, $req));
    }

    /**
     * Release a Message on a Queue
     * Releasing a reserved message unreserves the message and puts it back on the queue,
     * as if the message had timed out.
     *
     * @param string $queue_name
     * @param string $message_id
     * @param string $reservation_id
     * @param int $delay The item will not be available on the queue until this many seconds have passed.
     *                   Default is 0 seconds. Maximum is 604,800 seconds (7 days).
     * @return mixed
     */
    public function releaseMessage($queue_name, $message_id, $reservation_id, $delay)
    {
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $params = array('reservation_id' => $reservation_id);
        if ($delay !== 0) {
            $params['delay'] = (int) $delay;
        }
        $url = "projects/{$this->project_id}/queues/$queue/messages/{$message_id}/release";
        return self::json_decode($this->apiCall(self::POST, $url, $params));
    }

    /**
     * Add alerts to a queue. This is for Pull Queue only.
     *
     * @param string $queue_name
     * @param array $alerts_hash
     * @return mixed
     */
    public function addAlerts($queue_name, $alerts_hash)
    {
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue";
        $options = array(
            'queue' => array(
                'alerts' => $alerts_hash,
            ),
        );
        return self::json_decode($this->apiCall(self::PUT, $url, $options));
    }

    /**
     * Replace current queue alerts with a given list of alerts. This is for Pull Queue only.
     *
     * @param string $queue_name
     * @param array $alerts_hash
     * @return mixed
     */
    public function updateAlerts($queue_name, $alerts_hash)
    {
        return $this->addAlerts($queue_name, $alerts_hash);
    }

    /**
     * Remove alerts from a queue. This is for Pull Queue only.
     *
     * @param string $queue_name
     *
     * @return mixed
     * @deprecated
     */
    public function deleteAlerts($queue_name)
    {
        return $this->addAlerts($queue_name, array());
    }

    /**
     * Remove alert from a queue by its ID. This is for Pull Queue only.
     *
     * @param string $queue_name
     * @param string $alert_id
     * @return mixed
     * @deprecated
     */
    public function deleteAlertById($queue_name, $alert_id)
    {
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/alerts/$alert_id";

        return self::json_decode($this->apiCall(self::DELETE, $url));
    }

    /**
     * Delete a Message Queue
     * This call deletes a message queue and all its messages.
     *
     * @param string $queue_name
     * @return mixed
     */
    public function deleteQueue($queue_name)
    {
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue";
        return self::json_decode($this->apiCall(self::DELETE, $url));
    }

    /**
     * Updates the queue object
     *
     * @param string $queue_name
     * @param array $options Parameters to change. keys:
     *
     * @return object
     */
    public function updateQueue($queue_name, $options)
    {
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue";
        return self::json_decode($this->apiCall(self::PATCH, $url, array('queue' => $options)));
    }

    /**
     * Creates a queue
     *
     * @param string $queue_name
     * @param array $options Parameters to change. keys:
     *
     * @return object
     */
    public function createQueue($queue_name, $options)
    {
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue";
        return self::json_decode($this->apiCall(self::PUT, $url, array('queue' => $options)));
    }

    /**
     * @param $queue_name
     * @param $subscribers_hash - Array of subscribers. keys:
     * - "url" Subscriber url
     * - "name" Name of subscriber
     * @return mixed
     */
    public function addSubscribers($queue_name, $subscribers_hash)
    {
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/subscribers";
        $options = array(
            'subscribers' => $subscribers_hash,
        );
        return self::json_decode($this->apiCall(self::POST, $url, $options));
    }

    /**
     * Add Subscriber to a Queue
     *
     * Example:
     *
     * $ironmq->addSubscriber("test_queue", array("url" => "http://example.com"));
     *
     *
     * @param string $queue_name
     * @param array $subscriber_hash Subscriber. keys:
     * - "url" Subscriber url
     * - "name" Name of subscriber
     * @return mixed
     */
    public function addSubscriber($queue_name, $subscriber_hash)
    {
        return $this->addSubscribers($queue_name, array($subscriber_hash));
    }

    /**
     * Replace old Subscribers with new ones, Older subscribers will be removed.
     *
     * @param $queue_name
     * @param $subscribers_hash - Array of subscribers. keys:
     * - "url" Subscriber url
     * - "name" Name of subscriber
     * @return mixed
     */
    public function replaceSubscribers($queue_name, $subscribers_hash)
    {
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/subscribers";
        $options = array(
            'subscribers' => $subscribers_hash,
        );
        return self::json_decode($this->apiCall(self::PUT, $url, $options));
    }

    /**
     * Replace Subscribers with a new subscriber
     *
     * @param $queue_name
     * @param array $subscriber_hash Subscriber. keys:
     * - "url" Subscriber url
     * - "name" Name of subscriber
     */
    public function replaceSubscriber($queue_name, $subscriber_hash)
    {
        $this->replaceSubscribers($queue_name, array($subscriber_hash));
    }

    /**
     * Remove Subscriber from a Queue
     *
     * @param $queue_name
     * @param array $subscriber_hash Subscriber. keys:
     * - "url" Subscriber url
     * - "name" Name of subscriber
     */
    public function removeSubscriber($queue_name, $subscriber_hash)
    {
        $this->removeSubscribers($queue_name, array($subscriber_hash));
    }

    /**
     * Remove Subscribers from a Queue
     *
     * Example:
     *
     * $ironmq->removeSubscriber("test_queue", array("url" => "http://example.com"));
     *
     *
     * @param string $queue_name
     * @param array $subscriber_hash Subscriber. keys:
     * - "url" Subscriber url
     * @return mixed
     *
     */
    public function removeSubscribers($queue_name, $subscriber_hash)
    {
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/subscribers";
        $options = array(
            'subscribers' => $subscriber_hash,
        );
        return self::json_decode($this->apiCall(self::DELETE, $url, $options));
    }

    /**
     * Get Message's Push Statuses (for Push Queues only)
     *
     * Example:
     *
     * statuses = $ironmq->getMessagePushStatuses("test_queue", $message_id)
     *
     *
     * @param string $queue_name
     * @param string $message_id
     * @return array
     */
    public function getMessagePushStatuses($queue_name, $message_id)
    {
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/messages/{$message_id}/subscribers";
        $response = self::json_decode($this->apiCall(self::GET, $url));
        return $response->subscribers;
    }

    /**
     * Delete Push Message (for Push Queues only)
     *
     * Example:
     *
     * $ironmq->deletePushMessage("test_queue", $message_id, $reservation_id, $subscriber_name)
     *
     *
     * @param string $queue_name
     * @param string $message_id
     * @param string $reservation_id
     * @param string $subscriber_name
     * @return mixed
     */
    public function deletePushMessage($queue_name, $message_id, $reservation_id, $subscriber_name)
    {
        $req = array(
            'reservation_id'  => $reservation_id,
            'subscriber_name' => $subscriber_name,
        );
        $this->setJsonHeaders();
        $queue = rawurlencode($queue_name);
        $url = "projects/{$this->project_id}/queues/$queue/messages/{$message_id}";
        return self::json_decode($this->apiCall(self::DELETE, $url, $req));
    }

    /* PRIVATE FUNCTIONS */

    private function setJsonHeaders()
    {
        $this->setCommonHeaders();
        $token = isset($this->use_keystone) && $this->use_keystone ? $this->getToken() : $this->token;
        $this->headers['Authorization'] = "OAuth {$token}";
    }

    private function setPostHeaders()
    {
        $this->setCommonHeaders();
        $this->headers['Content-Type'] = 'multipart/form-data';
    }
}


<?php

namespace Bit3\FakerCli\Command;

use Faker\Factory;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class GenerateCommand extends Command
{
	protected function configure()
	{
		$this
			->setName('faker:generate')
			->addOption(
				'locale',
				'l',
				InputOption::VALUE_REQUIRED,
				'The locale to use.',
				Factory::DEFAULT_LOCALE
			)
			->addOption(
				'seed',
				's',
				InputOption::VALUE_REQUIRED,
				'The generators seed.'
			)
			->addOption(
				'pattern',
				'p',
				InputOption::VALUE_REQUIRED,
				'The printf pattern.',
				'%s'
			)
			->addOption(
				'delimiter',
				'd',
				InputOption::VALUE_REQUIRED,
				'The delimiter is used by the csv and printf format.',
				','
			)
			->addOption(
				'enclosure',
				'e',
				InputOption::VALUE_REQUIRED,
				'The enclosure is used by the csv and printf format.'
			)
			->addOption(
				'escape',
				'E',
				InputOption::VALUE_REQUIRED,
				'The escape character is used by the printf format.',
				'\\'
			)
			->addOption(
				'format',
				'f',
				InputOption::VALUE_REQUIRED,
				'The output format (json, xml, csv, php, printf, vprintf)',
				'printf'
			)
			->addOption(
				'count',
				'c',
				InputArgument::OPTIONAL,
				'The count of generated data.',
				10
			)
			->addArgument(
				'type',
				InputArgument::REQUIRED,
				'The data type to generate (e.g. "randomDigit", "words", "name", "city")'
			)
			->addArgument(
				'args',
				InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
				'Arguments for the type, e.g. "words 5" will generate 5 words.'
			);
	}

	protected function execute(InputInterface $input, OutputInterface $output)
	{
		$locale = $input->getOption('locale');
		$seed   = $input->getOption('seed');
		$format = $input->getOption('format');
		$count  = $input->getOption('count');
		$type   = $input->getArgument('type');
		$args   = $input->getArgument('args');

		$data = array();

		$faker = Factory::create($locale);

		if ($seed) {
			$faker->seed($seed);
		}

		for ($i = 0; $i < $count; $i++) {
			$data[] = $faker->format($type, $args);
		}

		switch ($format) {
			case 'json':
				$this->outputJson($output, $data);
				break;

			case 'xml':
				$this->outputXml($output, $data);
				break;

			case 'csv':
				$this->outputCsv($input, $output, $data);
				break;

			case 'php':
				$this->outputPhp($output, $data);
				break;

			case 'printf':
				$this->outputPrintf($input, $output, $data);
				break;

			case 'vprintf':
				$this->outputVprintf($input, $output, $data);
				break;

			default:
				throw new \RuntimeException('Unknown output format ' . $format);
		}
	}

	/**
	 * Generate and output the data as JSON.
	 *
	 * @param OutputInterface $output
	 * @param mixed           $data
	 */
	protected function outputJson(OutputInterface $output, $data)
	{
		$json = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
		$output->write($json);
	}

	/**
	 * Generate and output the data as XML.
	 *
	 * @param OutputInterface $output
	 * @param mixed           $data
	 */
	protected function outputXml(OutputInterface $output, $data)
	{
		$doc               = new \DOMDocument();
		$doc->formatOutput = true;
		$doc->appendChild($this->generateXml($doc, $data));
		$xml = $doc->saveXML();
		$output->write($xml);
	}

	/**
	 * Generate a xml element from the input data.
	 *
	 * @param \DOMDocument $doc
	 * @param mixed        $data
	 *
	 * @return \DOMElement
	 */
	protected function generateXml(\DOMDocument $doc, $data)
	{
		if (is_array($data) && range(0, count($data) - 1) == array_keys($data)) {
			// $data is a regular indexed array
			$array = $doc->createElement('array');

			foreach ($data as $value) {
				$entry = $doc->createElement('item');
				$entry->appendChild($this->generateXml($doc, $value));

				$array->appendChild($entry);
			}

			return $array;
		}
		else if (is_array($data) || is_object($data)) {
			// $data is an associative array or object
			$map = $doc->createElement('map');

			foreach ($data as $key => $value) {
				$entry = $doc->createElement('item');
				$entry->setAttribute('key', $key);
				$entry->appendChild($this->generateXml($doc, $value));

				$map->appendChild($entry);
			}

			return $map;
		}
		else {
			// $data is a primitive type
			return $doc->createTextNode($data);
		}
	}

	/**
	 * Generate and output the data as CSV.
	 *
	 * @param OutputInterface $output
	 * @param mixed           $data
	 */
	protected function outputCsv(InputInterface $input, OutputInterface $output, $data)
	{
		$delimiter = $input->getOption('delimiter');
		$enclosure = $input->getOption('enclosure');

		$stream = fopen('php://temp', 'w+');

		foreach ($data as $row) {
			if ($enclosure === null) {
				fputcsv($stream, $this->flattenArray($row), $delimiter);
			}
			else {
				fputcsv($stream, $this->flattenArray($row), $delimiter, $enclosure);
			}
		}

		fseek($stream, 0);
		$csv = stream_get_contents($stream);
		$output->write($csv);
	}

	/**
	 * Flatten an array.
	 *
	 * @param $data
	 *
	 * @return array
	 */
	protected function flattenArray($data)
	{
		if (is_array($data) || is_object($data)) {
			$buffer = array();

			foreach ($data as $item) {
				$buffer = array_merge($buffer, $this->flattenArray($item));
			}

			return $buffer;
		}
		else {
			return (array) $data;
		}
	}

	/**
	 * Generate and output the data as PHP.
	 *
	 * @param OutputInterface $output
	 * @param mixed           $data
	 */
	protected function outputPhp(OutputInterface $output, $data)
	{
		$php = var_export($data, true);
		$output->write($php);
	}

	/**
	 * Generate and output the data as PHP.
	 *
	 * @param OutputInterface $output
	 * @param mixed           $data
	 */
	protected function outputPrintf(InputInterface $input, OutputInterface $output, $data)
	{
		$pattern   = $input->getOption('pattern');
		$delimiter = $input->getOption('delimiter');
		$enclosure = $input->getOption('enclosure');
		$escape    = $input->getOption('escape');

		foreach ($data as $value) {
			$value = $this->flattenArray($value);
			$value = implode($delimiter, $value);

			if ($enclosure) {
				$value = $enclosure . str_replace($enclosure, $escape . $enclosure, $value) . $enclosure;
			}

			$output->writeln(sprintf($pattern, $value));
		}
	}

	/**
	 * Generate and output the data as PHP.
	 *
	 * @param OutputInterface $output
	 * @param mixed           $data
	 */
	protected function outputVprintf(InputInterface $input, OutputInterface $output, $data)
	{
		$pattern   = $input->getOption('pattern');
		$enclosure = $input->getOption('enclosure');
		$escape    = $input->getOption('escape');

		foreach ($data as $values) {
			$values = $this->flattenArray((array) $values);

			if ($enclosure) {
				foreach ($values as $key => $value) {
					$values[$key] = $enclosure . str_replace($enclosure, $escape . $enclosure, $value) . $enclosure;
				}
			}

			$output->writeln(vsprintf($pattern, $values));
		}
	}
}


<?php

namespace Beryllium\CacheBundle\Command;

use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;

/**
 * Provides a command-line interface for viewing cache client stats
 *
 * @author Kevin Boyd <beryllium@beryllium.ca>
 * @license See LICENSE.md
 */
class CacheClientStatsCommand extends ContainerAwareCommand
{
    /**
     * Configure the CLI task
     *
     * @return void
     */
    protected function configure()
    {
        $this->setName('cacheclient:stats')
             ->setDescription('Display Cache Statistics')
             ->addOption('debug', 'd', InputOption::VALUE_NONE, 'Show debugging information');
    }

    /**
     * Execute the CLI task
     *
     * @param InputInterface $input
     * @param OutputInterface $output
     * @return void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $debug = $input->getOption('debug');
        $client = $this->getContainer()->get('beryllium_cache.client');

        if (is_object($client) && method_exists($client, 'getStats')) {
            $text = $this->formatStats($client->getStats(), $debug);
        } else {
            $text = 'Active cache client does not have a stats function.';
        }

        $output->writeln($text);
    }

    /**
     * Format the raw array for the command line report
     *
     * @param array $stats An array of memcache::extendedstats
     * @param bool $debug (default: false) True causes a raw dump of the stats array to be provided
     * @return string ConsoleComponent-formatted output, suitable for ->writeln() usage
     */
    public function formatStats($stats, $debug = false)
    {
        if (!is_array($stats)) {
            return "No statistics returned.\n";
        }

        if (count($stats) == 0) {
            return "No statistics returned.\n";
        }

        $out = "Servers found: " . count($stats) . "\n\n";
        foreach ($stats as $host => $item) {
            if (!is_array($item) || count($item) == 0) {
                $out .= "  <error>" . $host . "</error>\n";

                continue;
            }

            $out .= "<info>Host:\t" . $host . "</info>\n";

            $out .= "\tUsage: " . $this->formatUsage($item['bytes'], $item['limit_maxbytes']) . "\n";
            $out .= "\tUptime: " . $this->formatUptime($item['uptime']) . "\n";
            $out .= "\tOpen Connections: " . $item['curr_connections'] . "\n";
            $out .= "\tHits: " . $item['get_hits'] . "\n";
            $out .= "\tMisses: " . $item['get_misses'] . "\n";
            if ($item['get_hits'] + $item['get_misses'] > 0) {
                $out .= "\tHelpfulness: " . round(
                    $item['get_hits'] / ($item['get_hits'] + $item['get_misses']) * 100,
                    2
                ) . "%\n";
            }

            if ($debug) {
                $out .= "\n";
                foreach ($item as $key => $value) {
                    $out .= "\t" . $key . ': ' . $value . "\n";
                }
            }
        }

        return $out;
    }

    /**
     * Format the usage stats
     *
     * @param integer $bytes Cache usage (in bytes)
     * @param integer $maxbytes Cache maximum size (in bytes)
     * @return string A short string with friendly formatting
     */
    public function formatUsage($bytes, $maxbytes)
    {
        if (!is_numeric($maxbytes) || $maxbytes < 1) {
            return '(undefined)';
        }

        $out = round($bytes / $maxbytes, 3) . "% (";
        $out .= round($bytes / 1024 / 1024, 2) . 'MB of ';
        $out .= round($maxbytes / 1024 / 1024, 2) . 'MB)';

        return $out;
    }

    /**
     * Formats the uptime to be friendlier
     *
     * @param integer $uptime Cache server uptime (in seconds)
     * @return string A short string with friendly formatting
     */
    public function formatUptime($uptime)
    {
        $days = floor($uptime / 24 / 60 / 60);
        $days_remainder = $uptime - ($days * 24 * 60 * 60);
        $hours = floor($days_remainder / 60 / 60);
        $hours_remainder = $days_remainder - ($hours * 60 * 60);
        $minutes = floor($hours_remainder / 60);
        $minutes_remainder = $hours_remainder - ($minutes * 60);
        $seconds = $minutes_remainder;
        $out = $uptime . ' seconds (';

        if ($days > 0) {
            $out .= $days . ' days, ';
        }

        if ($hours > 0) {
            $out .= $hours . ' hours, ';
        }

        if ($minutes > 0) {
            $out .= $minutes . ' minutes, ';
        }

        if ($seconds > 0) {
            $out .= $seconds . ' seconds';
        }

        return $out . ')';
    }
}


<?php

namespace Beryllium\CacheBundle\Client;

use Beryllium\CacheBundle\CacheClientInterface;

/**
 * Client interface for Memcache servers
 *
 * @uses CacheClientInterface
 * @package
 * @version $id$
 * @author Kevin Boyd <beryllium@beryllium.ca>
 * @license See LICENSE.md
 */
class MemcacheClient implements CacheClientInterface
{
    protected $safe = false;
    protected $mem = null;
    protected $servers = array();
    protected $sockttl = 0.2;
    protected $compression = false;
    protected $prefix = '';

    /**
     * Constructs the cache client using an injected Memcache instance
     *
     * @access public
     */
    public function __construct(\Memcache $memcache)
    {
        $this->mem = $memcache;
    }

    /**
     * Add a server to the memcache pool.
     *
     * Does not probe server, does not set Safe to true.
     *
     * Should really be private, or modified to handle the probeServer action itself.
     *
     * @param string $ip Location of memcache server
     * @param int $port Optional: Port number (default: 11211)
     * @access public
     * @return void
     */
    public function addServer($ip, $port = 11211)
    {
        if (is_object($this->mem)) {
            return $this->mem->addServer($ip, $port);
        }
    }

    /**
     * Add an array of servers to the memcache pool
     *
     * Uses ProbeServer to verify that the connection is valid.
     *
     * Format of array:
     *
     *   $servers[ '127.0.0.1' ] = 11211;
     *
     * Logic is somewhat flawed, of course, because it wouldn't let you add multiple
     * servers on the same IP.
     *
     * Serious flaw, right? ;-)
     *
     * @param array $servers See above format definition
     * @access public
     * @return void
     */
    public function addServers(array $servers)
    {
        if (count($servers) == 0) {
            return false;
        }

        foreach ($servers as $ip => $port) {
            if (intval($port) == 0) {
                $port = null;
            }

            if ($this->probeServer($ip, $port)) {
                $status = $this->addServer($ip, $port);
                $this->safe = true;
            }
        }
    }

    /**
     * Spend a few tenths of a second opening a socket to the requested IP and port
     *
     * The purpose of this is to verify that the server exists before trying to add it,
     * to cut down on weird errors when doing ->get(). This could be a controversial or
     * flawed way to go about this.
     *
     * @param string $ip IP address (or hostname, possibly)
     * @param int $port Port that memcache is running on
     * @access public
     * @return boolean True if the socket opens successfully, or false if it fails
     */
    public function probeServer($ip, $port)
    {
        $errno = null;
        $errstr = null;
        $fp = @fsockopen($ip, $port, $errno, $errstr, $this->sockttl);

        if ($fp) {
            fclose($fp);

            return true;
        } else {
            return false;
        }
    }

    /**
     * Retrieve a value from memcache
     *
     * @param string|array $key Unique identifier or array of identifiers
     * @access public
     * @return mixed Requested value, or false if an error occurs
     */
    public function get($key)
    {
        if ($this->isSafe()) {
            $key = $this->prefix . $key;
            return $this->mem->get($key);
        }

        return false;
    }

    /**
     * Add a value to the memcache
     *
     * @param string $key Unique key
     * @param mixed $value A value. I recommend a string, be it serialized or not - other values haven't been tested :)
     * @param int $ttl Number of seconds for the value to be valid for
     * @access public
     * @return void
     */
    public function set($key, $value, $ttl)
    {
        if ($this->isSafe()) {
            $key = $this->prefix . $key;
            return $this->mem->set($key, $value, $this->compression, $ttl);
        }

        return false;
    }

    /**
     * Delete a value from the memcache
     *
     * @param string $key Unique key
     * @access public
     * @return void
     */
    public function delete($key)
    {
        if ($this->isSafe()) {
            $key = $this->prefix . $key;
            return $this->mem->delete($key, 0);
        }

        return false;
    }

    /**
     * Check if the cache is live
     *
     * @access public
     * @return True if a valid server has been added, otherwise false
     */
    public function isSafe()
    {
        return $this->safe;
    }

    /**
     * getStats returns the result of Memcache::getExtendedStats(), an associative array
     * containing arrays of server stats
     *
     * @access public
     * @return array Server stats array
     */
    public function getStats()
    {
        return $this->mem->getExtendedStats();
    }

    /**
     * @param string $prefix
     */
    public function setPrefix($prefix)
    {
        $this->prefix = $prefix;
    }
}


<?php

namespace Beryllium\CacheBundle;

use Beryllium\CacheBundle\CacheInterface;
use Beryllium\CacheBundle\CacheClientInterface;

/**
 * Cache
 *
 * @uses CacheInterface
 * @package
 * @version $id$
 * @author Kevin Boyd <beryllium@beryllium.ca>
 * @license See LICENSE.md
 */
class Cache implements CacheInterface
{
    public $dic = false;

    protected $client = null;
    protected $safe = false;
    protected $ttl = 300;

    /**
     * Prep the cache
     *
     * @param CacheClientInterface $client Optional cache object/service
     * @access public
     * @return void
     */
    public function __construct(CacheClientInterface $client = null)
    {
        if (!empty($client)) {
            if (is_object($client) && ($client instanceof CacheClientInterface)) {
                $this->client = $client;
            }
            else {
                throw new \Exception('Invalid Cache Client Interface');
            }
        }
    }

    /**
     * Inject a dependency injection container (optional)
     *
     * @param mixed $dic The container
     * @access public
     * @return void
     */
    public function setContainer($dic)
    {
        $this->dic = $dic;
    }

    /**
     * Change the default lifetime of the data (default: 300 seconds - five minutes)
     *
     * @param int $ttl
     * @access public
     * @return void
     */
    public function setTtl($ttl)
    {
        $this->ttl = $ttl;
    }

    /**
     * Inject a cache client interface to interact with a custom cache service
     *
     * @param CacheClientInterface $client The client object or service
     * @access public
     * @return void
     */
    public function setClient(CacheClientInterface $client)
    {
        if (is_object($client) && ($client instanceof CacheClientInterface))
            $this->client = $client;
        else {
            throw new \Exception('Invalid Cache Client Interface');
        }
    }

    /**
     * Retrieve a value from the cache using the provided key
     *
     * @param string|array $key The unique key or array of keys identifying the data to be retrieved.
     * @access public
     * @return mixed The requested data, or false if there is an error
     */
    public function get($key)
    {
        if ($this->isSafe() && !empty($key)) {
            return $this->client->get($key);
        }

        return false;
    }

    /**
     * Add a key/value to the cache
     *
     * @param string $key A unique key to identify the data you want to store
     * @param string $value The value you want to store in the cache
     * @param int $ttl Optional: Lifetime of the data
     * @access public
     * @return mixed Whatever the CacheClientObject returns, or false.
     */
    public function set($key, $value, $ttl = null)
    {
        $ttl = (null !== $ttl) ? $ttl : $this->ttl;

        if ($this->isSafe() && !empty($key)) {
            return $this->client->set($key, $value, $ttl);
        }

        return false;
    }

    /**
     * Delete a key from the cache
     *
     * @param string $key Unique key
     * @access public
     * @return void
     */
    public function delete($key)
    {
        if ($this->isSafe() && !empty($key)) {
            return $this->client->delete($key);
        }

        return false;
    }

    /**
     * Checks if the cache is in a usable state
     *
     * @access public
     * @return boolean True if the cache is usable, otherwise false
     */
    public function isSafe()
    {
        if ($this->client instanceof CacheClientInterface) {
            return $this->client->isSafe();
        }

        return $this->safe;
    }
}


<?php

namespace NotificationChannels\FCM;

use Illuminate\Support\Arr;
use LaravelFCM\Message\Options;
use LaravelFCM\Message\PayloadData;
use LaravelFCM\Message\OptionsBuilder;
use LaravelFCM\Message\OptionsPriorities;
use LaravelFCM\Message\PayloadDataBuilder;
use LaravelFCM\Message\PayloadNotification;
use LaravelFCM\Message\PayloadNotificationBuilder;
use NotificationChannels\FCM\Exceptions\InvalidArgumentException;

class FCMMessage
{
    /**
     * @var array
     */
    const OPTIONS_SETTERS = [
        'setDryRun'                => 'dry_run',
        'setPriority'              => 'priority',
        'setTimeToLive'            => 'time_to_live',
        'setCollapseKey'           => 'collapse_key',
        'setMutableContent'        => 'mutable_content',
        'setDelayWhileIdle'        => 'delay_while_idle',
        'setContentAvailable'      => 'content_available',
        'setRestrictedPackageName' => 'restricted_package_name',
    ];

    /**
     * @var array
     */
    const OPTIONS_GETTERS = [
        'isDryRun'                 => 'dry_run',
        'getPriority'              => 'priority',
        'getTimeToLive'            => 'time_to_live',
        'getCollapseKey'           => 'collapse_key',
        'isMutableContent'         => 'mutable_content',
        'isDelayWhileIdle'         => 'delay_while_idle',
        'isContentAvailable'       => 'content_available',
        'getRestrictedPackageName' => 'restricted_package_name',
    ];

    /**
     * @var array
     */
    const NOTIFICATION_SETTERS = [
        'setTag'               => 'tag',
        'setBody'              => 'body',
        'setChannelId'         => 'android_channel_id',
        'setIcon'              => 'icon',
        'setTitle'             => 'title',
        'setSound'             => 'sound',
        'setBadge'             => 'badge',
        'setColor'             => 'color',
        'setClickAction'       => 'click_action',
        'setBodyLocationKey'   => 'body_loc_key',
        'setBodyLocationArgs'  => 'body_loc_args',
        'setTitleLocationKey'  => 'title_loc_key',
        'setTitleLocationArgs' => 'title_loc_args',
    ];

    /**
     * @var array
     */
    const NOTIFICATION_GETTERS = [
        'getTag'               => 'tag',
        'getBody'              => 'body',
        'getChannelId'         => 'android_channel_id',
        'getIcon'              => 'icon',
        'getTitle'             => 'title',
        'getSound'             => 'sound',
        'getBadge'             => 'badge',
        'getColor'             => 'color',
        'getClickAction'       => 'click_action',
        'getBodyLocationKey'   => 'body_loc_key',
        'getBodyLocationArgs'  => 'body_loc_args',
        'getTitleLocationKey'  => 'title_loc_key',
        'getTitleLocationArgs' => 'title_loc_args',
    ];

    /**
     * @var array
     */
    const OPTIONS_MAP = [
        'dry_run'                 => 'setDryRun',
        'priority'                => 'setPriority',
        'time_to_live'            => 'setTimeToLive',
        'collapse_key'            => 'setCollapseKey',
        'mutable_content'         => 'setMutableContent',
        'delay_while_idle'        => 'setDelayWhileIdle',
        'content_available'       => 'setContentAvailable',
        'restricted_package_name' => 'setRestrictedPackageName',
    ];

    /**
     * @var array
     */
    const NOTIFICATION_MAP = [
        'tag'                => 'setTag',
        'body'               => 'setBody',
        'android_channel_id' => 'setChannelId',
        'icon'               => 'setIcon',
        'title'              => 'setTitle',
        'sound'              => 'setSound',
        'badge'              => 'setBadge',
        'color'              => 'setColor',
        'click_action'       => 'setClickAction',
        'body_loc_key'       => 'setBodyLocationKey',
        'body_loc_args'      => 'setBodyLocationArgs',
        'title_loc_key'      => 'setTitleLocationKey',
        'title_loc_args'     => 'setTitleLocationArgs',
    ];

    /**
     * @var mixed
     */
    protected $recipient;

    /**
     * @var mixed
     */
    protected $options;

    /**
     * @var mixed
     */
    protected $notification;

    /**
     * @var mixed
     */
    protected $data;

    /**
     * Message Constructor.
     *
     * @param mixed $options
     * @param mixed $notification
     * @param mixed $data
     */
    public function __construct($options = null, $notification = null, $data = null)
    {
        $this->options($options);
        $this->notification($notification);
        $this->data($data);
    }

    /**
     * Call method.
     *
     * @param  string $method
     * @param  mixed $value
     *
     * @return mixed
     * @throws \BadMethodCallException
     */
    public function __call($method, array $arguments)
    {
        if (Arr::has(static::OPTIONS_SETTERS, $method)) {
            $this->options([
                Arr::get(static::OPTIONS_SETTERS, $method) => Arr::get($arguments, 0),
            ]);

            return $this;
        } elseif (Arr::has(static::OPTIONS_GETTERS, $method)) {
            $value = null;
            if ($this->options && $this->options instanceof Options) {
                $property = Arr::get(static::OPTIONS_GETTERS, $method);

                $value = Arr::get($this->options->toArray(), $property);
            }

            return $value;
        } elseif (Arr::has(static::NOTIFICATION_SETTERS, $method)) {
            $this->notification([
                Arr::get(static::NOTIFICATION_SETTERS, $method) => Arr::get($arguments, 0),
            ]);

            return $this;
        } elseif (Arr::has(static::NOTIFICATION_GETTERS, $method)) {
            $value = null;
            if ($this->notification && $this->notification instanceof PayloadNotification) {
                $property = Arr::get(static::NOTIFICATION_GETTERS, $method);

                $value = Arr::get($this->notification->toArray(), $property);
            }

            return $value;
        } else {
            throw new \BadMethodCallException("Method {$method} does not exist.");
        }
    }

    /**
     * Create Message.
     *
     * @param mixed $options
     * @param mixed $notification
     * @param mixed $data
     */
    public static function create($options = null, $notification = null, $data = null)
    {
        return new static(...func_get_args());
    }

    /**
     * Recipient.
     *
     * @return $this
     */
    public function to($recipient)
    {
        $this->recipient = $recipient;

        return $this;
    }

    /**
     * Get Recipient.
     *
     * @return mixed
     */
    public function getRecipient()
    {
        return $this->recipient;
    }

    /**
     * Make Invalid Argument Exception.
     *
     * @param  mixed  $data
     * @param  string $method
     * @param  string $container
     * @param  string $builder
     * @return \NotificationChannels\FCM\Exceptions\InvalidArgumentException
     */
    protected static function makeInvalidArgumentException($data, $method, $container, $builder)
    {
        $type = gettype($data);
        if (is_object($data)) {
            $type = get_class($data);
        }

        return new InvalidArgumentException(
           'The argument for %s::%s must be instanceof %s, %s, null or array. %s given.',
            [
                self::class, $method, $container, $builder, $type,
            ]
        );
    }

    /**
     * Populate Builder.
     *
     * @param  object $builder
     * @param  array  $map
     * @param  array  $data
     * @return object
     */
    protected static function populateBuilder($builder, array $map, array $data)
    {
        foreach ($map as $key => $method) {
            $value = Arr::get($data, $key);
            $builder->{$method}($value);
        }

        return $builder;
    }

    /**
     * Set Options.
     *
     * @param mixed $options
     * @return
     */
    public function options($options = null)
    {
        if (is_array($options)) {
            if ($this->options && $this->options instanceof Options) {
                $options = array_merge($this->options->toArray(), $options);
            }
            if (! Arr::has($options, 'priority')) {
                $options['priority'] = OptionsPriorities::normal;
            }
            $builder = new OptionsBuilder();
            $options = static::populateBuilder($builder, static::OPTIONS_MAP, $options);
        }
        if ($options instanceof Options) {
            $this->options = $options;
        } elseif ($options instanceof OptionsBuilder) {
            $this->options = $options->build();
        } elseif (! is_null($options)) {
            throw static::makeInvalidArgumentException(
                $options,
                'options',
                Options::class,
                OptionsBuilder::class
            );
        }

        return $this;
    }

    /**
     * Set Notification.
     *
     * @param  mixed $notification
     * @return $this
     */
    public function notification($notification = null)
    {
        if (is_array($notification)) {
            if ($this->notification && $this->notification instanceof PayloadNotification) {
                $notification = array_merge($this->notification->toArray(), $notification);
            }
            $builder = new PayloadNotificationBuilder();
            $notification = static::populateBuilder($builder, static::NOTIFICATION_MAP, $notification);
        }
        if ($notification instanceof PayloadNotification) {
            $this->notification = $notification;
        } elseif ($notification instanceof PayloadNotificationBuilder) {
            $this->notification = $notification->build();
        } elseif (! is_null($notification)) {
            throw static::makeInvalidArgumentException(
                $notification,
                'notification',
                PayloadNotification::class,
                PayloadNotificationBuilder::class
            );
        }

        return $this;
    }

    /**
     * Set Data.
     *
     * @param mixed $data
     * @return $this
     */
    public function data($data = null)
    {
        if (is_array($data)) {
            if ($this->data && $this->data instanceof PayloadData) {
                $data = array_merge($this->data->toArray(), $data);
            }
            $data = (new PayloadDataBuilder())->setData($data);
        }

        if ($data instanceof PayloadData) {
            $this->data = $data;
        } elseif ($data instanceof PayloadDataBuilder) {
            $this->data = $data->build();
        } elseif (! is_null($data)) {
            throw static::makeInvalidArgumentException(
                $data,
                'data',
                PayloadData::class,
                PayloadDataBuilder::class
            );
        }

        return $this;
    }

    public function getData()
    {
        return $this->data;
    }

    public function getOptions()
    {
        return $this->options;
    }

    public function getNotification()
    {
        return $this->notification;
    }

    public function getArgs()
    {
        return [
            $this->getRecipient(),
            $this->getOptions(),
            $this->getNotification(),
            $this->getData(),
        ];
    }

    /**
     * Determine if recipient is not given.
     *
     * @return bool
     */
    public function recipientNotGiven()
    {
        return ! $this->recipient;
    }
}


<?php

namespace NotificationChannels\FCM;

use Illuminate\Notifications\ChannelManager;

class ServiceProvider extends \Illuminate\Support\ServiceProvider
{
    /**
     * Bootstrap the application services.
     */
    public function boot()
    {
        $this->app->make(ChannelManager::class)->extend('fcm', function () {
            return new FCMChannel($this->app->make('fcm.sender'), $this->app->make('events'));
        });
    }

    /**
     * Register any package services.
     */
    public function register()
    {
        $this->app->register(\LaravelFCM\FCMServiceProvider::class);
    }
}


<?php

namespace NotificationChannels\FCM;

use LaravelFCM\Sender\FCMSender;
use Illuminate\Events\Dispatcher;
use Illuminate\Notifications\Notification;
use NotificationChannels\FCM\Exceptions\CouldNotSendNotification;

class FCMChannel
{
    /**
     * @var \LaravelFCM\Sender\FCMSender
     */
    protected $sender;

    /**
     * @var \Illuminate\Events\Dispatcher
     */
    protected $events;

    /**
     * Constructor.
     *
     * @param \LaravelFCM\Sender\FCMSender $sender
     */
    public function __construct(FCMSender $sender, Dispatcher $events)
    {
        $this->sender = $sender;
        $this->events = $events;
    }

    /**
     * Send the given notification.
     *
     * @param mixed $notifiable
     * @param \Illuminate\Notifications\Notification $notification
     *
     * @throws \NotificationChannels\FCM\Exceptions\CouldNotSendNotification
     *
     * @return \LaravelFCM\Response\DownstreamResponse|null
     */
    public function send($notifiable, Notification $notification)
    {
        $message = $notification->toFCM($notifiable);
        if ($message->recipientNotGiven()) {
            $to = $notifiable->routeNotificationFor('FCM');
            if (is_array($to) && empty($to)) {
                return;
            }
            if (! $to) {
                throw CouldNotSendNotification::missingRecipient();
            }
            $message->to($to);
        }
        $method = 'sendTo';
        if ($message instanceof FCMMessageTopic) {
            $method .= 'Topic';
        } elseif ($message instanceof FCMMessageGroup) {
            $method .= 'Group';
        }

        $response = $this->sender->{$method}(...$message->getArgs());

        $this->events->fire(new MessageWasSended($response, $notifiable));

        return $response;
    }
}


<?php

namespace NotificationChannels\FCM;

use LaravelFCM\Message\Topics;
use NotificationChannels\FCM\Exceptions\InvalidArgumentException;

class FCMMessageTopic extends FCMMessage
{
    /**
     * {@inheritdoc}
     */
    public function to($recipient)
    {
        if (! ($recipient instanceof Topics)) {
            $type = gettype($recipient);
            if (is_object($recipient)) {
                $type = get_class($recipient);
            }

            return new InvalidArgumentException(
               'The argument for %s::%s must be instanceof %s, null or array. %s given.',
                [
                    self::class, 'to', Topics::class, $type,
                ]
            );
        }

        return parent::to($recipient);
    }
}


<?php
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *	   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package log4php
 */

/**
 * This is a very simple filter based on level matching.
 *
 *
The filter admits two options <var>LevelToMatch</var> and
 * <var>AcceptOnMatch</var>. If there is an exact match between the value
 * of the <var>LevelToMatch</var> option and the level of the
 * {@link LoggerLoggingEvent}, then the {@link decide()} method returns
 * {@link LoggerFilter::ACCEPT} in case the <var>AcceptOnMatch</var>
 * option value is set to true, if it is false then
 * {@link LoggerFilter::DENY} is returned. If there is no match,
 * {@link LoggerFilter::NEUTRAL} is returned.

 *
 *

 * An example for this filter:
 *
 * {@example ../../examples/php/filter_levelmatch.php 19}
 *
 *

 * The corresponding XML file:
 *
 * {@example ../../examples/resources/filter_levelmatch.xml 18}
 *
 * @version $Revision: 1213283 $
 * @package log4php
 * @subpackage filters
 * @since 0.6
 */
class LoggerFilterLevelMatch extends LoggerFilter {

	/**
	 * Indicates if this event should be accepted or denied on match
	 * @var boolean
	 */
	protected $acceptOnMatch = true;

	/**
	 * The level, when to match
	 * @var LoggerLevel
	 */
	protected $levelToMatch;

	/**
	 * @param boolean $acceptOnMatch
	 */
	public function setAcceptOnMatch($acceptOnMatch) {
		$this->setBoolean('acceptOnMatch', $acceptOnMatch);
	}

	/**
	 * @param string $l the level to match
	 */
	public function setLevelToMatch($level) {
		$this->setLevel('levelToMatch', $level);
	}

	/**
	 * Return the decision of this filter.
	 *
	 * Returns {@link LoggerFilter::NEUTRAL} if the <var>LevelToMatch</var>
	 * option is not set or if there is not match.	Otherwise, if there is a
	 * match, then the returned decision is {@link LoggerFilter::ACCEPT} if the
	 * <var>AcceptOnMatch</var> property is set to true. The
	 * returned decision is {@link LoggerFilter::DENY} if the
	 * <var>AcceptOnMatch</var> property is set to false.
	 *
	 * @param LoggerLoggingEvent $event
	 * @return integer
	 */
	public function decide(LoggerLoggingEvent $event) {
		if($this->levelToMatch === null) {
			return LoggerFilter::NEUTRAL;
		}

		if($this->levelToMatch->equals($event->getLevel())) {
			return $this->acceptOnMatch ? LoggerFilter::ACCEPT : LoggerFilter::DENY;
		} else {
			return LoggerFilter::NEUTRAL;
		}
	}
}


<?php
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package log4php
 */

/**
 * The output of the LoggerXmlLayout consists of a series of log4php:event elements.
 *
 * Configurable parameters:
 * - {@link $locationInfo} - If set to true then the file name and line number
 *   of the origin of the log statement will be included in output.
 * - {@link $log4jNamespace} - If set to true then log4j namespace will be used
 *   instead of log4php namespace. This can be usefull when using log viewers
 *   which can only parse the log4j namespace such as Apache Chainsaw.
 *
 *
It does not output a complete well-formed XML file.
 * The output is designed to be included as an external entity in a separate file to form
 * a correct XML file.

 *
 * Example:
 *
 * {@example ../../examples/php/layout_xml.php 19}

 *
 * {@example ../../examples/resources/layout_xml.properties 18}

 *
 * The above would print:
 *
 *

 * <log4php:eventSet xmlns:log4php="http://logging.apache.org/log4php/" version="0.3" includesLocationInfo="true">
 * 	<log4php:event logger="root" level="INFO" thread="13802" timestamp="1252456226491">
 * 		<log4php:message><![CDATA[Hello World!]]></log4php:message>
 * 		<log4php:locationInfo class="main" file="examples/php/layout_xml.php" line="6" method="main" />
 * 	</log4php:event>
 * </log4php:eventSet>
 *

 *
 * @version $Revision: 1213283 $
 * @package log4php
 * @subpackage layouts
 */
class LoggerLayoutXml extends LoggerLayout {
	const LOG4J_NS_PREFIX ='log4j';
	const LOG4J_NS = 'http://jakarta.apache.org/log4j/';

	const LOG4PHP_NS_PREFIX = 'log4php';
	const LOG4PHP_NS = 'http://logging.apache.org/log4php/';

	const CDATA_START = '<![CDATA[';
	const CDATA_END = ']]>';
	const CDATA_PSEUDO_END = ']]>';
	const CDATA_EMBEDDED_END = ']]>]]><![CDATA[';

	/**
	 * If set to true then the file name and line number of the origin of the
	 * log statement will be output.
	 * @var boolean
	 */
	protected $locationInfo = true;

	/**
	 * If set to true, log4j namespace will be used instead of the log4php
	 * namespace.
	 * @var boolean
	 */
	protected $log4jNamespace = false;

	/** The namespace in use. */
	protected $namespace = self::LOG4PHP_NS;

	/** The namespace prefix in use */
	protected $namespacePrefix = self::LOG4PHP_NS_PREFIX;

	public function activateOptions() {
		if ($this->getLog4jNamespace()) {
			$this->namespace        = self::LOG4J_NS;
			$this->namespacePrefix  = self::LOG4J_NS_PREFIX;
		} else {
			$this->namespace        = self::LOG4PHP_NS;
			$this->namespacePrefix  = self::LOG4PHP_NS_PREFIX;
		}
	}

	/**
	 * @return string
	 */
	public function getHeader() {
		return "<{$this->namespacePrefix}:eventSet ".
			"xmlns:{$this->namespacePrefix}=\"{$this->namespace}\" ".
			"version=\"0.3\" ".
			"includesLocationInfo=\"".($this->getLocationInfo() ? "true" : "false")."\"".
			">" . PHP_EOL;
	}

	/**
	 * Formats a {@link LoggerLoggingEvent} in conformance with the log4php.dtd.
	 *
	 * @param LoggerLoggingEvent $event
	 * @return string
	 */
	public function format(LoggerLoggingEvent $event) {
		$ns = $this->namespacePrefix;

		$loggerName = $event->getLoggerName();
		$timeStamp = number_format((float)($event->getTimeStamp() * 1000), 0, '', '');
		$thread = $event->getThreadName();
		$level = $event->getLevel()->toString();

		$buf  = "<$ns:event logger=\"{$loggerName}\" level=\"{$level}\" thread=\"{$thread}\" timestamp=\"{$timeStamp}\">".PHP_EOL;
		$buf .= "<$ns:message>";
		$buf .= $this->encodeCDATA($event->getRenderedMessage());
		$buf .= "</$ns:message>".PHP_EOL;

		$ndc = $event->getNDC();
		if(!empty($ndc)) {
			$buf .= "<$ns:NDC><![CDATA[";
			$buf .= $this->encodeCDATA($ndc);
			$buf .= "]]></$ns:NDC>".PHP_EOL;
		}

		$mdcMap = $event->getMDCMap();
		if (!empty($mdcMap)) {
			$buf .= "<$ns:properties>".PHP_EOL;
			foreach ($mdcMap as $name=>$value) {
				$buf .= "<$ns:data name=\"$name\" value=\"$value\" />".PHP_EOL;
			}
			$buf .= "</$ns:properties>".PHP_EOL;
		}

		if ($this->getLocationInfo()) {
			$locationInfo = $event->getLocationInformation();
			$buf .= "<$ns:locationInfo ".
					"class=\"" . $locationInfo->getClassName() . "\" ".
					"file=\"" .  htmlentities($locationInfo->getFileName(), ENT_QUOTES) . "\" ".
					"line=\"" .  $locationInfo->getLineNumber() . "\" ".
					"method=\"" . $locationInfo->getMethodName() . "\" ";
			$buf .= "/>".PHP_EOL;
		}

		$buf .= "</$ns:event>".PHP_EOL;

		return $buf;
	}

	/**
	 * @return string
	 */
	public function getFooter() {
		return "</{$this->namespacePrefix}:eventSet>" . PHP_EOL;
	}

	/**
	 * Whether or not file name and line number will be included in the output.
	 * @return boolean
	 */
	public function getLocationInfo() {
		return $this->locationInfo;
	}

	/**
	 * The {@link $locationInfo} option takes a boolean value. By default,
	 * it is set to false which means there will be no location
	 * information output by this layout. If the the option is set to
	 * true, then the file name and line number of the statement at the
	 * origin of the log statement will be output.
	 */
	public function setLocationInfo($flag) {
		$this->setBoolean('locationInfo', $flag);
	}

	/**
	 * @return boolean
	 */
	 public function getLog4jNamespace() {
	 	return $this->log4jNamespace;
	 }

	/**
	 * @param boolean
	 */
	public function setLog4jNamespace($flag) {
		$this->setBoolean('log4jNamespace', $flag);
	}

	/**
	 * Encases a string in CDATA tags, and escapes any existing CDATA end
	 * tags already present in the string.
	 * @param string $string
	 */
	private function encodeCDATA($string) {
		$string = str_replace(self::CDATA_END, self::CDATA_EMBEDDED_END, $string);
		return self::CDATA_START . $string . self::CDATA_END;
	}
}


<?php
namespace FACTFinder\Core\Server;

use FACTFinder\Loader as FF;

/**
 * Assembles URLs to the FACT-Finder server for different kinds of
 * authentication based on the given parameters and the configuration.
 */
class UrlBuilder
{
    /**
     * @var FACTFinder\Util\LoggerInterface
     */
    private $log;

    /**
     * @var ConfigurationInterface
     */
    protected $configuration;

    /**
     * @param string $loggerClass Class name of logger to use. The class should
     *        implement FACTFinder\Util\LoggerInterface.
     * @param ConfigurationInterface $configuration
     * @param FACTFinder\Util\Parameters $parameters Optional parameters object
     *        to initialize the UrlBuilder with.
     */
    public function __construct(
        $loggerClass,
        \FACTFinder\Core\ConfigurationInterface $configuration
    ) {
        $this->log = $loggerClass::getLogger(__CLASS__);
        $this->log->info("Initializing URL Builder.");

        $this->configuration = $configuration;
    }

    /**
     * Get URL without authentication data.
     * Note that this method may set a channel parameter if there is none
     * already.
     *
     * @param string $action The action to be targeted on the FACT-Finder
     *        server.
     * @param FACTFinder\Util\Parameters $parameters The parameters object from
     *        which to build the URL.
     *
     * @return string The full URL.
     */
    public function getNonAuthenticationUrl(
        $action,
        \FACTFinder\Util\Parameters $parameters
    ) {
        $configuration = $this->configuration;

        $this->ensureChannelParameter($parameters);

        $url = $this->buildAddress($action)
             . (count($parameters) ? '?' : '') . $parameters->toJavaQueryString();

        return $url;
    }

    /**
     * Returns a full URL with authentication data. The type of authentication
     * is determined from the configuration.
     * Note that this method may set a channel parameter if there is none
     * already.
     *
     * @param string $action The action to be targeted on the FACT-Finder
     *        server.
     * @param FACTFinder\Util\Parameters $parameters The parameters object from
     *        which to build the URL.
     *
     * @return string The full URL.
     *
     * @throws Exception if no valid authentication type was configured.
     */
    public function getAuthenticationUrl(
        $action,
        \FACTFinder\Util\Parameters $parameters
    ) {
        $this->ensureChannelParameter($parameters);

        $c = $this->configuration;
        if ($c->isAdvancedAuthenticationType())
            return $this->getAdvancedAuthenticationUrl($action, $parameters);
        else if ($c->isSimpleAuthenticationType())
            return $this->getSimpleAuthenticationUrl($action, $parameters);
        else if ($c->isHttpAuthenticationType())
            return $this->getHttpAuthenticationUrl($action, $parameters);
        else
            throw new \Exception('Invalid authentication type configured.');
    }

    /**
     * Get URL with advanced authentication encryption.
     *
     * @param string $action The action to be targeted on the FACT-Finder
     *        server.
     * @param FACTFinder\Util\Parameters $parameters The parameters object from
     *        which to build the URL.
     *
     * @return string The full URL.
     */
    protected function getAdvancedAuthenticationUrl(
        $action,
        \FACTFinder\Util\Parameters $parameters
    ) {
        $configuration = $this->configuration;

        $ts         = time() . '000'; //milliseconds needed
        $prefix     = $configuration->getAuthenticationPrefix();
        $postfix    = $configuration->getAuthenticationPostfix();
        $hashedPW   = md5($prefix
                    . $ts
                    . md5($configuration->getPassword())
                    . $postfix);
        $authenticationParameters = 'timestamp=' . $ts
                                  . '&username=' . $configuration->getUserName()
                                  . '&password=' . $hashedPW;

        $url = $this->buildAddress($action)
             . '?' . $parameters->toJavaQueryString()
             . (count($parameters) ? '&' : '') . $authenticationParameters;

        $this->log->info("Request Url: " . $url);
        return $url;
    }

    /**
     * Get URL with simple authentication encryption.
     *
     * @param string $action The action to be targeted on the FACT-Finder
     *        server.
     * @param FACTFinder\Util\Parameters $parameters The parameters object from
     *        which to build the URL.
     *
     * @return string The full URL.
     */
    protected function getSimpleAuthenticationUrl(
        $action,
        \FACTFinder\Util\Parameters $parameters
    ) {
        $configuration = $this->configuration;

        $ts = time() . '000'; //milliseconds needed but won't be considered
        $authenticationParameters = "timestamp=" . $ts
                        . '&username=' . $configuration->getUserName()
                        . '&password=' . md5($configuration->getPassword());

        $url = $this->buildAddress($action)
             . '?' . $parameters->toJavaQueryString()
             . (count($parameters) ? '&' : '') . $authenticationParameters;

        $this->log->info("Request Url: " . $url);
        return $url;
    }

    /**
     * Get URL with HTTP authentication.
     *
     * @param string $action The action to be targeted on the FACT-Finder
     *        server.
     * @param FACTFinder\Util\Parameters $parameters The parameters object from
     *        which to build the URL.
     *
     * @return string The full URL.
     */
    protected function getHttpAuthenticationUrl(
        $action,
        \FACTFinder\Util\Parameters $parameters
    ) {
        $configuration = $this->configuration;

        $authentication = sprintf(
            '%s:%s@',
            $configuration->getUserName(),
            $configuration->getPassword()
        );
        if ($authentication == ':@') $authentication = '';

        $url = $this->buildAddress($action, true)
             . (count($parameters) ? '?' : '') . $parameters->toJavaQueryString();

        $this->log->info("Request Url: " . $url);
        return $url;
    }

    /**
     * If no channel is set, try to fill it from configuration data.
     *
     * @param FACTFinder\Util\Parameters $parameters The parameters object to
     *        check.
     */
    protected function ensureChannelParameter($parameters) {
        if ((!isset($parameters['channel'])
            || $parameters['channel'] == '')
            && $this->configuration->getChannel() != ''
        ) {
            $parameters['channel'] = $this->configuration->getChannel();
        }
    }

    protected function buildAddress($action, $includeHttpAuthentication = false)
    {
        $configuration = $this->configuration;

        $authentication = '';
        if ($includeHttpAuthentication
            && $configuration->getUserName() != ''
            && $configuration->getPassword() != ''
        ) {
            $authentication = sprintf(
                '%s:%s@',
                $configuration->getUserName(),
                $configuration->getPassword()
            );
        }

        return $configuration->getRequestProtocol() . '://'
             . $authentication . $configuration->getServerAddress()
             . ':' . $configuration->getServerPort()
             . '/' . $configuration->getContext()
             . '/' . $action;
    }
}


<?php
namespace FACTFinder\Adapter;

use FACTFinder\Loader as FF;

class SimilarRecords extends ConfigurableResponse
{
    /**
     * @var FACTFinder\Util\LoggerInterface
     */
    private $log;

    /**
     * @var mixed[]
     * @see getSimilarAttributes()
     */
    private $similarAttributes;

    /**
     * @var FACTFinder\Data\Result
     */
    private $similarRecords;

    public function __construct(
        $loggerClass,
        \FACTFinder\Core\ConfigurationInterface $configuration,
        \FACTFinder\Core\Server\Request $request,
        \FACTFinder\Core\Client\UrlBuilder $urlBuilder,
        \FACTFinder\Core\AbstractEncodingConverter $encodingConverter = null
    ) {
        parent::__construct($loggerClass, $configuration, $request,
                            $urlBuilder, $encodingConverter);

        $this->log = $loggerClass::getLogger(__CLASS__);

        $this->request->setAction('SimilarRecords.ff');
        $this->parameters['format'] = 'json';

        $this->useJsonResponseContentProcessor();
    }

    /**
     * Set the maximum amount of similar records to be fetched.
     *
     * @param int $recordCount The number of records to be fetched. Something
     *        else than a positive integer is passed, the record count will be
     *        unlimited (or determined by FACT-Finder).
     */
    public function setRecordCount($recordCount)
    {
        $parameters = $this->request->getParameters();
        if (is_numeric($recordCount)
            && (int)$recordCount == (float)$recordCount // Is integer?
            && $recordCount > 0
        ) {
            $parameters['maxRecordCount'] = $recordCount;
        }
        else
        {
            unset($parameters['maxRecordCount']);
        }
        // Make sure that the records are fetched again. In principle, we only
        // have to do this when recordCount increases.
        $this->upToDate = false;
    }

    /**
     * Set one product IDs to get similar records for.
     *
     * @param string $productID
     */
    public function setProductID($productID)
    {
        $parameters = $this->request->getParameters();
        $parameters['id'] = $productID;
        $this->upToDate = false;
    }

    /**
     * Returns the attributes based on which similar records are determined as
     * well as the source product's values of those attributes. If no ID has
     * been set, there will be a warning raised and an empty array will be
     * returned.
     *
     * @return mixed[] Attribute names as keys and attribute values as values.
     */
    public function getSimilarAttributes()
    {
        if (is_null($this->similarAttributes)
            || !$this->upToDate
        ) {
            $this->similarAttributes = $this->createSimilarAttributes();
            $this->upToDate = true;
        }

        return $this->similarAttributes;
    }

    private function createSimilarAttributes()
    {
        $attributes = array();

        $parameters = $this->request->getParameters();
        if (!isset($parameters['id']))
        {
            $this->log->warn('Similar attributes cannot be loaded without a product ID. '
                           . 'Use setProductID() first.');
        }
        else
        {
            $jsonData = $this->getResponseContent();
            if(parent::isValidResponse($jsonData))
            {
                foreach($jsonData['attributes'] as $attributeData)
                {
                    $attributes[$attributeData['name']] = $attributeData['value'];
                }
            }
        }

        return $attributes;
    }

    /**
     * Returns similar records for the ID previously specified. If no ID has
     * been set, there will be a warning raised and an empty result will be
     * returned.
     *
     * @return \FACTFinder\Data\Result
     */
    public function getSimilarRecords()
    {
        if (is_null($this->similarRecords)
            || !$this->upToDate
        ) {
            $this->request->resetLoaded();
            $this->similarRecords = $this->createSimilarRecords();
            $this->upToDate = true;
        }

        return $this->similarRecords;
    }

    private function createSimilarRecords()
    {
        $records = array();

        $parameters = $this->request->getParameters();
        if (!isset($parameters['id']))
        {
            $this->log->warn('Similar records cannot be loaded without a product ID. '
                           . 'Use setProductID() first.');
        }
        else
        {
            $position = 1;
            $jsonData = $this->getResponseContent();
            if(parent::isValidResponse($jsonData))
            {
                foreach($jsonData['records'] as $recordData)
                {
                    $records[] = $this->createRecord($recordData, $position++);
                }
            }
        }

        return FF::getInstance(
            'Data\Result',
            $records,
            null,
            count($records)
        );
    }

    private function createRecord($recordData, $position)
    {
        return FF::getInstance(
            'Data\Record',
            (string)$recordData['id'],
            $recordData['record'],
            100.0,
            $position
        );
    }
}


<?php
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *	   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * LoggerAppenderFile appends log events to a file.
 *
 * This appender uses a layout.
 *
 * ## Configurable parameters: ##
 *
 * - **file** - Path to the target file. Relative paths are resolved based on
 *     the working directory.
 * - **append** - If set to true, the appender will append to the file,
 *     otherwise the file contents will be overwritten.
 *
 * @version $Revision: 1382274 $
 * @package log4php
 * @subpackage appenders
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 * @link http://logging.apache.org/log4php/docs/appenders/file.html Appender documentation
 */
class LoggerAppenderFile extends LoggerAppender {

	/**
	 * If set to true, the file is locked before appending. This allows
	 * concurrent access. However, appending without locking is faster so
	 * it should be used where appropriate.
	 *
	 * TODO: make this a configurable parameter
	 *
	 * @var boolean
	 */
	protected $locking = true;

	/**
	 * If set to true, appends to file. Otherwise overwrites it.
	 * @var boolean
	 */
	protected $append = true;

	/**
	 * Path to the target file.
	 * @var string
	 */
	protected $file;

	/**
	 * The file resource.
	 * @var resource
	 */
	protected $fp;

	/**
	 * Helper function which can be easily overriden by daily file appender.
	 */
	protected function getTargetFile() {
		return $this->file;
	}

	/**
	 * Acquires the target file resource, creates the destination folder if
	 * necessary. Writes layout header to file.
	 *
	 * @return boolean FALSE if opening failed
	 */
	protected function openFile() {
		$file = $this->getTargetFile();

		// Create the target folder if needed
		if(!is_file($file)) {
			$dir = dirname($file);

			if(!is_dir($dir)) {
				$success = mkdir($dir, 0777, true);
				if ($success === false) {
					$this->warn("Failed creating target directory [$dir]. Closing appender.");
					$this->closed = true;
					return false;
				}
			}
		}

		$mode = $this->append ? 'a' : 'w';
		$this->fp = fopen($file, $mode);
		if ($this->fp === false) {
			$this->warn("Failed opening target file. Closing appender.");
			$this->fp = null;
			$this->closed = true;
			return false;
		}

		// Required when appending with concurrent access
		if($this->append) {
			fseek($this->fp, 0, SEEK_END);
		}

		// Write the header
		$this->write($this->layout->getHeader());
	}

	/**
	 * Writes a string to the target file. Opens file if not already open.
	 * @param string $string Data to write.
	 */
	protected function write($string) {
		// Lazy file open
		if(!isset($this->fp)) {
			if ($this->openFile() === false) {
				return; // Do not write if file open failed.
			}
		}

		if ($this->locking) {
			$this->writeWithLocking($string);
		} else {
			$this->writeWithoutLocking($string);
		}
	}

	protected function writeWithLocking($string) {
		if(flock($this->fp, LOCK_EX)) {
			if(fwrite($this->fp, $string) === false) {
				$this->warn("Failed writing to file. Closing appender.");
				$this->closed = true;
			}
			flock($this->fp, LOCK_UN);
		} else {
			$this->warn("Failed locking file for writing. Closing appender.");
			$this->closed = true;
		}
	}

	protected function writeWithoutLocking($string) {
		if(fwrite($this->fp, $string) === false) {
			$this->warn("Failed writing to file. Closing appender.");
			$this->closed = true;
		}
	}

	public function activateOptions() {
		if (empty($this->file)) {
			$this->warn("Required parameter 'file' not set. Closing appender.");
			$this->closed = true;
			return;
		}
	}

	public function close() {
		if (is_resource($this->fp)) {
			$this->write($this->layout->getFooter());
			fclose($this->fp);
		}
		$this->fp = null;
		$this->closed = true;
	}

	public function append(LoggerLoggingEvent $event) {
		$this->write($this->layout->format($event));
	}

	/**
	 * Sets the 'file' parameter.
	 * @param string $file
	 */
	public function setFile($file) {
		$this->setString('file', $file);
	}

	/**
	 * Returns the 'file' parameter.
	 * @return string
	 */
	public function getFile() {
		return $this->file;
	}

	/**
	 * Returns the 'append' parameter.
	 * @return boolean
	 */
	public function getAppend() {
		return $this->append;
	}

	/**
	 * Sets the 'append' parameter.
	 * @param boolean $append
	 */
	public function setAppend($append) {
		$this->setBoolean('append', $append);
	}

	/**
	 * Sets the 'file' parmeter. Left for legacy reasons.
	 * @param string $fileName
	 * @deprecated Use setFile() instead.
	 */
	public function setFileName($fileName) {
		$this->setFile($fileName);
	}

	/**
	 * Returns the 'file' parmeter. Left for legacy reasons.
	 * @return string
	 * @deprecated Use getFile() instead.
	 */
	public function getFileName() {
		return $this->getFile();
	}
}


<?php
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package log4php
 */

if (function_exists('__autoload')) {
	trigger_error("log4php: It looks like your code is using an __autoload() function. log4php uses spl_autoload_register() which will bypass your __autoload() function and may break autoloading.", E_USER_WARNING);
}

spl_autoload_register(array('LoggerAutoloader', 'autoload'));

/**
 * Class autoloader.
 *
 * @package log4php
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 * @version $Revision: 1394956 $
 */
class LoggerAutoloader {

	/** Maps classnames to files containing the class. */
	private static $classes = array(

		// Base
		'LoggerAppender' => '/LoggerAppender.php',
		'LoggerAppenderPool' => '/LoggerAppenderPool.php',
		'LoggerConfigurable' => '/LoggerConfigurable.php',
		'LoggerConfigurator' => '/LoggerConfigurator.php',
		'LoggerException' => '/LoggerException.php',
		'LoggerFilter' => '/LoggerFilter.php',
		'LoggerHierarchy' => '/LoggerHierarchy.php',
		'LoggerLevel' => '/LoggerLevel.php',
		'LoggerLocationInfo' => '/LoggerLocationInfo.php',
		'LoggerLoggingEvent' => '/LoggerLoggingEvent.php',
		'LoggerMDC' => '/LoggerMDC.php',
		'LoggerNDC' => '/LoggerNDC.php',
		'LoggerLayout' => '/LoggerLayout.php',
		'LoggerReflectionUtils' => '/LoggerReflectionUtils.php',
		'LoggerRoot' => '/LoggerRoot.php',
		'LoggerThrowableInformation' => '/LoggerThrowableInformation.php',

		// Appenders
		'LoggerAppenderConsole' => '/appenders/LoggerAppenderConsole.php',
		'LoggerAppenderDailyFile' => '/appenders/LoggerAppenderDailyFile.php',
		'LoggerAppenderEcho' => '/appenders/LoggerAppenderEcho.php',
		'LoggerAppenderFile' => '/appenders/LoggerAppenderFile.php',
		'LoggerAppenderMail' => '/appenders/LoggerAppenderMail.php',
		'LoggerAppenderMailEvent' => '/appenders/LoggerAppenderMailEvent.php',
		'LoggerAppenderMongoDB' => '/appenders/LoggerAppenderMongoDB.php',
		'LoggerAppenderNull' => '/appenders/LoggerAppenderNull.php',
		'LoggerAppenderFirePHP' => '/appenders/LoggerAppenderFirePHP.php',
		'LoggerAppenderPDO' => '/appenders/LoggerAppenderPDO.php',
		'LoggerAppenderPhp' => '/appenders/LoggerAppenderPhp.php',
		'LoggerAppenderRollingFile' => '/appenders/LoggerAppenderRollingFile.php',
		'LoggerAppenderSocket' => '/appenders/LoggerAppenderSocket.php',
		'LoggerAppenderSyslog' => '/appenders/LoggerAppenderSyslog.php',

		// Configurators
		'LoggerConfigurationAdapter' => '/configurators/LoggerConfigurationAdapter.php',
		'LoggerConfigurationAdapterINI' => '/configurators/LoggerConfigurationAdapterINI.php',
		'LoggerConfigurationAdapterPHP' => '/configurators/LoggerConfigurationAdapterPHP.php',
		'LoggerConfigurationAdapterXML' => '/configurators/LoggerConfigurationAdapterXML.php',
		'LoggerConfiguratorDefault' => '/configurators/LoggerConfiguratorDefault.php',

		// Filters
		'LoggerFilterDenyAll' => '/filters/LoggerFilterDenyAll.php',
		'LoggerFilterLevelMatch' => '/filters/LoggerFilterLevelMatch.php',
		'LoggerFilterLevelRange' => '/filters/LoggerFilterLevelRange.php',
		'LoggerFilterStringMatch' => '/filters/LoggerFilterStringMatch.php',

		// Helpers
		'LoggerFormattingInfo' => '/helpers/LoggerFormattingInfo.php',
		'LoggerOptionConverter' => '/helpers/LoggerOptionConverter.php',
		'LoggerPatternParser' => '/helpers/LoggerPatternParser.php',
		'LoggerUtils' => '/helpers/LoggerUtils.php',

		// Pattern converters
		'LoggerPatternConverter' => '/pattern/LoggerPatternConverter.php',
		'LoggerPatternConverterClass' => '/pattern/LoggerPatternConverterClass.php',
		'LoggerPatternConverterCookie' => '/pattern/LoggerPatternConverterCookie.php',
		'LoggerPatternConverterDate' => '/pattern/LoggerPatternConverterDate.php',
		'LoggerPatternConverterEnvironment' => '/pattern/LoggerPatternConverterEnvironment.php',
		'LoggerPatternConverterFile' => '/pattern/LoggerPatternConverterFile.php',
		'LoggerPatternConverterLevel' => '/pattern/LoggerPatternConverterLevel.php',
		'LoggerPatternConverterLine' => '/pattern/LoggerPatternConverterLine.php',
		'LoggerPatternConverterLiteral' => '/pattern/LoggerPatternConverterLiteral.php',
		'LoggerPatternConverterLocation' => '/pattern/LoggerPatternConverterLocation.php',
		'LoggerPatternConverterLogger' => '/pattern/LoggerPatternConverterLogger.php',
		'LoggerPatternConverterMDC' => '/pattern/LoggerPatternConverterMDC.php',
		'LoggerPatternConverterMessage' => '/pattern/LoggerPatternConverterMessage.php',
		'LoggerPatternConverterMethod' => '/pattern/LoggerPatternConverterMethod.php',
		'LoggerPatternConverterNDC' => '/pattern/LoggerPatternConverterNDC.php',
		'LoggerPatternConverterNewLine' => '/pattern/LoggerPatternConverterNewLine.php',
		'LoggerPatternConverterProcess' => '/pattern/LoggerPatternConverterProcess.php',
		'LoggerPatternConverterRelative' => '/pattern/LoggerPatternConverterRelative.php',
		'LoggerPatternConverterRequest' => '/pattern/LoggerPatternConverterRequest.php',
		'LoggerPatternConverterServer' => '/pattern/LoggerPatternConverterServer.php',
		'LoggerPatternConverterSession' => '/pattern/LoggerPatternConverterSession.php',
		'LoggerPatternConverterSessionID' => '/pattern/LoggerPatternConverterSessionID.php',
		'LoggerPatternConverterSuperglobal' => '/pattern/LoggerPatternConverterSuperglobal.php',
		'LoggerPatternConverterThrowable' => '/pattern/LoggerPatternConverterThrowable.php',

		// Layouts
		'LoggerLayoutHtml' => '/layouts/LoggerLayoutHtml.php',
		'LoggerLayoutPattern' => '/layouts/LoggerLayoutPattern.php',
		'LoggerLayoutSerialized' => '/layouts/LoggerLayoutSerialized.php',
		'LoggerLayoutSimple' => '/layouts/LoggerLayoutSimple.php',
		'LoggerLayoutTTCC' => '/layouts/LoggerLayoutTTCC.php',
		'LoggerLayoutXml' => '/layouts/LoggerLayoutXml.php',

		// Renderers
		'LoggerRendererDefault' => '/renderers/LoggerRendererDefault.php',
		'LoggerRendererException' => '/renderers/LoggerRendererException.php',
		'LoggerRendererMap' => '/renderers/LoggerRendererMap.php',
		'LoggerRenderer' => '/renderers/LoggerRenderer.php',
	);

	/**
	 * Loads a class.
	 * @param string $className The name of the class to load.
	 */
	public static function autoload($className) {
		if(isset(self::$classes[$className])) {
			include dirname(__FILE__) . self::$classes[$className];
		}
	}
}


<?php
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *	   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * LoggerAppenderConsole appends log events either to the standard output
 * stream (php://stdout) or the standard error stream (php://stderr).
 *
 * **Note**: Use this Appender with command-line php scripts. On web scripts
 * this appender has no effects.
 *
 * This appender uses a layout.
 *
 * ## Configurable parameters: ##
 *
 * - **target** - the target stream: "stdout" or "stderr"
 *
 * @version $Revision: 1343601 $
 * @package log4php
 * @subpackage appenders
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 * @link http://logging.apache.org/log4php/docs/appenders/console.html Appender documentation
 */
 class LoggerAppenderConsole extends LoggerAppender {

	/** The standard otuput stream.  */
	const STDOUT = 'php://stdout';

	/** The standard error stream.*/
	const STDERR = 'php://stderr';

	/** The 'target' parameter. */
	protected $target = self::STDOUT;

	/**
	 * Stream resource for the target stream.
	 * @var resource
	 */
	protected $fp = null;

	public function activateOptions() {
		$this->fp = fopen($this->target, 'w');
		if(is_resource($this->fp) && $this->layout !== null) {
			fwrite($this->fp, $this->layout->getHeader());
		}
		$this->closed = (bool)is_resource($this->fp) === false;
	}

	public function close() {
		if($this->closed != true) {
			if (is_resource($this->fp) && $this->layout !== null) {
				fwrite($this->fp, $this->layout->getFooter());
				fclose($this->fp);
			}
			$this->closed = true;
		}
	}

	public function append(LoggerLoggingEvent $event) {
		if (is_resource($this->fp) && $this->layout !== null) {
			fwrite($this->fp, $this->layout->format($event));
		}
	}

	/**
	 * Sets the 'target' parameter.
	 * @param string $target
	 */
	public function setTarget($target) {
		$value = trim($target);
		if ($value == self::STDOUT || strtoupper($value) == 'STDOUT') {
			$this->target = self::STDOUT;
		} elseif ($value == self::STDERR || strtoupper($value) == 'STDERR') {
			$this->target = self::STDERR;
		} else {
			$target = var_export($target);
			$this->warn("Invalid value given for 'target' property: [$target]. Property not set.");
		}
	}

	/**
	 * Returns the value of the 'target' parameter.
	 * @return string
	 */
	public function getTarget() {
		return $this->target;
	}
}


<?php
namespace FACTFinder\Adapter;

use FACTFinder\Loader as FF;

class Import extends AbstractAdapter
{
    /**
     * @var FACTFinder\Util\LoggerInterface
     */
    private $log;

    public function __construct(
        $loggerClass,
        \FACTFinder\Core\ConfigurationInterface $configuration,
        \FACTFinder\Core\Server\Request $request,
        \FACTFinder\Core\Client\UrlBuilder $urlBuilder,
        \FACTFinder\Core\AbstractEncodingConverter $encodingConverter = null
    ) {
        parent::__construct($loggerClass, $configuration, $request,
                            $urlBuilder, $encodingConverter);

        $this->log = $loggerClass::getLogger(__CLASS__);

        // Don't set request action yet, because it depends on the kind of
        // import to be done.
        $this->parameters['format'] = 'xml';

        $this->request->setConnectTimeout($configuration->getImportConnectTimeout());
        $this->request->setTimeout($configuration->getImportTimeout());

        $this->useXmlResponseContentProcessor();
    }

    /**
     * Trigger a search data import.
     *
     * @param bool $download If true, update import files prior to import.
     */
    public function triggerDataImport($download = false)
    {
        //this function changes parameters, action, ... so reload of response is neccessary
        $this->request->resetLoaded();

        $this->request->setAction('Import.ff');

        $this->parameters['download'] = $download ? 'true' : 'false';

        // TODO: Parse the response XML into some nice domain object.
        return $this->getResponseContent();
    }

    /**
     * Trigger a suggest data import.
     *
     * @param bool $download If true, update import files prior to import.
     */
    public function triggerSuggestImport($download = false)
    {
        //this function changes parameters, action, ... so reload of response is neccessary
        $this->request->resetLoaded();

        $this->request->setAction('Import.ff');

        $this->parameters['download'] = $download ? 'true' : 'false';
        $this->parameters['type'] = 'suggest';

        $report = $this->getResponseContent();

        // Clean up for next import
        unset($this->parameters['type']);

        // TODO: Parse the response XML into some nice domain object.
        return $report;
    }

    /**
     * Trigger a recommendation data import.
     *
     * @param bool $download If true, update import files prior to import.
     */
    public function triggerRecommendationImport($download = false)
    {
        //this function changes parameters, action, ... so reload of response is neccessary
        $this->request->resetLoaded();

        $this->request->setAction('Recommender.ff');

        $this->parameters['download'] = $download ? 'true' : 'false';
        $this->parameters['do'] = 'importData';

        $report = $this->getResponseContent();

        // Clean up for next import
        unset($this->parameters['do']);

        // TODO: Parse the response XML into some nice domain object.
        return $report;
    }
}