<?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 internal representation of logging event.
 *
 * @version $Revision: 1382273 $
 * @package log4php
 */
class LoggerLoggingEvent {

	private static $startTime;

	/**
	* @var string Fully Qualified Class Name of the calling category class.
	*/
	private $fqcn;

	/**
	* @var Logger reference
	*/
	private $logger;

	/**
	 * The category (logger) name.
	 * This field will be marked as private in future
	 * releases. Please do not access it directly.
	 * Use the {@link getLoggerName()} method instead.
	 * @deprecated
	 */
	private $categoryName;

	/**
	 * Level of the logging event.
	 * @var LoggerLevel
	 */
	protected $level;

	/**
	 * The nested diagnostic context (NDC) of logging event.
	 * @var string
	 */
	private $ndc;

	/**
	 * Have we tried to do an NDC lookup? If we did, there is no need
	 * to do it again.	Note that its value is always false when
	 * serialized. Thus, a receiving SocketNode will never use it's own
	 * (incorrect) NDC. See also writeObject method.
	 * @var boolean
	 */
	private $ndcLookupRequired = true;

	/**
	 * @var mixed The application supplied message of logging event.
	 */
	private $message;

	/**
	 * The application supplied message rendered through the log4php
	 * objet rendering mechanism. At present renderedMessage == message.
	 * @var string
	 */
	private $renderedMessage;

	/**
	 * The name of thread in which this logging event was generated.
	 * log4php saves here the process id via {@link PHP_MANUAL#getmypid getmypid()}
	 * @var mixed
	 */
	private $threadName;

	/**
	* The number of seconds elapsed from 1/1/1970 until logging event
	* was created plus microseconds if available.
	* @var float
	*/
	public $timeStamp;

	/**
	* @var LoggerLocationInfo Location information for the caller.
	*/
	private $locationInfo;

	/**
	 * @var LoggerThrowableInformation log4php internal representation of throwable
	 */
	private $throwableInfo;

	/**
	* Instantiate a LoggingEvent from the supplied parameters.
	*
	* Except {@link $timeStamp} all the other fields of
	* LoggerLoggingEvent are filled when actually needed.
	*
	* @param string $fqcn name of the caller class.
	* @param mixed $logger The {@link Logger} category of this event or the logger name.
	* @param LoggerLevel $level The level of this event.
	* @param mixed $message The message of this event.
	* @param integer $timeStamp the timestamp of this logging event.
	* @param Exception $throwable The throwable associated with logging event
	*/
	public function __construct($fqcn, $logger, LoggerLevel $level, $message, $timeStamp = null, $throwable = null) {
		$this->fqcn = $fqcn;
		if($logger instanceof Logger) {
			$this->logger = $logger;
			$this->categoryName = $logger->getName();
		} else {
			$this->categoryName = strval($logger);
		}
		$this->level = $level;
		$this->message = $message;
		if($timeStamp !== null && is_numeric($timeStamp)) {
			$this->timeStamp = $timeStamp;
		} else {
			$this->timeStamp = microtime(true);
		}

		if ($throwable !== null && $throwable instanceof Exception) {
			$this->throwableInfo = new LoggerThrowableInformation($throwable);
		}
	}

	/**
	 * Returns the full qualified classname.
	 * TODO: PHP does contain namespaces in 5.3. Those should be returned too,
	 */
	 public function getFullQualifiedClassname() {
		 return $this->fqcn;
	 }

	/**
	 * Set the location information for this logging event. The collected
	 * information is cached for future use.
	 *
	 *
This method uses {@link PHP_MANUAL#debug_backtrace debug_backtrace()} function (if exists)
	 * to collect informations about caller.

	 *
It only recognize informations generated by {@link Logger} and its subclasses.

	 * @return LoggerLocationInfo
	 */
	public function getLocationInformation() {
		if($this->locationInfo === null) {

			$locationInfo = array();
			$trace = debug_backtrace();
			$prevHop = null;
			// make a downsearch to identify the caller
			$hop = array_pop($trace);
			while($hop !== null) {
				if(isset($hop['class'])) {
					// we are sometimes in functions = no class available: avoid php warning here
					$className = strtolower($hop['class']);
					if(!empty($className) and ($className == 'logger' or
						strtolower(get_parent_class($className)) == 'logger')) {
						$locationInfo['line'] = $hop['line'];
						$locationInfo['file'] = $hop['file'];
						break;
					}
				}
				$prevHop = $hop;
				$hop = array_pop($trace);
			}
			$locationInfo['class'] = isset($prevHop['class']) ? $prevHop['class'] : 'main';
			if(isset($prevHop['function']) and
				$prevHop['function'] !== 'include' and
				$prevHop['function'] !== 'include_once' and
				$prevHop['function'] !== 'require' and
				$prevHop['function'] !== 'require_once') {

				$locationInfo['function'] = $prevHop['function'];
			} else {
				$locationInfo['function'] = 'main';
			}

			$this->locationInfo = new LoggerLocationInfo($locationInfo, $this->fqcn);
		}
		return $this->locationInfo;
	}

	/**
	 * Return the level of this event. Use this form instead of directly
	 * accessing the {@link $level} field.
	 * @return LoggerLevel
	 */
	public function getLevel() {
		return $this->level;
	}

	/**
	 * Returns the logger which created the event.
	 * @return Logger
	 */
	public function getLogger() {
		return $this->logger;
	}

	/**
	 * Return the name of the logger. Use this form instead of directly
	 * accessing the {@link $categoryName} field.
	 * @return string
	 */
	public function getLoggerName() {
		return $this->categoryName;
	}

	/**
	 * Return the message for this logging event.
	 * @return mixed
	 */
	public function getMessage() {
		return $this->message;
	}

	/**
	 * This method returns the NDC for this event. It will return the
	 * correct content even if the event was generated in a different
	 * thread or even on a different machine. The {@link LoggerNDC::get()} method
	 * should never be called directly.
	 * @return string
	 */
	public function getNDC() {
		if($this->ndcLookupRequired) {
			$this->ndcLookupRequired = false;
			$this->ndc = LoggerNDC::get();
		}
		return $this->ndc;
	}

	/**
	 * Returns the the context corresponding to the key
	 * parameter.
	 * @return string
	 */
	public function getMDC($key) {
		return LoggerMDC::get($key);
	}

	/**
	 * Returns the entire MDC context.
	 * @return array
	 */
	public function getMDCMap () {
		return LoggerMDC::getMap();
	}

	/**
	 * Render message.
	 * @return string
	 */
	public function getRenderedMessage() {
		if($this->renderedMessage === null and $this->message !== null) {
			if(is_string($this->message)) {
				$this->renderedMessage = $this->message;
			} else {
				$rendererMap = Logger::getHierarchy()->getRendererMap();
				$this->renderedMessage= $rendererMap->findAndRender($this->message);
			}
		}
		return $this->renderedMessage;
	}

	/**
	 * Returns the time when the application started, as a UNIX timestamp
	 * with microseconds.
	 * @return float
	 */
	public static function getStartTime() {
		if(!isset(self::$startTime)) {
			self::$startTime = microtime(true);
		}
		return self::$startTime;
	}

	/**
	 * @return float
	 */
	public function getTimeStamp() {
		return $this->timeStamp;
	}

	/**
	 * Returns the time in seconds passed from the beginning of execution to
	 * the time the event was constructed.
	 *
	 * @return float Seconds with microseconds in decimals.
	 */
	public function getRelativeTime() {
		return $this->timeStamp - self::$startTime;
	}

	/**
	 * Returns the time in milliseconds passed from the beginning of execution
	 * to the time the event was constructed.
	 *
	 * @deprecated This method has been replaced by getRelativeTime which
	 * 		does not perform unneccesary multiplication and formatting.
	 *
	 * @return integer
	 */
	public function getTime() {
		$eventTime = $this->getTimeStamp();
		$eventStartTime = LoggerLoggingEvent::getStartTime();
		return number_format(($eventTime - $eventStartTime) * 1000, 0, '', '');
	}

	/**
	 * @return mixed
	 */
	public function getThreadName() {
		if ($this->threadName === null) {
			$this->threadName = (string)getmypid();
		}
		return $this->threadName;
	}

	/**
	 * @return mixed LoggerThrowableInformation
	 */
	public function getThrowableInformation() {
		return $this->throwableInfo;
	}

	/**
	 * Serialize this object
	 * @return string
	 */
	public function toString() {
		serialize($this);
	}

	/**
	 * Avoid serialization of the {@link $logger} object
	 */
	public function __sleep() {
		return array(
			'fqcn',
			'categoryName',
			'level',
			'ndc',
			'ndcLookupRequired',
			'message',
			'renderedMessage',
			'threadName',
			'timeStamp',
			'locationInfo',
		);
	}

}

LoggerLoggingEvent::getStartTime();


<?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
 */

/**
 * Converts XML configuration files to a PHP array.
 *
 * @package log4php
 * @subpackage configurators
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 * @version $Revision: 1394956 $
 * @since 2.2
 */
class LoggerConfigurationAdapterXML implements LoggerConfigurationAdapter
{
	/** Path to the XML schema used for validation. */
	const SCHEMA_PATH = '/../xml/log4php.xsd';

	private $config = array(
		'appenders' => array(),
		'loggers' => array(),
		'renderers' => array(),
	);

	public function convert($url) {
		$xml = $this->loadXML($url);

		$this->parseConfiguration($xml);

		// Parse the <root> node
		if (isset($xml->root)) {
			$this->parseRootLogger($xml->root);
		}

		// Process <logger> nodes
		foreach($xml->logger as $logger) {
			$this->parseLogger($logger);
		}

		// Process <appender> nodes
		foreach($xml->appender as $appender) {
			$this->parseAppender($appender);
		}

		// Process <renderer> nodes
		foreach($xml->renderer as $rendererNode) {
			$this->parseRenderer($rendererNode);
		}

		// Process <defaultRenderer> node
		foreach($xml->defaultRenderer as $rendererNode) {
			$this->parseDefaultRenderer($rendererNode);
		}

		return $this->config;
	}

	/**
	 * Loads and validates the XML.
	 * @param string $url Input XML.
	 */
	private function loadXML($url) {
		if (!file_exists($url)) {
			throw new LoggerException("File [$url] does not exist.");
		}

		libxml_clear_errors();
		$oldValue = libxml_use_internal_errors(true);

		// Load XML
		$xml = @simplexml_load_file($url);
		if ($xml === false) {

			$errorStr = "";
			foreach(libxml_get_errors() as $error) {
				$errorStr .= $error->message;
			}

			throw new LoggerException("Error loading configuration file: " . trim($errorStr));
		}

		libxml_clear_errors();
		libxml_use_internal_errors($oldValue);

		return $xml;
	}

	/**
	 * Parses the <configuration> node.
	 */
	private function parseConfiguration(SimpleXMLElement $xml) {
		$attributes = $xml->attributes();
		if (isset($attributes['threshold'])) {
			$this->config['threshold'] = (string) $attributes['threshold'];
		}
	}

	/** Parses an <appender> node. */
	private function parseAppender(SimpleXMLElement $node) {
		$name = $this->getAttributeValue($node, 'name');
		if (empty($name)) {
			$this->warn("An <appender> node is missing the required 'name' attribute. Skipping appender definition.");
			return;
		}

		$appender = array();
		$appender['class'] = $this->getAttributeValue($node, 'class');

		if (isset($node['threshold'])) {
			$appender['threshold'] = $this->getAttributeValue($node, 'threshold');
		}

		if (isset($node->layout)) {
			$appender['layout']= $this->parseLayout($node->layout, $name);
		}

		if (count($node->param) > 0) {
			$appender['params'] = $this->parseParameters($node);
		}

		foreach($node->filter as $filterNode) {
			$appender['filters'][] = $this->parseFilter($filterNode);
		}

		$this->config['appenders'][$name] = $appender;
	}

	/** Parses a <layout> node. */
	private function parseLayout(SimpleXMLElement $node, $appenderName) {
		$layout = array();
		$layout['class'] = $this->getAttributeValue($node, 'class');

		if (count($node->param) > 0) {
			$layout['params'] = $this->parseParameters($node);
		}

		return $layout;
	}

	/** Parses any <param> child nodes returning them in an array. */
	private function parseParameters($paramsNode) {
		$params = array();

		foreach($paramsNode->param as $paramNode) {
			if (empty($paramNode['name'])) {
				$this->warn("A <param> node is missing the required 'name' attribute. Skipping parameter.");
				continue;
			}

			$name = $this->getAttributeValue($paramNode, 'name');
			$value = $this->getAttributeValue($paramNode, 'value');

			$params[$name] = $value;
		}

		return $params;
	}

	/** Parses a <root> node. */
	private function parseRootLogger(SimpleXMLElement $node) {
		$logger = array();

		if (isset($node->level)) {
			$logger['level'] = $this->getAttributeValue($node->level, 'value');
		}

		$logger['appenders'] = $this->parseAppenderReferences($node);

		$this->config['rootLogger'] = $logger;
	}

	/** Parses a <logger> node. */
	private function parseLogger(SimpleXMLElement $node) {
		$logger = array();

		$name = $this->getAttributeValue($node, 'name');
		if (empty($name)) {
			$this->warn("A <logger> node is missing the required 'name' attribute. Skipping logger definition.");
			return;
		}

		if (isset($node->level)) {
			$logger['level'] = $this->getAttributeValue($node->level, 'value');
		}

		if (isset($node['additivity'])) {
			$logger['additivity'] = $this->getAttributeValue($node, 'additivity');
		}

		$logger['appenders'] = $this->parseAppenderReferences($node);

		// Check for duplicate loggers
		if (isset($this->config['loggers'][$name])) {
			$this->warn("Duplicate logger definition [$name]. Overwriting.");
		}

		$this->config['loggers'][$name] = $logger;
	}

	/**
	 * Parses a <logger> node for appender references and returns them in an array.
	 *
	 * Previous versions supported appender-ref, as well as appender_ref so both
	 * are parsed for backward compatibility.
	 */
	private function parseAppenderReferences(SimpleXMLElement $node) {
		$refs = array();
		foreach($node->appender_ref as $ref) {
			$refs[] = $this->getAttributeValue($ref, 'ref');
		}

		foreach($node->{'appender-ref'} as $ref) {
			$refs[] = $this->getAttributeValue($ref, 'ref');
		}

		return $refs;
	}

	/** Parses a <filter> node. */
	private function parseFilter($filterNode) {
		$filter = array();
		$filter['class'] = $this->getAttributeValue($filterNode, 'class');

		if (count($filterNode->param) > 0) {
			$filter['params'] = $this->parseParameters($filterNode);
		}

		return $filter;
	}

	/** Parses a <renderer> node. */
	private function parseRenderer(SimpleXMLElement $node) {
		$renderedClass = $this->getAttributeValue($node, 'renderedClass');
		$renderingClass = $this->getAttributeValue($node, 'renderingClass');

		$this->config['renderers'][] = compact('renderedClass', 'renderingClass');
	}

	/** Parses a <defaultRenderer> node. */
	private function parseDefaultRenderer(SimpleXMLElement $node) {
		$renderingClass = $this->getAttributeValue($node, 'renderingClass');

		// Warn on duplicates
		if(isset($this->config['defaultRenderer'])) {
			$this->warn("Duplicate <defaultRenderer> node. Overwriting.");
		}

		$this->config['defaultRenderer'] = $renderingClass;
	}

	// ******************************************
	// ** Helper methods                       **
	// ******************************************

	private function getAttributeValue(SimpleXMLElement $node, $name) {
		return isset($node[$name]) ? (string) $node[$name] : null;
	}

	private function warn($message) {
		trigger_error("log4php: " . $message, E_USER_WARNING);
	}
}


<?php
namespace FACTFinder\Core\Server;

use FACTFinder\Loader as FF;

/**
 * This implementation retrieves the FACT-Finder data by using the cURL's "multi
 * interface".
 * Responses are queried in parallel and lazily and are cached as long as
 * parameters don't change.
 */
class MultiCurlDataProvider extends AbstractDataProvider
{
    /**
     * @var \FACTFinder\Util\LoggerInterface
     */
    private $log;

    /**
     * @var UrlBuilder
     */
    protected $urlBuilder;

    /**
     * @var \FACTFinder\Util\CurlInterface
     */
    protected $curl;

    protected $defaultCurlOptions;
    protected $necessaryCurlOptions;

    /**
     * @var bool
     * This is used to issue a warning if not all connections are being fetched
     * at the same time.
     */
    protected $usedBefore;

    public function __construct(
        $loggerClass,
        \FACTFinder\Core\ConfigurationInterface $configuration,
        \FACTFinder\Util\CurlInterface $curl,
        UrlBuilder $urlBuilder
    ) {
        parent::__construct($loggerClass, $configuration);

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

        $this->urlBuilder = $urlBuilder;

        $this->curl = $curl;

        $this->defaultCurlOptions = array(
            CURLOPT_CONNECTTIMEOUT => $this->configuration->getDefaultConnectTimeout(),
            CURLOPT_TIMEOUT        => $this->configuration->getDefaultTimeout(),
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_ENCODING       => '',
        );

        $this->necessaryCurlOptions = array(
            CURLOPT_RETURNTRANSFER => true,
        );
    }

    public function setConnectTimeout($id, $timeout)
    {
        if (!isset($this->connectionData[$id]))
            throw new \InvalidArgumentException('Tried to set timeout for invalid ID $id.');

        $this->connectionData[$id]->setConnectionOption(
            CURLOPT_CONNECTTIMEOUT,
            $timeout
        );
    }

    public function setTimeout($id, $timeout)
    {
        if (!isset($this->connectionData[$id]))
            throw new \InvalidArgumentException('Tried to set timeout for invalid ID $id.');

        $this->connectionData[$id]->setConnectionOption(
            CURLOPT_TIMEOUT,
            $timeout
        );
    }

    // TODO: Could this be refactored some more?
    public function loadResponse($id)
    {
        if (!isset($this->connectionData[$id]))
            throw new \InvalidArgumentException('Tried to get response for invalid ID $id.');

        // Although this implementation of the data provider generally loads all
        // all available responses at once (basically ignoring the $id
        // parameter), it will not do so if the requested $id does not actually
        // need a connection itself. This allows for other connections to be
        // deferred as long as possible.
        if (!$this->hasUrlChanged($id))
            return;

        $connectionData = $this->connectionData[$id];

        $action = $connectionData->getAction();
        if (empty($action))
        {
            $this->log->error('Request type missing.');
            $connectionData->setNullResponse();
            return;
        }

        // From now on we ignore what parameter has been passed in and just load
        // all available responses.
        $connectionsToFetch = array();
        foreach ($this->connectionData as $id => $connectionData)
        {
            $action = $connectionData->getAction();
            if (!$this->hasUrlChanged($id)
                || empty($action)
            ) {
                continue;
            }

            $httpHeaderFields = $this->prepareHttpHeaders($connectionData);
            $parameters = $this->prepareParameters($connectionData);
            $url = $this->prepareConnectionOptions(
                $connectionData,
                $httpHeaderFields,
                $parameters
            );

            $connectionsToFetch[$id] = array(
                'connection' => $connectionData,
                'url' => $url
            );
        }

        if (count($connectionsToFetch))
        {
            if ($this->usedBefore)
                $this->log->warn('loadResponse() has been called before. You should try to configure all requests '
                               . 'before loading the first response so that all connections can be made in parallel.');

            $this->retrieveResponses($connectionsToFetch);

            $this->usedBefore = true;
        }
    }

    private function prepareHttpHeaders($connectionData)
    {
        $httpHeaderFields = clone $connectionData->getHttpHeaderFields();

        $language = $this->configuration->getLanguage();
        if (!empty($language))
            $httpHeaderFields['Accept-Language'] = $language;

        return $httpHeaderFields;
    }

    private function prepareParameters($connectionData)
    {
        $parameters = clone $connectionData->getParameters();

        if ($this->configuration->isDebugEnabled())
            $parameters['verbose'] = 'true';

        return $parameters;
    }

    private function prepareConnectionOptions(
        $connectionData,
        $httpHeaderFields,
        $parameters
    ) {
        if ($this->configuration->isDebugEnabled()
            && isset($_SERVER['HTTP_REFERER'])
            && !$connectionData->issetConnectionOption(CURLOPT_REFERER)
        ) {
            $connectionData->setConnectionOption(
                CURLOPT_REFERER,
                $_SERVER['HTTP_REFERER']
            );
        }

        $connectionData->setConnectionOption(
            CURLOPT_HTTPHEADER,
            $httpHeaderFields->toHttpHeaderFields()
        );

        $url = $this->urlBuilder->getAuthenticationUrl(
            $connectionData->getAction(),
            $parameters
        );

        $connectionData->setConnectionOption(CURLOPT_URL, $url);

        // Return non-authentication URL for reproducability.
        return $this->urlBuilder->getNonAuthenticationUrl(
            $connectionData->getAction(),
            $parameters
        );
    }

    private function retrieveResponses($connectionsToFetch)
    {
        $curl = $this->curl;

        $multiHandle = $curl->multi_init();
        if ($multiHandle === false)
        {
            $this->log->error("curl_multi_init() did not return a multi handle. "
                            . 'Setting a empty responses...');
            foreach ($connectionsToFetch as $id => $data)
            {
                $data['connection']->setResponse(
                    FF::getInstance('Core\Server\NullResponse'),
                    $data['url']
                );
            }
        }

        // Use a reference so that we can add a 'handle' field in the loop.
        foreach ($connectionsToFetch as $id => &$data)
        {
            $data['handle'] = $curl->init();

            if ($data['handle'] === false)
            {
                $this->log->error("curl_init() did not return a handle for ID $id. "
                                . 'Setting an empty response...');
                $data['connection']->setResponse(
                    FF::getInstance('Core\Server\NullResponse'),
                    $data['url']
                );
            }

            // We cannot use array_merge() here, because that does not preserve
            // numeric keys. So we use array union instead. However, as opposed
            // to array_merge() the left-hand operator's keys will be preserved.
            $curlOptions = $this->necessaryCurlOptions
                         + $data['connection']->getConnectionOptions()
                         + $this->defaultCurlOptions;

            $curl->setopt_array(
                $data['handle'],
                $curlOptions
            );

            $curl->multi_add_handle($multiHandle, $data['handle']);
        }
        unset($data); // Otherwise the reference remains and reusing the
                      // variable name $data further down this function will
                      // change the last element of the array.

        do
        {
            $status = $curl->multi_exec($multiHandle, $still_running);
        } while ($status == CURLM_CALL_MULTI_PERFORM);

        while ($still_running && $status == CURLM_OK)
        {
            // curl_multi_select sometimes returns -1 indefinitely in which case
            // the usual curl multi loop would run endlessly. This is due to the
            // underlying cURL library in C, which returns -1 upon errors which
            // cannot be checked from the outside. Simply waiting for 100 ms is
            // the suggested workaround. For further information see:
            // https://bugs.php.net/bug.php?id=61141
            // https://bugs.php.net/bug.php?id=63411
            // https://bugs.php.net/bug.php?id=63842
            // http://curl.haxx.se/libcurl/c/curl_multi_fdset.html
            if ($curl->multi_select($multiHandle) == -1)
                usleep(100);

            do
            {
                $status = $curl->multi_exec($multiHandle, $still_running);
            } while ($status == CURLM_CALL_MULTI_PERFORM);

            // TODO: Fetch responses within this loop as they are ready (while
            // the others are still loading).
        }

        if ($status != CURLM_OK)
            $this->log->error('There was a cURL error: ' . $status);

        while (($msg = $curl->multi_info_read($multiHandle)) !== false)
        {
            // We do not check the value of $msg['msg'], because currently this
            // will always be CURLMSG_DONE.
            $curlErrorNumber = $msg['result'];

            // Set $data to the data array corresponding to the current handle.
            foreach ($connectionsToFetch as $data)
                if ($data['handle'] === $msg['handle'])
                    break;

            // We could skip multi_getcontent if $curlErrorNumber is different
            // from CURLE_OK, as it will always return null.
            $responseText = $curl->multi_getcontent($data['handle']);
            $httpCode = (int)$curl->getinfo($data['handle'], CURLINFO_HTTP_CODE);
            $curlError = $curl->error($data['handle']);

            $curl->multi_remove_handle($multiHandle, $data['handle']);
            $this->curl->close($data['handle']);

            $response = FF::getInstance('Core\Server\Response',
                $responseText,
                $httpCode,
                $curlErrorNumber,
                $curlError
            );

            $data['connection']->setResponse($response, $data['url']);
            $this->logResult($response);
        }
    }

    private function logResult($response)
    {
        $httpCode = $response->getHttpCode();
        $curlError = $response->getConnectionError();
        if ($httpCode >= 400) {
            $this->log->error("Connection failed. HTTP code: $httpCode");
        } else if ($httpCode == 0) {
            $this->log->error("Connection refused. cURL error: $curlError");
        } else if (floor($httpCode / 100) == 2) { // all successful status codes (2**)
            $this->log->info("Request successful!");
        }
    }

    private function hasUrlChanged($id)
    {
        $connectionData = $this->connectionData[$id];

        if (FF::isInstanceOf($connectionData->getResponse(), 'Core\Server\NullResponse'))
            return true;

        $url = $this->urlBuilder->getNonAuthenticationUrl(
            $connectionData->getAction(),
            $this->prepareParameters($connectionData)
        );

        return $url != $connectionData->getPreviousUrl();
    }
}


<?php
namespace FACTFinder\Core\Server;

use \FACTFinder\Loader as FF;

/**
 * This implementation backs the Request with an EasyCurlDataProvider.
 */
class EasyCurlRequestFactory implements RequestFactoryInterface
{
    /**
     * @var \FACTFinder\Util\LoggerInterface
     */
    private $log;
    private $loggerClass;

    /**
     * @var \FACTFinder\Core\ConfigurationInterface
     */
    protected $configuration;

    /**
     * @var EasyCurlDataProvider
     */
    private $dataProvider;

    /**
     * @var \FACTFinder\Util\Parameters
     */
    private $requestParameters;

    /**
     * @param string $loggerClass
     * @param \FACTFinder\Core\ConfigurationInterface $configuration
     * @param \FACTFinder\Util\Parameters $requestParameters
     * @param \FACTFinder\Util\CurlInterface $curl Optional. If omitted, an
     *        instance of \FACTFinder\Util\Curl will be used.
     */
    public function __construct(
        $loggerClass,
        \FACTFinder\Core\ConfigurationInterface $configuration,
        \FACTFinder\Util\Parameters $requestParameters,
        \FACTFinder\Util\CurlInterface $curl = null
    ) {
        $this->loggerClass = $loggerClass;
        $this->log = $loggerClass::getLogger(__CLASS__);
        $this->configuration = $configuration;

        $urlBuilder = FF::getInstance('Core\Server\UrlBuilder',
            $loggerClass,
            $configuration
        );
        $this->dataProvider = FF::getInstance('Core\Server\EasyCurlDataProvider',
            $loggerClass,
            $configuration,
            is_null($curl) ? FF::getInstance('Util\Curl') : $curl,
            $urlBuilder
        );

        $this->requestParameters = $requestParameters;
    }

    /**
     * Returns a request object all wired up and ready for use.
     * @return Request
     */
    public function getRequest()
    {
        $connectionData = FF::getInstance(
            'Core\Server\ConnectionData',
            clone $this->requestParameters
        );
        return FF::getInstance('Core\Server\Request',
            $this->loggerClass,
            $connectionData,
            $this->dataProvider
        );
    }
}


<?php
namespace FACTFinder\Adapter;

use FACTFinder\Loader as FF;

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

    /**
     * @var FACTFinder\Data\TagQuery[]
     */
    private $tagCloud;

    /**
     * @var string
     */
    private $lastRequestQuery = null;

    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('TagCloud.ff');
        $this->parameters['do'] = 'getTagCloud';
        $this->parameters['format'] = 'json';

        $this->useJsonResponseContentProcessor();
    }

    /**
     * Get the tag cloud from FACT-Finder as an array of TagQuery's.
     *
     * @param
     *
     * @return FACTFinder\Data\TagQuery[]
     */
    public function getTagCloud($requestQuery = null)
    {
        if (is_null($this->tagCloud)
            || $requestQuery != $this->lastRequestQuery
        ) {
            $this->tagCloud = $this->createTagCloud($requestQuery);
            $this->lastRequestQuery = $requestQuery;
        }

        return $this->tagCloud;
    }

    /**
     * Set the maximum amount of tag queries to be fetched.
     *
     * @param int $wordCount The number of tag queries to be fetched. Something
     *        else than a positive integer is passed, the word count will be
     *        unlimited (or determined by FACT-Finder).
     */
    public function setWordCount($wordCount)
    {
        $parameters = $this->request->getParameters();
        if (is_numeric($wordCount)
            && (int)$wordCount == (float)$wordCount // Is integer?
            && $wordCount > 0
        ) {
            $parameters['wordCount'] = $wordCount;
        }
        else
        {
            unset($parameters['wordCount']);
        }
        // Make sure that the tag cloud is fetched again. In theory, we only
        // have to do this when wordCount increases.
        $this->tagCloud = null;
    }

    private function createTagCloud($requestQuery = null)
    {
        $tagCloud = array();

        $tagCloudData = $this->getResponseContent();
        if (parent::isValidResponse($tagCloudData))
        {
            foreach ($tagCloudData as $tagQueryData)
            {
                $query = $tagQueryData['query'];

                // TODO: Once JIRA issue FF-5328 is fixed, retrieve the
                //       parameters from searchParams, like all other adapters
                //       do.
                $parameters = FF::getInstance('Util\Parameters');
                $parameters['query'] = $query;

                $tagCloud[] = FF::getInstance(
                    'Data\TagQuery',
                    $query,
                    $this->urlBuilder->generateUrl($parameters),
                    $requestQuery == $query,
                    $tagQueryData['weight'],
                    $tagQueryData['searchCount']
                );
            }
        }

        return $tagCloud;
    }
}


<?php
namespace FACTFinder\Data;

class Campaign
{
    /**
     * @var string
     */
    private $name;

    /**
     * @var string
     */
    private $category;

    /**
     * @var string
     */
    private $redirectUrl;

    /**
     * @var Record[]
     */
    private $pushedProducts = array();

    /**
     * @var string[]
     */
    private $feedback = array();

    /**
     * @var AdvisorQuestion[]
     */
    private $activeQuestions = array();

    /**
     * @var AdvisorQuestion[]
     */
    private $advisorTree = array();

    /**
     * @param string $name
     * @param string $category
     * @param string $redirectUrl
     * @param Record[] $pushedProducts
     * @param array feedback; array of strings with labels as keys
     * @param array activeQuestions; array of FACTFinder_AdvisorQuestion objects
     */
    public function __construct(
        $name,
        $category = '',
        $redirectUrl = '',
        array $pushedProducts = array(),
        array $feedback = array(),
        array $activeQuestions = array(),
        array $advisorTree = array()
    ) {
        $this->name = (string)$name;
        $this->category = (string)$category;
        $this->redirectUrl = (string)$redirectUrl;
        $this->addPushedProducts($pushedProducts);
        $this->addFeedback($feedback);
        $this->addActiveQuestions($activeQuestions);
        $this->addToAdvisorTree($advisorTree);
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return string
     */
    public function getCategory()
    {
        return $this->category;
    }

    /**
     * @return bool True, if a redirect link was set.
     */
    public function hasRedirect()
    {
        return !empty($this->redirectUrl);
    }

    /**
     * @return string
     */
    public function getRedirectUrl()
    {
        return $this->redirectUrl;
    }

    /**
     * @param Record[]
     */
    public function addPushedProducts(array $pushedProducts)
    {
        foreach ($pushedProducts as $product)
            $this->pushedProducts[] = $product;
    }

    /**
     * @return bool True, if at least one pushed product exists.
     */
    public function hasPushedProducts()
    {
        return count($this->pushedProducts) > 0;
    }

    /**
     * @return Record[]
     */
    public function getPushedProducts()
    {
        return $this->pushedProducts;
    }

    /**
     * In case of a collision of keys, the existing feedback string will be
     * overwritten.
     *
     * @param string[]
     */
    public function addFeedback(array $feedback)
    {
        foreach($feedback as $label => $text)
            $this->feedback[$label] = (string)$text;
    }

    /**
     * @param string $label Optional label to check.
     * @return bool If $label parameter is given, this returns true if there is
     *         any feedback for that particular label. If $label is ommitted,
     *         this returns true if there is any feedback at all.
     */
    public function hasFeedback($label = null)
    {
        if (is_null($label))
            return count($this->feedback) > 0
                   && implode('', $this->feedback) != '';
        else
            return isset($this->feedback[$label])
                   && $this->feedback[$label] != '';
    }

    /**
     * @param string $label
     * @return string
     */
    public function getFeedback($label)
    {
        if (isset($this->feedback[$label]))
            return $this->feedback[$label];
        else
            return '';
    }

    /**
     * @return string[]
     */
    public function getFeedbackArray()
    {
        return $this->feedback;
    }

    /**
     * @param AdvisorQuestion[] $activeQuestions
     */
    public function addActiveQuestions(array $activeQuestions)
    {
        foreach ($activeQuestions as $question)
            $this->activeQuestions[] = $question;
    }

    /**
     * @return bool
     */
    public function hasActiveQuestions()
    {
        return count($this->activeQuestions) > 0;
    }

    /**
     * @return AdvisorQuestion[]
     */
    public function getActiveQuestions()
    {
        return $this->activeQuestions;
    }

    /**
     * Add questions to the top level of the advisor tree.
     *
     * @param AdvisorQuestion[] $advisorTree
     */
    public function addToAdvisorTree(array $advisorTree)
    {
        foreach ($advisorTree as $question)
            $this->advisorTree[] = $question;
    }

    /**
     * @return bool
     */
    public function hasAdvisorTree()
    {
        return count($this->advisorTree) > 0;
    }

    /**
     * @return AdvisorQuestion[]
     */
    public function getAdvisorTree()
    {
        return $this->advisorTree;
    }
}


<?php
namespace FACTFinder\Adapter;

use FACTFinder\Loader as FF;

class ProductCampaign extends PersonalisedResponse
{
    /**
     * @var FACTFinder\Util\LoggerInterface
     */
    private $log;

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

    /**
     * @var bool
     */
    protected $isShoppingCartCampaign = false;
    protected $isLandingPageCampaign = false;

    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('ProductCampaign.ff');
        $this->parameters['do'] = 'getProductCampaigns';
        $this->parameters['format'] = 'json';

        $this->useJsonResponseContentProcessor();
    }

    /**
     * Set one or multiple product numbers to get campaigns for, overwriting any
     * numbers previously set. Note that multiple numbers will only be considered for
     * shopping cart campaigns. For product detail campaigns only the first number
     * will be used.
     *
     * @param string|string[] $productNumbers One or more product numbers.
     */
    public function setProductNumbers($productNumbers)
    {
        $parameters = $this->request->getParameters();
        $parameters['productNumber'] = $productNumbers;
        $this->upToDate = false;
    }

    /**
     * Add one or multiple product numbser to get campaigns for, in addition to any
     * numbers previously set.
     *
     * @param string|string[] $productNumbers One or more product numbers.
     */
    public function addProductNumbers($productNumbers)
    {
        $parameters = $this->request->getParameters();
        $parameters->add('productNumber', $productNumbers);
        $this->upToDate = false;
    }

    /**
     * Set the page id to get landing page campaigns.
     *
     * @param string $pageId The id which determines the campaigns for a page.
     */
    public function setPageId($pageId)
    {
        $parameters = $this->request->getParameters();
        $parameters->add('pageId', $pageId);
        $this->upToDate = false;
    }

    /**
     * Sets the adapter up for fetching campaigns on product detail pages
     */
    public function makeProductCampaign()
    {
        $this->isShoppingCartCampaign = false;
        $this->isLandingPageCampaign = false;
        $this->upToDate = false;
        $this->parameters['do'] = 'getProductCampaigns';
    }

    /**
     * Sets the adapter up for fetching campaigns on shopping cart pages
     */
    public function makeShoppingCartCampaign()
    {
        $this->isShoppingCartCampaign = true;
        $this->isLandingPageCampaign = false;
        $this->upToDate = false;
        $this->parameters['do'] = 'getShoppingCartCampaigns';
    }

    /**
     * Sets the adapter up for fetching campaigns on landing pages
     */
    public function makePageCampaign()
    {
        $this->isLandingPageCampaign = true;
        $this->upToDate = false;
        $this->parameters['do'] = 'getPageCampaigns';
    }

    /**
     * Returns campaigns for IDs previously specified. If no IDs have been
     * set, there will be a warning raised and an empty result will be returned.
     *
     * @return \FACTFinder\Data\Result
     */
    public function getCampaigns()
    {
        if (is_null($this->campaigns)
            || !$this->upToDate
        ) {
            $this->request->resetLoaded();
            $this->campaigns = $this->createCampaigns();
            $this->upToDate = true;
        }

        return $this->campaigns;
    }

    private function createCampaigns()
    {
        $campaigns = array();

        if ($this->isLandingPageCampaign && !isset($this->parameters['pageId']))
        {
            $this->log->warn('Page campaigns cannot be loaded without a page ID. '
                           . 'Use setPageId() first.');
        }
        else if (!$this->isLandingPageCampaign && !isset($this->parameters['productNumber']))
        {
            $this->log->warn('Product campaigns cannot be loaded without a product ID. '
                           . 'Use setProductIDs() or addProductIDs() first.');
        }
        else
        {
            if ($this->isShoppingCartCampaign || $this->isLandingPageCampaign)
            {
                $jsonData = $this->getResponseContent();
            }
            else
            {
                // Use only the first product ID
                $productIDs = $this->parameters['productNumber'];
                if (is_array($productIDs) && !empty($productIDs))
                {
                    $this->parameters['productNumber'] = $productIDs[0];
                }
                $jsonData = $this->getResponseContent();

                // Restore IDs
                $this->parameters['productNumber'] = $productIDs;
            }

            if(parent::isValidResponse($jsonData))
            {
                foreach ($jsonData as $campaignData) {
                    $campaign = $this->createEmptyCampaignObject($campaignData);

                    $this->fillCampaignWithFeedback($campaign, $campaignData);
                    $this->fillCampaignWithPushedProducts($campaign, $campaignData);

                    $campaigns[] = $campaign;
                }
            }
        }

        $campaignIterator = FF::getInstance(
            'Data\CampaignIterator',
            $campaigns
        );
        return $campaignIterator;
    }

    /**
     * @param mixed[] $campaignData An associative array corresponding to the
     *        JSON for a single campaign.
     * @return \FACTFinder\Data\Campaign
     */
    private function createEmptyCampaignObject(array $campaignData)
    {
        return FF::getInstance(
            'Data\Campaign',
            $campaignData['name'],
            $campaignData['category'],
            $campaignData['target']['destination']
        );
    }

    /**
     * @param \FACTFinder\Data\Campaign $campaign The campaign object to be
     *        filled.
     * @param mixed[] $campaignData An associative array corresponding to the
     *        JSON for that campaign.
     */
    protected function fillCampaignWithFeedback(
        \FACTFinder\Data\Campaign $campaign,
        array $campaignData
    ) {
        if (!empty($campaignData['feedbackTexts']))
        {
            $feedback = array();

            foreach ($campaignData['feedbackTexts'] as $feedbackData)
            {
                // If present, add the feedback to both the label and the ID.
                $html = $feedbackData['html'];
                $text = $feedbackData['text'];
                if (!$html)
                {
                    $text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
                }

                $label = $feedbackData['label'];
                if ($label !== '')
                    $feedback[$label] = $text;

                $id = $feedbackData['id'];
                if ($id !== null)
                    $feedback[$id] = $text;
            }

            $campaign->addFeedback($feedback);
        }
    }

    /**
     * @param \FACTFinder\Data\Campaign $campaign The campaign object to be
     *        filled.
     * @param mixed[] $campaignData An associative array corresponding to the
     *        JSON for that campaign.
     */
    private function fillCampaignWithPushedProducts(
        \FACTFinder\Data\Campaign $campaign,
        array $campaignData
    ) {
        if (!empty($campaignData['pushedProductsRecords']))
        {
            $pushedProducts = array();

            foreach ($campaignData['pushedProductsRecords'] as $recordData)
            {
                $pushedProducts[] = FF::getInstance(
                    'Data\Record',
                    (string)$recordData['id'],
                    $recordData['record']
                );
            }

            $campaign->addPushedProducts($pushedProducts);
        }
    }

    /**
     * Get the product campaigns from FACT-Finder as the string returned by the
     * server.
     *
     * @param string $format Optional. Either 'json' or 'jsonp'. Use to
     *                       overwrite the 'format' parameter.
     * @param string $callback Optional name to overwrite the 'callback'
     *                         parameter, which determines the name of the
     *                         callback the response is wrapped in.
     *
     * @return string
     */
    public function getRawProductCampaigns($format = null, $callback = null)
    {
        $this->usePassthroughResponseContentProcessor();

        if (!is_null($format))
            $this->parameters['format'] = $format;
        if (!is_null($callback))
            $this->parameters['callback'] = $callback;

        return $this->getResponseContent();
    }
}


<?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
 */

/**
 * Most of the work of the {@link LoggerPatternLayout} class
 * is delegated to the {@link LoggerPatternParser} class.
 *
 *
It is this class that parses conversion patterns and creates
 * a chained list of {@link LoggerPatternConverter} converters.

 *
 * @version $Revision: 1395467 $
 * @package log4php
 * @subpackage helpers
 *
 * @since 0.3
 */
class LoggerPatternParser {

	/** Escape character for conversion words in the conversion pattern. */
	const ESCAPE_CHAR = '%';

	/** Maps conversion words to relevant converters. */
	private $converterMap;

	/** Conversion pattern used in layout. */
	private $pattern;

	/** Regex pattern used for parsing the conversion pattern. */
	private $regex;

	/**
	 * First converter in the chain.
	 * @var LoggerPatternConverter
	 */
	private $head;

	/** Last converter in the chain. */
	private $tail;

	public function __construct($pattern, $converterMap) {
		$this->pattern = $pattern;
		$this->converterMap = $converterMap;

		// Construct the regex pattern
		$this->regex =
			'/' .                       // Starting regex pattern delimiter
			self::ESCAPE_CHAR .         // Character which marks the start of the conversion pattern
			'(?P<modifiers>[0-9.-]*)' . // Format modifiers (optional)
			'(?P<word>[a-zA-Z]+)' .     // The conversion word
			'(?P<option>{[^}]*})?' .    // Conversion option in braces (optional)
			'/';                        // Ending regex pattern delimiter
	}

	/**
	 * Parses the conversion pattern string, converts it to a chain of pattern
	 * converters and returns the first converter in the chain.
	 *
	 * @return LoggerPatternConverter
	 */
	public function parse() {

		// Skip parsing if the pattern is empty
		if (empty($this->pattern)) {
			$this->addLiteral('');
			return $this->head;
		}

		// Find all conversion words in the conversion pattern
		$count = preg_match_all($this->regex, $this->pattern, $matches, PREG_OFFSET_CAPTURE);
		if ($count === false) {
			$error = error_get_last();
			throw new LoggerException("Failed parsing layotut pattern: {$error['message']}");
		}

		$prevEnd = 0;

		foreach($matches[0] as $key => $item) {

			// Locate where the conversion command starts and ends
			$length = strlen($item[0]);
			$start = $item[1];
			$end = $item[1] + $length;

			// Find any literal expressions between matched commands
			if ($start > $prevEnd) {
				$literal = substr($this->pattern, $prevEnd, $start - $prevEnd);
				$this->addLiteral($literal);
			}

			// Extract the data from the matched command
			$word = !empty($matches['word'][$key]) ? $matches['word'][$key][0] : null;
			$modifiers = !empty($matches['modifiers'][$key]) ? $matches['modifiers'][$key][0] : null;
			$option = !empty($matches['option'][$key]) ? $matches['option'][$key][0] : null;

			// Create a converter and add it to the chain
			$this->addConverter($word, $modifiers, $option);

			$prevEnd = $end;
		}

		// Add any trailing literals
		if ($end < strlen($this->pattern)) {
			$literal = substr($this->pattern, $end);
			$this->addLiteral($literal);
		}

		return $this->head;
	}

	/**
	 * Adds a literal converter to the converter chain.
	 * @param string $string The string for the literal converter.
	 */
	private function addLiteral($string) {
		$converter = new LoggerPatternConverterLiteral($string);
		$this->addToChain($converter);
	}

	/**
	 * Adds a non-literal converter to the converter chain.
	 *
	 * @param string $word The conversion word, used to determine which
	 *  converter will be used.
	 * @param string $modifiers Formatting modifiers.
	 * @param string $option Option to pass to the converter.
	 */
	private function addConverter($word, $modifiers, $option) {
 		$formattingInfo = $this->parseModifiers($modifiers);
		$option = trim($option, "{} ");

		if (isset($this->converterMap[$word])) {
			$converter = $this->getConverter($word, $formattingInfo, $option);
			$this->addToChain($converter);
		} else {
			trigger_error("log4php: Invalid keyword '%$word' in converison pattern. Ignoring keyword.", E_USER_WARNING);
		}
	}

	/**
	 * Determines which converter to use based on the conversion word. Creates
	 * an instance of the converter using the provided formatting info and
	 * option and returns it.
	 *
	 * @param string $word The conversion word.
	 * @param LoggerFormattingInfo $info Formatting info.
	 * @param string $option Converter option.
	 *
	 * @throws LoggerException
	 *
	 * @return LoggerPatternConverter
	 */
	private function getConverter($word, $info, $option) {
		if (!isset($this->converterMap[$word])) {
			throw new LoggerException("Invalid keyword '%$word' in converison pattern. Ignoring keyword.");
		}

		$converterClass = $this->converterMap[$word];
		if(!class_exists($converterClass)) {
			throw new LoggerException("Class '$converterClass' does not exist.");
		}

		$converter = new $converterClass($info, $option);
		if(!($converter instanceof LoggerPatternConverter)) {
			throw new LoggerException("Class '$converterClass' is not an instance of LoggerPatternConverter.");
		}

		return $converter;
	}

	/** Adds a converter to the chain and updates $head and $tail pointers. */
	private function addToChain(LoggerPatternConverter $converter) {
		if (!isset($this->head)) {
			$this->head = $converter;
			$this->tail = $this->head;
		} else {
			$this->tail->next = $converter;
			$this->tail = $this->tail->next;
		}
	}

	/**
	 * Parses the formatting modifiers and produces the corresponding
	 * LoggerFormattingInfo object.
	 *
	 * @param string $modifier
	 * @return LoggerFormattingInfo
	 * @throws LoggerException
	 */
	private function parseModifiers($modifiers) {
		$info = new LoggerFormattingInfo();

		// If no modifiers are given, return default values
		if (empty($modifiers)) {
			return $info;
		}

		// Validate
		$pattern = '/^(-?[0-9]+)?\.?-?[0-9]+$/';
		if (!preg_match($pattern, $modifiers)) {
			trigger_error("log4php: Invalid modifier in conversion pattern: [$modifiers]. Ignoring modifier.", E_USER_WARNING);
			return $info;
		}

		$parts = explode('.', $modifiers);

		if (!empty($parts[0])) {
			$minPart = (integer) $parts[0];
			$info->min = abs($minPart);
			$info->padLeft = ($minPart > 0);
		}

		if (!empty($parts[1])) {
			$maxPart = (integer) $parts[1];
			$info->max = abs($maxPart);
			$info->trimLeft = ($maxPart < 0);
		}

		return $info;
	}
}


<?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
 */

/**
 * A simple layout.
 *
 * Returns the log statement in a format consisting of the
 * level, followed by " - " and then the message.
 *
 * For example the following php and properties files
 *
 * {@example ../../examples/php/layout_simple.php 19}

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

 *
 * would result in:
 *
 * <samp>INFO - Hello World!</samp>
 *
 * @version $Revision: 1213283 $
 * @package log4php
 * @subpackage layouts
 */
class LoggerLayoutSimple extends LoggerLayout {
	/**
	 * Returns the log statement in a format consisting of the
	 * level, followed by " - " and then the
	 * message. For example,
	 * <samp> INFO - "A message" </samp>
	 *
	 * @param LoggerLoggingEvent $event
	 * @return string
	 */
	public function format(LoggerLoggingEvent $event) {
		$level = $event->getLevel();
		$message = $event->getRenderedMessage();
		return "$level - $message" . PHP_EOL;
	}
}


<?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 root logger.
 *
 * @version $Revision: 1343190 $
 * @package log4php
 * @see Logger
 */
class LoggerRoot extends Logger {
	/**
	 * Constructor
	 *
	 * @param integer $level initial log level
	 */
	public function __construct(LoggerLevel $level = null) {
		parent::__construct('root');

		if($level == null) {
			$level = LoggerLevel::getLevelAll();
		}
		$this->setLevel($level);
	}

	/**
	 * @return LoggerLevel the level
	 */
	public function getEffectiveLevel() {
		return $this->getLevel();
	}

	/**
	 * Override level setter to prevent setting the root logger's level to
	 * null. Root logger must always have a level.
	 *
	 * @param LoggerLevel $level
	 */
	public function setLevel(LoggerLevel $level = null) {
		if (isset($level)) {
			parent::setLevel($level);
		} else {
			trigger_error("log4php: Cannot set LoggerRoot level to null.", E_USER_WARNING);
		}
	}

	/**
	 * Override parent setter. Root logger cannot have a parent.
	 * @param Logger $parent
	 */
	public function setParent(Logger $parent) {
		trigger_error("log4php: LoggerRoot cannot have a parent.", E_USER_WARNING);
	}
}


<?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
 */

/**
 * TTCC layout format consists of time, thread, category and nested
 * diagnostic context information, hence the name.
 *
 *
Each of the four fields can be individually enabled or
 * disabled. The time format depends on the DateFormat used.

 *
 *
If no dateFormat is specified it defaults to '%c'.
 * See php {@link PHP_MANUAL#date} function for details.

 *
 * Configurable parameters for this layout are:
 * - {@link $threadPrinting} (true|false) enable/disable pid reporting.
 * - {@link $categoryPrefixing} (true|false) enable/disable logger category reporting.
 * - {@link $contextPrinting} (true|false) enable/disable NDC reporting.
 * - {@link $microSecondsPrinting} (true|false) enable/disable micro seconds reporting in timestamp.
 * - {@link $dateFormat} (string) set date format. See php {@link PHP_MANUAL#date} function for details.
 *
 * An example how to use this layout:
 *
 * {@example ../../examples/php/layout_ttcc.php 19}

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

 *
 * The above would print:

 * <samp>02:28 [13714] INFO root - Hello World!</samp>
 *
 * @version $Revision: 1302503 $
 * @package log4php
 * @subpackage layouts
 *
 * @deprecated LoggerLayout TTCC is deprecated and will be removed in a future release. Please use
 *   LoggerLayoutPattern instead.
 */
class LoggerLayoutTTCC extends LoggerLayout {

	// Internal representation of options
	protected $threadPrinting    = true;
	protected $categoryPrefixing = true;
	protected $contextPrinting   = true;
	protected $microSecondsPrinting = true;

	/**
	 * @var string date format. See {@link PHP_MANUAL#strftime} for details
	 */
	protected $dateFormat = '%c';

	/**
	 * Constructor
	 *
	 * @param string date format
	 * @see dateFormat
	 */
	public function __construct($dateFormat = '') {
		$this->warn("LoggerLayout TTCC is deprecated and will be removed in a future release. Please use LoggerLayoutPattern instead.");
		if (!empty($dateFormat)) {
			$this->dateFormat = $dateFormat;
		}
		return;
	}

	/**
	 * The ThreadPrinting option specifies whether the name of the
	 * current thread is part of log output or not. This is true by default.
	 */
	public function setThreadPrinting($threadPrinting) {
		$this->setBoolean('threadPrinting', $threadPrinting);
	}

	/**
	 * @return boolean Returns value of the ThreadPrinting option.
	 */
	public function getThreadPrinting() {
		return $this->threadPrinting;
	}

	/**
	 * The CategoryPrefixing option specifies whether {@link Category}
	 * name is part of log output or not. This is true by default.
	 */
	public function setCategoryPrefixing($categoryPrefixing) {
		$this->setBoolean('categoryPrefixing', $categoryPrefixing);
	}

	/**
	 * @return boolean Returns value of the CategoryPrefixing option.
	 */
	public function getCategoryPrefixing() {
		return $this->categoryPrefixing;
	}

	/**
	 * The ContextPrinting option specifies log output will include
	 * the nested context information belonging to the current thread.
	 * This is true by default.
	 */
	public function setContextPrinting($contextPrinting) {
		$this->setBoolean('contextPrinting', $contextPrinting);
	}

	/**
	 * @return boolean Returns value of the ContextPrinting option.
	 */
	public function getContextPrinting() {
		return $this->contextPrinting;
	}

	/**
	 * The MicroSecondsPrinting option specifies if microseconds infos
	 * should be printed at the end of timestamp.
	 * This is true by default.
	 */
	public function setMicroSecondsPrinting($microSecondsPrinting) {
		$this->setBoolean('microSecondsPrinting', $microSecondsPrinting);
	}

	/**
	 * @return boolean Returns value of the MicroSecondsPrinting option.
	 */
	public function getMicroSecondsPrinting() {
		return $this->microSecondsPrinting;
	}

	public function setDateFormat($dateFormat) {
		$this->setString('dateFormat', $dateFormat);
	}

	/**
	 * @return string
	 */
	public function getDateFormat() {
		return $this->dateFormat;
	}

	/**
	 * In addition to the level of the statement and message, the
	 * returned string includes time, thread, category.
	 *
Time, thread, category are printed depending on options.
	 *
	 * @param LoggerLoggingEvent $event
	 * @return string
	 */
	public function format(LoggerLoggingEvent $event) {
		$timeStamp = (float)$event->getTimeStamp();
		$format = strftime($this->dateFormat, (int)$timeStamp);

		if ($this->microSecondsPrinting) {
			$usecs = floor(($timeStamp - (int)$timeStamp) * 1000);
			$format .= sprintf(',%03d', $usecs);
		}

		$format .= ' ';

		if ($this->threadPrinting) {
			$format .= '['.getmypid().'] ';
		}

		$level = $event->getLevel();
		$format .= $level.' ';

		if($this->categoryPrefixing) {
			$format .= $event->getLoggerName().' ';
		}

		if($this->contextPrinting) {
			$ndc = $event->getNDC();
			if($ndc != null) {
				$format .= $ndc.' ';
			}
		}

		$format .= '- '.$event->getRenderedMessage();
		$format .= PHP_EOL;

		return $format;
	}

	public function ignoresThrowable() {
		return true;
	}
}


<?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
 */

/**
 * Manages defined renderers and determines which renderer to use for a given
 * input.
 *
 * @version $Revision: 1394956 $
 * @package log4php
 * @subpackage renderers
 * @since 0.3
 */
class LoggerRendererMap {

	/**
	 * Maps class names to appropriate renderers.
	 * @var array
	 */
	private $map = array();

	/**
	 * The default renderer to use if no specific renderer is found.
	 * @var LoggerRenderer
	 */
	private $defaultRenderer;

	public function __construct() {

		// Set default config
		$this->reset();
	}

	/**
	 * Adds a renderer to the map.
	 *
	 * If a renderer already exists for the given <var>$renderedClass</var> it
	 * will be overwritten without warning.
	 *
	 * @param string $renderedClass The name of the class which will be
	 * 		rendered by the renderer.
	 * @param string $renderingClass The name of the class which will
	 * 		perform the rendering.
	 */
	public function addRenderer($renderedClass, $renderingClass) {
		// Check the rendering class exists
		if (!class_exists($renderingClass)) {
			trigger_error("log4php: Failed adding renderer. Rendering class [$renderingClass] not found.");
			return;
		}

		// Create the instance
		$renderer = new $renderingClass();

		// Check the class implements the right interface
		if (!($renderer instanceof LoggerRenderer)) {
			trigger_error("log4php: Failed adding renderer. Rendering class [$renderingClass] does not implement the LoggerRenderer interface.");
			return;
		}

		// Convert to lowercase since class names in PHP are not case sensitive
		$renderedClass = strtolower($renderedClass);

		$this->map[$renderedClass] = $renderer;
	}

	/**
	 * Sets a custom default renderer class.
	 *
	 * TODO: there's code duplication here. This method is almost identical to
	 * addRenderer(). However, it has custom error messages so let it sit for
	 * now.
	 *
	 * @param string $renderingClass The name of the class which will
	 * 		perform the rendering.
	 */
	public function setDefaultRenderer($renderingClass) {
		// Check the class exists
		if (!class_exists($renderingClass)) {
			trigger_error("log4php: Failed setting default renderer. Rendering class [$renderingClass] not found.");
			return;
		}

		// Create the instance
		$renderer = new $renderingClass();

		// Check the class implements the right interface
		if (!($renderer instanceof LoggerRenderer)) {
			trigger_error("log4php: Failed setting default renderer. Rendering class [$renderingClass] does not implement the LoggerRenderer interface.");
			return;
		}

		$this->defaultRenderer = $renderer;
	}

	/**
	 * Returns the default renderer.
	 * @var LoggerRenderer
	 */
	public function getDefaultRenderer() {
		return $this->defaultRenderer;
	}

	/**
	 * Finds the appropriate renderer for the given <var>input</var>, and
	 * renders it (i.e. converts it to a string).
	 *
	 * @param mixed $input Input to render.
	 * @return string The rendered contents.
	 */
	public function findAndRender($input) {
		if ($input === null) {
			return null;
		}

		// For objects, try to find a renderer in the map
		if(is_object($input)) {
			$renderer = $this->getByClassName(get_class($input));
			if (isset($renderer)) {
				return $renderer->render($input);
			}
		}

		// Fall back to the default renderer
		return $this->defaultRenderer->render($input);
	}

	/**
	 * Returns the appropriate renderer for a given object.
	 *
	 * @param mixed $object
	 * @return LoggerRenderer Or null if none found.
	 */
	public function getByObject($object) {
		if (!is_object($object)) {
			return null;
		}
		return $this->getByClassName(get_class($object));
	}

	/**
	 * Returns the appropriate renderer for a given class name.
	 *
	 * If no renderer could be found, returns NULL.
	 *
	 * @param string $class
	 * @return LoggerRendererObject Or null if not found.
	 */
	public function getByClassName($class) {
		for(; !empty($class); $class = get_parent_class($class)) {
			$class = strtolower($class);
			if(isset($this->map[$class])) {
				return $this->map[$class];
			}
		}
		return null;
	}

	/** Empties the renderer map. */
	public function clear() {
		$this->map = array();
	}

	/** Resets the renderer map to it's default configuration. */
	public function reset() {
		$this->defaultRenderer = new LoggerRendererDefault();
		$this->clear();
		$this->addRenderer('Exception', 'LoggerRendererException');
	}
}


<?php
namespace FACTFinder\Core;

/**
 * Base class for the configuration implementation.
 */
abstract class AbstractConfiguration implements ConfigurationInterface
{
    /**
     * @param string name
     * @return string value
     */
    public function getCustomValue($name)
    {
        return null;
    }

    /**
     * @return bool
     */
    public function isDebugEnabled()
    {
        return false;
    }

    /**
     * @return string
     */
    public function getRequestProtocol()
    {
        return 'http';
    }

    /**
     * @return string
     */
    public function getServerAddress()
    {
        return null;
    }

    /**
     * @return int
     */
    public function getServerPort()
    {
        return '80';
    }

    /**
     * @return string
     */
    public function getContext()
    {
        return null;
    }

    /**
     * @return string
     */
    public function getChannel()
    {
        return null;
    }

    /**
     * @return string
     */
    public function getLanguage()
    {
        return null;
    }

    /**
     * @return boolean
     */
    public function isHttpAuthenticationType()
    {
        return false;
    }

    /**
     * @return boolean
     */
    public function isSimpleAuthenticationType()
    {
        return false;
    }

    /**
     * @return boolean
     */
    public function isAdvancedAuthenticationType()
    {
        return false;
    }

    /**
     * @return string
     */
    public function getUserName()
    {
        return null;
    }

    /**
     * @return string
     */
    public function getPassword()
    {
        return null;
    }

    /**
     * @return string
     */
    public function getAuthenticationPrefix()
    {
        return null;
    }

    /**
     * @return string
     */
    public function getAuthenticationPostfix()
    {
        return null;
    }

    /**
     * Get mappings from server to client parameters.
     *
     * @return array
     */
    public function getClientMappings()
    {
        return array();
    }

    /**
     * Get mappings from client to server parameters.
     *
     * @return array
     */
    public function getServerMappings()
    {
        return array();
    }

    /**
     * Get parameters which should be ignored in client URLs.
     *
     * @return array with string as key and boolean true as value for each item
     */
    public function getIgnoredClientParameters()
    {
        return array();
    }

    /**
     * Get parameters which should be ignored in server URLs.
     *
     * @return array with string as key and boolean true as value for each item
     */
    public function getIgnoredServerParameters()
    {
        return array();
    }

    /**
     * Get parameters which are allowed in client URLs.
     *
     * @return array with string as key and boolean true as value for each item
     */
    public function getWhitelistClientParameters()
    {
        return array();
    }

    /**
     * Get parameters which are allowed in server URLs.
     *
     * @return array with string as key and boolean true as value for each item
     */
    public function getWhitelistServerParameters()
    {
        return array(
            '/^filter.*/' => true,
            '/^sort.*/' => true,
            '/^substringFilter.*/' => true,
            'advisorStatus' => true,
            'callback' => true,
            'campaign' => true,
            'catalog' => true,
            'channel' => true,
            'cookieId' => true,
            'count' => true,
            'do' => true,
            'event' => true,
            'followSearch' => true,
            'format' => true,
            'id' => true,
            'ids' => true,
            'idsOnly' => true,
            'ignoreForCache' => true,
            'instoreAds' => true,
            'isArticleNumber' => true,
            'log' => true,
            'mainId' => true,
            'masterId' => true,
            'maxRecordCount' => true,
            'maxResults' => true,
            'navigation' => true,
            'noArticleNumberSearch' => true,
            'omitContextName' => true,
            'origPageSize' => true,
            'origPos' => true,
            'page' => true,
            'pageId' => true,
            'pageSize' => true,
            'pos' => true,
            'price' => true,
            'productNumber' => true,
            'productsPerPage' => true,
            'query' => true,
            'queryFromSuggest' => true,
            'searchField' => true,
            'seoPath' => true,
            'sid' => true,
            'simi' => true,
            'title' => true,
            'useAsn' => true,
            'useAso' => true,
            'useCampaigns' => true,
            'useFoundWords' => true,
            'useKeywords' => true,
            'usePersonalization' => true,
            'userId' => true,
            'userInput' => true,
            'useSemanticEnhancer' => true,
            'verbose' => true,
            'wordCount' => true
        );
    }

    /**
     * Get parameters which are required in client URLs.
     *
     * @return array with parameter name as key and default value as value.
     */
    public function getRequiredClientParameters()
    {
        return array();
    }

    /**
     * Get parameters which are required in server URLs.
     *
     * @return array with parameter name as key and default value as value.
     */
    public function getRequiredServerParameters()
    {
        return array();
    }

    /**
     * Get default connect timeout for all adapters.
     *
     * @return int
     */
    public function getDefaultConnectTimeout()
    {
        return 2;
    }

    /**
     * Get default timeout for all adapters.
     *
     * @return int
     */
    public function getDefaultTimeout()
    {
        return 4;
    }

    /**
     * Get connect timeout for Suggest adapter.
     *
     * @return int
     */
    public function getSuggestConnectTimeout()
    {
        return 2;
    }

    /**
     * Get timeout for Suggest adapter.
     *
     * @return int
     */
    public function getSuggestTimeout()
    {
        return 2;
    }

    /**
     * Get connect timeout for Tracking adapter.
     *
     * @return int
     */
    public function getTrackingConnectTimeout()
    {
        return 2;
    }

    /**
     * Get timeout for Tracking adapter.
     *
     * @return int
     */
    public function getTrackingTimeout()
    {
        return 2;
    }

    /**
     * Get connect timeout for Import adapter.
     *
     * @return int
     */
    public function getImportConnectTimeout()
    {
        return 10;
    }

    /**
     * Get timeout for Import adapter-
     *
     * @return int
     */
    public function getImportTimeout()
    {
        return 360;
    }

    /**
     * Get encoding for content to be sent to the browser.
     *
     * @return string
     */
    public function getPageContentEncoding()
    {
        return 'UTF-8';
    }

    /**
     * Get encoding for URLs of the client.
     *
     * @return string
     */
    public function getClientUrlEncoding()
    {
        return 'UTF-8';
    }
}


<?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
 */

/**
 * Converts ini configuration files to a PHP array.
 *
 * These used to be called "properties" files (inherited from log4j), and that
 * file extension is still supported.
 *
 * @package log4php
 * @subpackage configurators
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 * @version $Revision: 1343601 $
 * @since 2.2
 */
class LoggerConfigurationAdapterINI implements LoggerConfigurationAdapter {

	/** Name to assign to the root logger. */
	const ROOT_LOGGER_NAME = "root";

	/** Prefix used for defining logger additivity. */
	const ADDITIVITY_PREFIX = "log4php.additivity.";

	/** Prefix used for defining logger threshold. */
	const THRESHOLD_PREFIX = "log4php.threshold";

	/** Prefix used for defining the root logger. */
	const ROOT_LOGGER_PREFIX = "log4php.rootLogger";

	/** Prefix used for defining a logger. */
	const LOGGER_PREFIX = "log4php.logger.";

	/** Prefix used for defining an appender. */
	const APPENDER_PREFIX = "log4php.appender.";

	/** Prefix used for defining a renderer. */
	const RENDERER_PREFIX = "log4php.renderer.";

	/** Holds the configuration. */
	private $config = array();

	/**
	 * Loads and parses the INI configuration file.
	 *
	 * @param string $url Path to the config file.
	 * @throws LoggerException
	 */
	private function load($url) {
		if (!file_exists($url)) {
			throw new LoggerException("File [$url] does not exist.");
		}

		$properties = @parse_ini_file($url, true);
		if ($properties === false) {
			$error = error_get_last();
			throw new LoggerException("Error parsing configuration file: {$error['message']}");
		}

		return $properties;
	}

	/**
	* Converts the provided INI configuration file to a PHP array config.
	*
	* @param string $path Path to the config file.
	* @throws LoggerException If the file cannot be loaded or parsed.
	*/
	public function convert($path) {
		// Load the configuration
		$properties = $this->load($path);

		// Parse threshold
		if (isset($properties[self::THRESHOLD_PREFIX])) {
			$this->config['threshold'] = $properties[self::THRESHOLD_PREFIX];
		}

		// Parse root logger
		if (isset($properties[self::ROOT_LOGGER_PREFIX])) {
			$this->parseLogger($properties[self::ROOT_LOGGER_PREFIX], self::ROOT_LOGGER_NAME);
		}

		$appenders = array();

		foreach($properties as $key => $value) {
			// Parse loggers
			if ($this->beginsWith($key, self::LOGGER_PREFIX)) {
				$name = substr($key, strlen(self::LOGGER_PREFIX));
				$this->parseLogger($value, $name);
			}

			// Parse additivity
			if ($this->beginsWith($key, self::ADDITIVITY_PREFIX)) {
				$name = substr($key, strlen(self::ADDITIVITY_PREFIX));
				$this->config['loggers'][$name]['additivity'] = $value;
			}

			// Parse appenders
			else if ($this->beginsWith($key, self::APPENDER_PREFIX)) {
				$this->parseAppender($key, $value);
			}

			// Parse renderers
			else if ($this->beginsWith($key, self::RENDERER_PREFIX)) {
				$this->parseRenderer($key, $value);
			}
		}

		return $this->config;
	}

	/**
	 * Parses a logger definition.
	 *
	 * Loggers are defined in the following manner:
	 *

	 * log4php.logger.<name> = [<level>], [<appender-ref>, <appender-ref>, ...]
	 *

	 *
	 * @param string $value The configuration value (level and appender-refs).
	 * @param string $name Logger name.
	 */
	private function parseLogger($value, $name) {
		// Value is divided by commas
		$parts = explode(',', $value);
		if (empty($value) || empty($parts)) {
			return;
		}

		// The first value is the logger level
		$level = array_shift($parts);

		// The remaining values are appender references
		$appenders = array();
		while($appender = array_shift($parts)) {
			$appender = trim($appender);
			if (!empty($appender)) {
				$appenders[] = trim($appender);
			}
		}

		// Find the target configuration
		if ($name == self::ROOT_LOGGER_NAME) {
			$this->config['rootLogger']['level'] = trim($level);
			$this->config['rootLogger']['appenders'] = $appenders;
		} else {
			$this->config['loggers'][$name]['level'] = trim($level);
			$this->config['loggers'][$name]['appenders'] = $appenders;
		}
	}

	/**
	 * Parses an configuration line pertaining to an appender.
	 *
	 * Parses the following patterns:
	 *
	 * Appender class:
	 *

	 * log4php.appender.<name> = <class>
	 *

	 *
	 * Appender parameter:
	 *

	 * log4php.appender.<name>.<param> = <value>
	 *

	 *
 	 * Appender threshold:
	 *

	 * log4php.appender.<name>.threshold = <level>
	 *

	 *
 	 * Appender layout:
	 *

	 * log4php.appender.<name>.layout = <layoutClass>
	 *

	 *
	 * Layout parameter:
	 *

	 * log4php.appender.<name>.layout.<param> = <value>
	 *

	 *
	 * For example, a full appender config might look like:
	 *

	 * log4php.appender.myAppender = LoggerAppenderConsole
	 * log4php.appender.myAppender.threshold = info
	 * log4php.appender.myAppender.target = stdout
	 * log4php.appender.myAppender.layout = LoggerLayoutPattern
	 * log4php.appender.myAppender.layout.conversionPattern = "%d %c: %m%n"
	 *

	 *
	 * After parsing all these options, the following configuration can be
	 * found under $this->config['appenders']['myAppender']:
	 *

	 * array(
	 * 	'class' => LoggerAppenderConsole,
	 * 	'threshold' => info,
	 * 	'params' => array(
	 * 		'target' => 'stdout'
	 * 	),
	 * 	'layout' => array(
	 * 		'class' => 'LoggerAppenderConsole',
	 * 		'params' => array(
	 * 			'conversionPattern' => '%d %c: %m%n'
	 * 		)
	 * 	)
	 * )
	 *

	 *
	 * @param string $key
	 * @param string $value
	 */
	private function parseAppender($key, $value) {

		// Remove the appender prefix from key
		$subKey = substr($key, strlen(self::APPENDER_PREFIX));

		// Divide the string by dots
		$parts = explode('.', $subKey);
		$count = count($parts);

		// The first part is always the appender name
		$name = trim($parts[0]);

		// Only one part - this line defines the appender class
		if ($count == 1) {
			$this->config['appenders'][$name]['class'] = $value;
			return;
		}

		// Two parts - either a parameter, a threshold or layout class
		else if ($count == 2) {

			if ($parts[1] == 'layout') {
				$this->config['appenders'][$name]['layout']['class'] = $value;
				return;
			} else if ($parts[1] == 'threshold') {
				$this->config['appenders'][$name]['threshold'] = $value;
				return;
			} else {
				$this->config['appenders'][$name]['params'][$parts[1]] = $value;
				return;
			}
		}

		// Three parts - this can only be a layout parameter
		else if ($count == 3) {
			if ($parts[1] == 'layout') {
				$this->config['appenders'][$name]['layout']['params'][$parts[2]] = $value;
				return;
			}
		}

		trigger_error("log4php: Don't know how to parse the following line: \"$key = $value\". Skipping.");
	}

	/**
	 * Parses a renderer definition.
	 *
	 * Renderers are defined as:
	 *

	 * log4php.renderer.<renderedClass> = <renderingClass>
	 *

	 *
	 * @param string $key log4php.renderer.<renderedClass>
	 * @param string $value <renderingClass>
	 */
	private function parseRenderer($key, $value) {
		// Remove the appender prefix from key
		$renderedClass = substr($key, strlen(self::APPENDER_PREFIX));
		$renderingClass = $value;

		$this->config['renderers'][] = compact('renderedClass', 'renderingClass');
	}

	/** Helper method. Returns true if $str begins with $sub. */
	private function beginsWith($str, $sub) {
		return (strncmp($str, $sub, strlen($sub)) == 0);
	}

}


<?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.
 */

/**
 * Log events to a system log using the PHP syslog() function.
 *
 * This appenders requires a layout.
 *
 * ## Configurable parameters: ##
 *
 * - **ident** - The ident of the syslog message.
 * - **priority** - The priority for the syslog message (used when overriding
 *     priority).
 * - **facility** - The facility for the syslog message
 * - **overridePriority** - If set to true, the message priority will always
 *     use the value defined in {@link $priority}, otherwise the priority will
 *     be determined by the message's log level.
 * - **option** - The option value for the syslog message.
 *
 * Recognised syslog options are:
 *
 * - CONS 	 - if there is an error while sending data to the system logger, write directly to the system console
 * - NDELAY - open the connection to the logger immediately
 * - ODELAY - delay opening the connection until the first message is logged (default)
 * - PERROR - print log message also to standard error
 * - PID    - include PID with each message
 *
 * Multiple options can be set by delimiting them with a pipe character,
 * e.g.: "CONS|PID|PERROR".
 *
 * Recognised syslog priorities are:
 *
 * - EMERG
 * - ALERT
 * - CRIT
 * - ERR
 * - WARNING
 * - NOTICE
 * - INFO
 * - DEBUG
 *
 * Levels are mapped as follows:
 *
 * - FATAL to LOG_ALERT
 * - ERROR to LOG_ERR
 * - WARN to LOG_WARNING
 * - INFO to LOG_INFO
 * - DEBUG to LOG_DEBUG
 * - TRACE to LOG_DEBUG
 *
 * @version $Revision: 1337820 $
 * @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/syslog.html Appender documentation
 */
class LoggerAppenderSyslog extends LoggerAppender {

	/**
	 * The ident string is added to each message. Typically the name of your application.
	 *
	 * @var string
	 */
	protected $ident = "Apache log4php";

	/**
	 * The syslog priority to use when overriding priority. This setting is
	 * required if {@link overridePriority} is set to true.
	 *
	 * @var string
	 */
	protected $priority;

	/**
	 * The option used when opening the syslog connection.
	 *
	 * @var string
	 */
	protected $option = 'PID|CONS';

	/**
	 * The facility value indicates the source of the message.
	 *
	 * @var string
	 */
	protected $facility = 'USER';

	/**
	 * If set to true, the message priority will always use the value defined
	 * in {@link $priority}, otherwise the priority will be determined by the
	 * message's log level.
	 *
	 * @var string
	 */
	protected $overridePriority = false;

	/**
	 * Holds the int value of the {@link $priority}.
	 * @var int
	 */
	private $intPriority;

	/**
	 * Holds the int value of the {@link $facility}.
	 * @var int
	 */
	private $intFacility;

	/**
	 * Holds the int value of the {@link $option}.
	 * @var int
	 */
	private $intOption;

	/**
	 * Sets the {@link $ident}.
	 *
	 * @param string $ident
	 */
	public function setIdent($ident) {
		$this->ident = $ident;
	}

	/**
	 * Sets the {@link $priority}.
	 *
	 * @param string $priority
	 */
	public function setPriority($priority) {
		$this->priority = $priority;
	}

	/**
	 * Sets the {@link $facility}.
	 *
	 * @param string $facility
	 */
	public function setFacility($facility) {
		$this->facility = $facility;
	}

	/**
	 * Sets the {@link $overridePriority}.
	 *
	 * @param string $overridePriority
	 */
	public function setOverridePriority($overridePriority) {
		$this->overridePriority = $overridePriority;
	}

	/**
	* Sets the 'option' parameter.
	*
	* @param string $option
	*/
	public function setOption($option) {
		$this->option = $option;
	}

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

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

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

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

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

	public function activateOptions() {
		$this->intPriority = $this->parsePriority();
		$this->intOption   = $this->parseOption();
		$this->intFacility = $this->parseFacility();

		$this->closed = false;
	}

	public function close() {
		if($this->closed != true) {
			closelog();
			$this->closed = true;
		}
	}

	/**
	 * Appends the event to syslog.
	 *
	 * Log is opened and closed each time because if it is not closed, it
	 * can cause the Apache httpd server to log to whatever ident/facility
	 * was used in openlog().
	 *
	 * @see http://www.php.net/manual/en/function.syslog.php#97843
	 */
	public function append(LoggerLoggingEvent $event) {
		$priority = $this->getSyslogPriority($event->getLevel());
		$message = $this->layout->format($event);

		openlog($this->ident, $this->intOption, $this->intFacility);
		syslog($priority, $message);
		closelog();
	}

	/** Determines which syslog priority to use based on the given level. */
	private function getSyslogPriority(LoggerLevel $level) {
		if($this->overridePriority) {
			return $this->intPriority;
		}
		return $level->getSyslogEquivalent();
	}

	/** Parses a syslog option string and returns the correspodning int value. */
	private function parseOption() {
		$value = 0;
		$options = explode('|', $this->option);

		foreach($options as $option) {
			if (!empty($option)) {
				$constant = "LOG_" . trim($option);
				if (defined($constant)) {
					$value |= constant($constant);
				} else {
					trigger_error("log4php: Invalid syslog option provided: $option. Whole option string: {$this->option}.", E_USER_WARNING);
				}
			}
		}
		return $value;
	}

	/** Parses the facility string and returns the corresponding int value. */
	private function parseFacility() {
		if (!empty($this->facility)) {
			$constant = "LOG_" . trim($this->facility);
			if (defined($constant)) {
				return constant($constant);
			} else {
				trigger_error("log4php: Invalid syslog facility provided: {$this->facility}.", E_USER_WARNING);
			}
		}
	}

	/** Parses the priority string and returns the corresponding int value. */
	private function parsePriority() {
		if (!empty($this->priority)) {
			$constant = "LOG_" . trim($this->priority);
			if (defined($constant)) {
				return constant($constant);
			} else {
				trigger_error("log4php: Invalid syslog priority provided: {$this->priority}.", E_USER_WARNING);
			}
		}
	}
}