<?php
/**
 *
 * EPV :: The phpBB Forum Extension Pre Validator.
 *
 * @copyright (c) 2014 phpBB Limited <https://www.phpbb.com>
 * @license       GNU General Public License, version 2 (GPL-2.0)
 *
 */
namespace Phpbb\Epv\Output;

use Phpbb\Epv\Files\FileInterface;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

class Output implements \Phpbb\Epv\Output\OutputInterface
{
	private $messages = array();
	private $debugMessages = array();
	private $fatal = 0;
	private $error = 0;
	private $warning = 0;
	private $notice = 0;

	private $output;
	private $debug;

	/** @var int */
	protected $progress = 0;
	/** @var int */
	protected $maxProgress = 0;

	public function __construct(\Symfony\Component\Console\Output\OutputInterface $output, $debug)
	{
		$this->output = $output;
		$this->debug  = $debug;
	}

	/**
	 * Writes a message to the output.
	 *
	 * @param string|array $messages The message as an array of lines or a single string
	 * @param Boolean      $newline  Whether to add a newline
	 * @param integer      $type     The type of output (one of the OUTPUT constants)
	 *
	 * @throws \InvalidArgumentException When unknown output type is given
	 *
	 * @api
	 */
	public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
	{
		return $this->output->write($messages, $newline, $type);
	}

	/**
	 * Writes a message to the output and adds a newline at the end.
	 *
	 * @param string|array $messages The message as an array of lines of a single string
	 * @param integer      $type     The type of output (one of the OUTPUT constants)
	 *
	 * @throws \InvalidArgumentException When unknown output type is given
	 *
	 * @api
	 */
	public function writeln($messages, $type = self::OUTPUT_NORMAL)
	{
		return $this->output->writeln($messages, $type);
	}

	/**
	 * Sets the verbosity of the output.
	 *
	 * @param integer $level The level of verbosity (one of the VERBOSITY constants)
	 *
	 * @api
	 */
	public function setVerbosity($level)
	{
		return $this->output->setVerbosity($level);
	}

	/**
	 * Gets the current verbosity of the output.
	 *
	 * @return integer The current level of verbosity (one of the VERBOSITY constants)
	 *
	 * @api
	 */
	public function getVerbosity()
	{
		return $this->output->getVerbosity();
	}

	/**
	 * Sets the decorated flag.
	 *
	 * @param Boolean $decorated Whether to decorate the messages
	 *
	 * @api
	 */
	public function setDecorated($decorated)
	{
		return $this->output->setDecorated($decorated);
	}

	/**
	 * Gets the decorated flag.
	 *
	 * @return Boolean true if the output will decorate messages, false otherwise
	 *
	 * @api
	 */
	public function isDecorated()
	{
		return $this->output->isDecorated();
	}

	/**
	 * Sets output formatter.
	 *
	 * @param OutputFormatterInterface $formatter
	 *
	 * @api
	 */
	public function setFormatter(OutputFormatterInterface $formatter)
	{
		return $this->output->setFormatter($formatter);
	}

	/**
	 * Returns current output formatter instance.
	 *
	 * @return  OutputFormatterInterface
	 *
	 * @api
	 */
	public function getFormatter()
	{
		return $this->output->getFormatter();
	}

	/**
	 * Write a message to the output, but only if Debug is enabled.
	 *
	 * @param $message string|array $messages The message as an array of lines of a single string
	 *
	 * @throws \InvalidArgumentException When unknown output type is given
	 */
	public function writelnIfDebug($message)
	{
		if ($this->debug)
		{
			$this->debugMessages[] = new Message(Output::DEBUG, $message, null);
		}
	}

	/**
	 * Add a new message to the output of the validator.
	 *
	 * @param                                $type    int message type
	 * @param                                $message string message
	 * @param \Phpbb\Epv\Files\FileInterface $file    File the error happened in. When provided, this is displayed to the user
	 */
	public function addMessage($type, $message, FileInterface $file = null)
	{
		switch ($type)
		{
			case Output::FATAL:
				$this->fatal++;
				break;
			case Output::ERROR:
				$this->error++;
				break;
			case Output::WARNING:
				$this->warning++;
				break;
			case Output::NOTICE:
				$this->notice++;
				break;
			case Output::DEBUG:
				break;
			default:
				// TODO: Decide on this?
		}
		$this->messages[] = new Message($type, $message, $file);
	}

	/**
	 * Get all messages saved into the message queue.
	 * @return array Array with messages
	 */
	public function getMessages()
	{
		return $this->messages;
	}

	/**
	 * Get all saved debug messages in the queue.
	 * @return array Array with messages
	 */
	public function getDebugMessages()
	{
		return $this->debugMessages;
	}

	/**
	 * Get the amount of messages that were fatal.
	 * @return int
	 */
	public function getFatalCount()
	{
		return $this->fatal;
	}

	/**
	 * Get the count for a type;
	 *
	 * @param $type
	 *
	 * @return mixed
	 */
	public function getMessageCount($type)
	{
		switch ($type)
		{
			case Output::FATAL:
				return $this->fatal;
			case Output::ERROR:
				return $this->error;
			case Output::WARNING:
				return $this->warning;
			case Output::NOTICE:
				return $this->notice;
		}
	}

    /**
     * Returns whether verbosity is quiet (-q).
     *
     * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
     */
    public function isQuiet()
    {
    }

    /**
     * Returns whether verbosity is verbose (-v).
     *
     * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
     */
    public function isVerbose()
    {
    }

    /**
     * Returns whether verbosity is very verbose (-vv).
     *
     * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
     */
    public function isVeryVerbose()
    {
    }

    /**
     * Returns whether verbosity is debug (-vvv).
     *
     * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
     */
    public function isDebug()
    {
    }
}


<?php
/**
 *
 * EPV :: The phpBB Forum Extension Pre Validator.
 *
 * @copyright (c) 2014 phpBB Limited <https://www.phpbb.com>
 * @license       GNU General Public License, version 2 (GPL-2.0)
 *
 */
namespace Phpbb\Epv\Files;

use Phpbb\Epv\Files\Exception\FileException;
use Phpbb\Epv\Files\Exception\FileLoadException;
use Phpbb\Epv\Files\Type\BinaryFile;
use Phpbb\Epv\Files\Type\ComposerFile;
use Phpbb\Epv\Files\Type\CssFile;
use Phpbb\Epv\Files\Type\HTMLFile;
use Phpbb\Epv\Files\Type\ImageFile;
use Phpbb\Epv\Files\Type\JavascriptFile;
use Phpbb\Epv\Files\Type\JsonFile;
use Phpbb\Epv\Files\Type\LangFile;
use Phpbb\Epv\Files\Type\LockFile;
use Phpbb\Epv\Files\Type\MigrationFile;
use Phpbb\Epv\Files\Type\PHPFile;
use Phpbb\Epv\Files\Type\PlainFile;
use Phpbb\Epv\Files\Type\RoutingFile;
use Phpbb\Epv\Files\Type\ServiceFile;
use Phpbb\Epv\Files\Type\XmlFile;
use Phpbb\Epv\Files\Type\YmlFile;
use Phpbb\Epv\Output\Output;
use Phpbb\Epv\Output\OutputInterface;

class FileLoader
{
	/**
	 * @var \Phpbb\Epv\Output\OutputInterface
	 */
	private $output;
	private $debug;
	private $basedir;
	private $loadError;
	private $rundir;

	/**
	 * @param OutputInterface $output
	 * @param                 $debug
	 * @param                 $basedir
	 * @param                 $rundir
	 */
	public function __construct(OutputInterface $output, $debug, $basedir, $rundir)
	{

		$this->output  = $output;
		$this->debug   = $debug;
		$this->basedir = $basedir;
		$this->rundir  = $rundir;
	}

	public function loadFile($fileName)
	{
		$file = null;

		$split = explode('.', basename($fileName));
		$size  = sizeof($split);

		try
		{
			// File has no extension
			if ($size == 1)
			{
				// If it is a readme file it is ok.
				// Otherwise add notice.
				if (strtolower($fileName) !== 'readme')
				{
					$this->output->addMessage(Output::NOTICE, sprintf("The file %s has no valid extension.", basename($fileName)));
				}
				$file = new PlainFile($this->debug, $fileName, $this->rundir);
			}
			// File has an extension
			else if ($size > 1)
			{
				// Try to load the file, the last part of $split contains the extension
				$file = self::tryLoadFile($fileName, $split[sizeof($split) - 1]);
			}
			else // Blank filename?
			{
				throw new FileException("Filename was empty");
			}
		}
		catch (FileLoadException $e)
		{
			$this->output->addMessage(Output::FATAL, $e->getMessage());

			return null;
		}

		return $file;
	}

	/**
	 * Attempts to load a file based on extension.
	 *
	 * In case of plaintext files, contents are also checked to see if it isn't a php file.
	 *
	 * @param $fileName
	 * @param $extension
	 *
	 * @return BinaryFile|ComposerFile|CssFile|HTMLFile|JavascriptFile|JsonFile|PHPFile|PlainFile|XmlFile|YmlFile|ImageFile|null
	 */
	private function tryLoadFile($fileName, $extension)
	{
		$this->output->writelnIfDebug("<info>Attempting to load $fileName with extension $extension</info>");
		$this->loadError = false;

		switch (strtolower($extension))
		{
			case 'php':
				// First, check if this file is a lang file.
				$file = basename($fileName);
				$dir  = str_replace($file, '', $fileName);
				$dir  = str_replace($this->basedir, '', $dir);
				$dir  = explode('/', trim($dir, '/'));
				$dir  = array_map('strtolower', $dir);

				if (in_array('language', $dir))
				{
					return new LangFile($this->debug, $fileName, $this->rundir);
				}

				if (in_array('migrations', $dir))
				{
					return new MigrationFile($this->debug, $fileName, $this->rundir);
				}

				return new PHPFile($this->debug, $fileName, $this->rundir);

			case 'html':
			case 'htm':
				return new HTMLFile($this->debug, $fileName, $this->rundir);

			case 'json':
				if (strtolower(basename($fileName)) == 'composer.json')
				{
					return new ComposerFile($this->debug, $fileName, $this->rundir);
				}
				else
				{
					return new JsonFile($this->debug, $fileName, $this->rundir);
				}

			case 'yml':
				if (strtolower(basename($fileName)) == 'services.yml')
				{
					return new ServiceFile($this->debug, $fileName, $this->rundir);
				}
				if (strtolower(basename($fileName)) == 'routing.yml')
				{
					return new RoutingFile($this->debug, $fileName, $this->rundir);
				}

				return new YmlFile($this->debug, $fileName, $this->rundir);

			case 'txt':
			case 'md':
			case 'htaccess':
			case 'gitattributes':
			case 'gitignore':
			case 'map':
			case 'sh': // Decide if we want a special file type for shell files!
				return new PlainFile($this->debug, $fileName, $this->rundir);

			case 'xml':
				return new XmlFile($this->debug, $fileName, $this->rundir);

			case 'js':
				return new JavascriptFile($this->debug, $fileName, $this->rundir);

			case 'css':
				return new CssFile($this->debug, $fileName, $this->rundir);

			case 'gif':
			case 'png':
			case 'jpg':
			case 'jpeg':
			case 'svg':
				return new ImageFile($this->debug, $fileName, $this->rundir);

			case 'swf':
				$this->output->addMessage(Output::NOTICE, sprintf("Found an SWF file (%s), please make sure to include the source files for it, as required by the GPL.", basename($fileName)));

				return new BinaryFile($this->debug, $fileName, $this->rundir);
			case 'ds_store':
				$this->output->addMessage(Output::ERROR, sprintf("Found an OS X specific file at %s, please make sure to remove it prior to submission.", $fileName));

				return new BinaryFile($this->debug, $fileName, $this->rundir);

			case 'lock':
				return new LockFile($this->debug, $fileName, $this->rundir);

			default:

				$file = basename($fileName);
				$this->output->addMessage(Output::WARNING, "Can't detect the file type for $file, handling it as a binary file.");

				return new BinaryFile($this->debug, $fileName, $this->rundir);
		}
	}
}


<?php
/**
 *
 * EPV :: The phpBB Forum Extension Pre Validator.
 *
 * @copyright (c) 2014 phpBB Limited <https://www.phpbb.com>
 * @license       GNU General Public License, version 2 (GPL-2.0)
 *
 */
namespace Phpbb\Epv\Files;

use Phpbb\Epv\Files\Exception\FileException;

abstract class BaseFile implements FileInterface
{

	protected $fileName;
	protected $fileData;
	protected $fileArray;
	protected $debug;
	protected $basedir;

	/**
	 * @param $debug     boolean Debug Mode
	 * @param $fileName  string filename for this file
	 * @param $basedir   string namespace for the extension.
	 *
	 * @throws FileException
	 */
	public function __construct($debug, $fileName, $basedir)
	{
		if (!file_exists($fileName))
		{
			throw new FileException(sprintf("File (%s) could not be found", $fileName));
		}
		$this->debug    = $debug;
		$this->fileName = $fileName;
		$this->fileData = @file_get_contents($this->fileName);
		$this->basedir  = $basedir;

		if ($this->fileData === false)
		{
			throw new FileException("Unable to read file {$fileName}.");
		}
		$this->fileArray = explode("\n", $this->fileData);
	}

	/**
	 * @return array
	 */
	public function getLines()
	{
		return $this->fileArray;
	}

	/**
	 * Get the filename for this file.
	 * @return string
	 */
	public function getFilename()
	{
		return $this->fileName;
	}

	/*
	 * Get the filename without the full path
	 * @return string
	 */
	public function getSaveFilename()
	{
		$filename = $this->fileName;

		if (0 === strpos($filename, $this->basedir))
		{
			$filename = substr($filename, strlen($this->basedir));
		}

		return $filename;
	}

	/**
	 * Get the filedata
	 * @return string
	 */
	public function getFile()
	{
		return $this->fileData;
	}
}


<?php
/**
 *
 * EPV :: The phpBB Forum Extension Pre Validator.
 *
 * @copyright (c) 2014 phpBB Limited <https://www.phpbb.com>
 * @license       GNU General Public License, version 2 (GPL-2.0)
 *
 */

namespace Phpbb\Epv\Events;

use Phpbb\Epv\Output\Output;
use Phpbb\Epv\Output\OutputInterface;

/**
 * Class php_exporter
 * Crawls through a list of files and grabs all php-events
 */
class php_exporter
{
	/** @var string */
	protected $current_file;

	/** @var string */
	protected $current_event;

	/** @var int */
	protected $current_event_line;

	/** @var array */
	protected $events;

	/** @var array */
	protected $file_lines;

	/** @var \Phpbb\Epv\Output\OutputInterface */
	protected $output;

	/** @var string  */
	protected $rundir;

	/** @var string */
	protected $current_clean_file;

	/**
	 * @param \Phpbb\Epv\Output\OutputInterface $output
	 * @param string                            $rundir
	 */
	public function __construct(OutputInterface $output, $rundir)
	{
		$this->output             = $output;
		$this->events             = $this->file_lines = array();
		$this->current_file       = $this->current_event = '';
		$this->current_event_line = 0;
		$this->rundir             = $rundir;
	}

	/**
	 * Get the list of all events
	 *
	 * @return array        Array with events: name => details
	 */
	public function get_events()
	{
		return $this->events;
	}

	/**
	 * Set current event data
	 *
	 * @param string $name Name of the current event (used for error messages)
	 * @param int    $line Line where the current event is placed in
	 *
	 * @return null
	 */
	public function set_current_event($name, $line)
	{
		$this->current_event      = $name;
		$this->current_event_line = $line;
	}

	/**
	 * Set the content of this file
	 *
	 * @param array $content Array with the lines of the file
	 *
	 * @return null
	 */
	public function set_content($content)
	{
		$this->file_lines = $content;
	}

	/**
	 * @param string $file
	 *
	 * @return int Number of events found in this file
	 * @throws \LogicException
	 */
	public function crawl_php_file($file)
	{
		$this->current_file = $file;
		$this->current_clean_file = str_replace($this->rundir, '', $file);
		$this->file_lines   = array();
		$content            = file_get_contents($this->current_file);
		$num_events_found   = 0;

		if (strpos($content, "dispatcher->trigger_event('") || strpos($content, "dispatcher->dispatch('"))
		{
			$this->set_content(explode("\n", $content));
			for ($i = 0, $num_lines = sizeof($this->file_lines); $i < $num_lines; $i++)
			{
				$event_line          = false;
				$found_trigger_event = strpos($this->file_lines[$i], "dispatcher->trigger_event('");
				$arguments           = array();
				if ($found_trigger_event !== false)
				{
					$event_line = $i;
					$this->set_current_event($this->get_event_name($event_line, false), $event_line);

					// Find variables of the event
					$arguments = $this->get_vars_from_array();
					$doc_vars  = $this->get_vars_from_docblock();
					$this->validate_vars_docblock_array($arguments, $doc_vars);
				}
				else
				{
					$found_dispatch = strpos($this->file_lines[$i], "dispatcher->dispatch('");
					if ($found_dispatch !== false)
					{
						$event_line = $i;
						$this->set_current_event($this->get_event_name($event_line, true), $event_line);
					}
				}

				if ($event_line)
				{
					// Validate @event
					$event_line_num = $this->find_event();
					$this->validate_event($this->current_event, $this->file_lines[$event_line_num]);

					// Validate @since
					$since_line_num = $this->find_since();
					$since          = $this->validate_since($this->file_lines[$since_line_num]);

					// Find event description line
					$description_line_num = $this->find_description();
					$description          = substr(trim($this->file_lines[$description_line_num]), strlen('* '));

					if (isset($this->events[$this->current_event]))
					{
						throw new \LogicException("The event '{$this->current_event}' from file "
							. "'{$this->current_clean_file}:{$event_line_num}' already exists in file "
							. "'{$this->events[$this->current_event]['file']}'", 10);
					}

					sort($arguments);
					$this->events[$this->current_event] = array(
						'event'       => $this->current_event,
						'file'        => $this->current_file,
						'arguments'   => $arguments,
						'since'       => $since,
						'description' => $description,
					);
					$num_events_found++;
				}
			}
		}

		return $num_events_found;
	}

	/**
	 * Find the name of the event inside the dispatch() line
	 *
	 * @param int  $event_line
	 * @param bool $is_dispatch Do we look for dispatch() or trigger_event() ?
	 *
	 * @return string    Name of the event
	 * @throws \LogicException
	 */
	public function get_event_name($event_line, $is_dispatch)
	{
		$event_text_line = $this->file_lines[$event_line];
		$event_text_line = ltrim($event_text_line, " \t");

		if ($is_dispatch)
		{
			$regex = '#\$([a-z](?:[a-z0-9_]|->)*)';
			$regex .= '->dispatch\(';
			$regex .= '\'%s\'';
			$regex .= '\);#';
		}
		else
		{
			$regex = '#extract\(\$([a-z](?:[a-z0-9_]|->)*)';
			$regex .= '->trigger_event\(';
			$regex .= '\'%s\'';
			$regex .= ', compact\(\$vars\)\)\);#';
		}

		$match = array();
		preg_match(sprintf($regex, $this->preg_match_event_name()), $event_text_line, $match);
		if (!isset($match[2]))
		{
			$match = array();
			preg_match(sprintf($regex, $this->preg_match_event_name_uppercase()), $event_text_line, $match);

			if (isset($match[2]))
			{
				$this->output->addMessage(Output::ERROR, sprintf('Event names should be all lowercase in %s for event %s', $this->current_clean_file, $match[2]));
			}
			else
			{
				throw new \LogicException("Can not find event name in line '{$event_text_line}' "
					. "in file '{$this->current_clean_file}:{$event_line}'", 1);
			}
		}

		return $match[2];
	}

	/**
	 * Returns a regex match for the event name
	 *
	 * @return string
	 */
	protected function preg_match_event_name()
	{
		return '([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+)';
	}

	protected function preg_match_event_name_uppercase()
	{
		return '([a-zA-Z][a-zA-Z0-9_]*(?:\.[a-zA-Z][a-zA-Z0-9_]*)+)';
	}

	/**
	 * Find the $vars array
	 *
	 * @param    bool   $throw_multiline      Throw an exception when there are too
	 *                                        many arguments in one line.
	 *
 	 * @return array        List of variables
	 * @throws \LogicException
	 */
	public function get_vars_from_array($throw_multiline = true)
	{
		$line = ltrim($this->file_lines[$this->current_event_line - 1], " \t");
		if ($line === ');' || $line === '];')
		{
			$vars_array = $this->get_vars_from_multi_line_array();
		}
		else
		{
			$vars_array = $this->get_vars_from_single_line_array($line, $throw_multiline);
		}

		foreach ($vars_array as $var)
		{
			if (!preg_match('#^([a-zA-Z_][a-zA-Z0-9_]*)$#', $var))
			{
				throw new \LogicException("Found invalid var '{$var}' in array for event '{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 3);
			}
		}

		sort($vars_array);

		return $vars_array;
	}

	/**
	 * Find the variables in a single line array
	 *
	 * @param    string $line
	 * @param    bool   $throw_multiline      Throw an exception when there are too
	 *                                        many arguments in one line.
	 *
	 * @return array        List of variables
	 * @throws \LogicException
	 */
	public function get_vars_from_single_line_array($line, $throw_multiline = true)
	{
		$match = array();
		preg_match('#^\$vars = (?:(\[)|array\()\'([a-z0-9_\' ,]+)\'(?(1)\]|\));$#i', $line, $match);

		if (isset($match[2]))
		{
			$vars_array = explode("', '", $match[2]);
			if ($throw_multiline && sizeof($vars_array) > 6)
			{
				throw new \LogicException('Should use multiple lines for $vars definition '
					. "for event '{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 2);
			}

			return $vars_array;
		}
		else
		{
			throw new \LogicException("Can not find '\$vars = array();'-line for event '{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'. Are you using UNIX style linefeeds?", 1);
		}
	}

	/**
	 * Find the variables in a multi line array
	 *
	 * @return array        List of variables
	 * @throws \LogicException
	 */
	public function get_vars_from_multi_line_array()
	{
		$current_vars_line = 2;
		$var_lines         = array();
		while (!in_array(ltrim($this->file_lines[$this->current_event_line - $current_vars_line], "\t"), ['$vars = array(', '$vars = [']))
		{
			$var_lines[] = substr(trim($this->file_lines[$this->current_event_line - $current_vars_line]), 0, -1);

			$current_vars_line++;
			if ($current_vars_line > $this->current_event_line)
			{
				// Reached the start of the file
				throw new \LogicException("Can not find end of \$vars array for event '{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'.", 2);
			}
		}

		return $this->get_vars_from_single_line_array('$vars = array(' . implode(", ", $var_lines) . ');', false);
	}

	/**
	 * Find the $vars array
	 *
	 * @return array        List of variables
	 * @throws \LogicException
	 */
	public function get_vars_from_docblock()
	{
		$doc_vars          = array();
		$current_doc_line  = 1;
		$found_comment_end = false;
		while (ltrim($this->file_lines[$this->current_event_line - $current_doc_line], " \t") !== '/**')
		{
			if (ltrim($this->file_lines[$this->current_event_line - $current_doc_line], " \t") === '*/')
			{
				$found_comment_end = true;
			}

			if ($found_comment_end)
			{
				$var_line = trim($this->file_lines[$this->current_event_line - $current_doc_line]);
				$var_line = preg_replace('!\s+!', ' ', $var_line);
				if (strpos($var_line, '* @var ') === 0)
				{
					$doc_line = explode(' ', $var_line, 5);
					if (sizeof($doc_line) !== 5)
					{
						throw new \LogicException("Found invalid line '{$this->file_lines[$this->current_event_line - $current_doc_line]}' "
							. "for event '{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 1);
					}
					$doc_vars[] = $doc_line[3];
				}
			}

			$current_doc_line++;
			if ($current_doc_line > $this->current_event_line)
			{
				// Reached the start of the file
				throw new \LogicException("Can not find end of docblock for event '{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 2);
			}
		}

		if (empty($doc_vars))
		{
			// Reached the start of the file
			throw new \LogicException("Can not find @var lines for event '{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 3);
		}

		foreach ($doc_vars as $var)
		{
			if (!preg_match('#^([a-zA-Z_][a-zA-Z0-9_]*)$#', $var))
			{
				throw new \LogicException("Found invalid @var '{$var}' in docblock for event "
					. "'{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 4);
			}
		}

		sort($doc_vars);

		return $doc_vars;
	}

	/**
	 * Find the "@since" Information line
	 *
	 * @return int Absolute line number
	 * @throws \LogicException
	 */
	public function find_since()
	{
		return $this->find_tag('since', array('event', 'var'));
	}

	/**
	 * Find the "@event" Information line
	 *
	 * @return int Absolute line number
	 */
	public function find_event()
	{
		return $this->find_tag('event', array());
	}

	/**
	 * Find a "@*" Information line
	 *
	 * @param string $find_tag            Name of the tag we are trying to find
	 * @param array  $disallowed_tags     List of tags that must not appear between
	 *                                    the tag and the actual event
	 *
	 * @return int Absolute line number
	 * @throws \LogicException
	 */
	public function find_tag($find_tag, $disallowed_tags)
	{
		$find_tag_line     = 0;
		$found_comment_end = false;
		while (strpos(ltrim($this->file_lines[$this->current_event_line - $find_tag_line], " \t"), '* @' . $find_tag . ' ') !== 0)
		{
			if ($found_comment_end && ltrim($this->file_lines[$this->current_event_line - $find_tag_line], " \t") === '/**')
			{
				// Reached the start of this doc block
				throw new \LogicException("Can not find '@{$find_tag}' information for event "
					. "'{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 1);
			}

			foreach ($disallowed_tags as $disallowed_tag)
			{
				if ($found_comment_end && strpos(ltrim($this->file_lines[$this->current_event_line - $find_tag_line], " \t"), '* @' . $disallowed_tag) === 0)
				{
					// Found @var after the @since
					throw new \LogicException("Found '@{$disallowed_tag}' information after '@{$find_tag}' for event "
						. "'{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 3);
				}
			}

			if (ltrim($this->file_lines[$this->current_event_line - $find_tag_line], " \t") === '*/')
			{
				$found_comment_end = true;
			}

			$find_tag_line++;
			if ($find_tag_line >= $this->current_event_line)
			{
				// Reached the start of the file
				throw new \LogicException("Can not find '@{$find_tag}' information for event "
					. "'{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 2);
			}
		}

		return $this->current_event_line - $find_tag_line;
	}

	/**
	 * Find a "@*" Information line
	 *
	 * @return int Absolute line number
	 * @throws \LogicException
	 */
	public function find_description()
	{
		$find_desc_line = 0;
		while (ltrim($this->file_lines[$this->current_event_line - $find_desc_line], " \t") !== '/**')
		{
			$find_desc_line++;
			if ($find_desc_line > $this->current_event_line)
			{
				// Reached the start of the file
				throw new \LogicException("Can not find a description for event "
					. "'{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 1);
			}
		}

		$find_desc_line = $this->current_event_line - $find_desc_line + 1;

		$desc = trim($this->file_lines[$find_desc_line]);
		if (strpos($desc, '* @') === 0 || $desc[0] !== '*' || substr($desc, 1) == '')
		{
			// First line of the doc block is a @-line, empty or only contains "*"
			throw new \LogicException("Can not find a description for event "
				. "'{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 2);
		}

		return $find_desc_line;
	}

	/**
	 * Validate "@since" Information
	 *
	 * @param string $line
	 *
	 * @return string
	 * @throws \LogicException
	 */
	public function validate_since($line)
	{
		$match = array();
		preg_match('#^\* @since (\d+(\.\d+)+(?:-(?:a|b|RC|pl)\d+)?)$#', ltrim($line, " \t"), $match);
		if (!isset($match[1]))
		{
			throw new \LogicException("Invalid '@since' information for event "
				. "'{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'");
		}

		return $match[1];
	}

	/**
	 * Validate "@event" Information
	 *
	 * @param string $event_name
	 * @param string $line
	 *
	 * @return string
	 * @throws \LogicException
	 */
	public function validate_event($event_name, $line)
	{
		$event = substr(ltrim($line, " \t"), strlen('* @event '));

		if ($event !== trim($event))
		{
			throw new \LogicException("Invalid '@event' information for event "
				. "'{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 1);
		}

		if ($event !== $event_name)
		{
			throw new \LogicException("Event name does not match '@event' tag for event "
				. "'{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'", 2);
		}

		return $event;
	}

	/**
	 * Validates that two arrays contain the same strings
	 *
	 * @param array $vars_array    Variables found in the array line
	 * @param array $vars_docblock Variables found in the doc block
	 *
	 * @return null
	 * @throws \LogicException
	 */
	public function validate_vars_docblock_array($vars_array, $vars_docblock)
	{
		$vars_array        = array_unique($vars_array);
		$vars_docblock     = array_unique($vars_docblock);
		$sizeof_vars_array = sizeof($vars_array);

		if ($sizeof_vars_array !== sizeof($vars_docblock) || $sizeof_vars_array !== sizeof(array_intersect($vars_array, $vars_docblock)))
		{
			throw new \LogicException("\$vars array does not match the list of '@var' tags for event "
				. "'{$this->current_event}' in file '{$this->current_clean_file}:{$this->current_event_line}'");
		}
	}
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

trait GetterTrait
{
    /**
     * Returns true when a key exists of the same type and value as $OFFSET
     *
     * @param mixed $offset
     * @return bool
     */
    public function has($offset)
    {
        if ($this instanceof \Traversable) {
            foreach ($this as $key => $_) {
                if ($key === $offset) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns the value of a key with the same type and value as $OFFSET, or returns
     * $DEFAULT when it this key does not exist
     *
     * @param mixed $offset
     * @param mixed $default
     * @return mixed
     */
    public function get($offset, $default = null)
    {
        if ($this instanceof \Traversable) {
            foreach ($this as $key => $value) {
                if ($key === $offset) {
                    return $value;
                }
            }
        }
        return $default;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\lib\Containers\KeyValuePair;
use Zicht\Itertools\lib\Interfaces\FiniteIterableInterface;

trait ItemsTrait
{
    /**
     * Returns an array with items, i.e. [$key, $value] pairs, from this iterator
     *
     * @return array
     */
    public function items()
    {
        $items = [];
        if ($this instanceof \Traversable) {
            foreach ($this as $key => $value) {
                $items [] = new KeyValuePair($key, $value instanceof FiniteIterableInterface ? $value->items() : $value);
            }
        }
        return $items;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\conversions;
use Zicht\Itertools\lib\DifferenceIterator;

trait DifferenceTrait
{
    /**
     * Returns a DifferenceIterator containing elements in $this but not in $iterable
     *
     * @param array|string|\Iterator $iterable
     * @param null|string|\Closure $strategy Optional
     * @return null|DifferenceIterator
     */
    public function difference($iterable, $strategy = null)
    {
        if ($this instanceof \Iterator) {
            return new DifferenceIterator(
                $this,
                conversions\mixed_to_iterator($iterable),
                conversions\mixed_to_value_getter($strategy)
            );
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\conversions;
use Zicht\Itertools\lib\SortedIterator;

trait SortedTrait
{
    /**
     * Make an iterator that returns the values from this iterable
     * sorted by $strategy
     *
     * When determining the order of two entries the $strategy is called
     * twice, once for each value, and the results are used to determine
     * the order.  $strategy is called with two parameters: the value and
     * the key of the iterable as the first and second parameter, respectively.
     *
     * When $reverse is true the order of the results are reversed.
     *
     * The sorted() function is guaranteed to be stable.  A sort is stable
     * if it guarantees not to change the relative order of elements that
     * compare equal.  this is helpful for sorting in multiple passes (for
     * example, sort by department, then by salary grade).  This also
     * holds up when $reverse is true.
     *
     * > $list = [['type'=>'B', 'title'=>'second'], ['type'=>'C', 'title'=>'third'], ['type'=>'A', 'title'=>'first']]
     * > iter\iterable($list)->sorted('type')
     * ['type'=>'A', 'title'=>'first'] ['type'=>'B', 'title'=>'second']] ['type'=>'C', 'title'=>'third']
     *
     * @param null|string|\Closure $strategy
     * @param bool $reverse
     * @return SortedIterator
     */
    public function sorted($strategy = null, $reverse = false)
    {
        if (!is_bool($reverse)) {
            throw new \InvalidArgumentException('Argument $reverse must be boolean');
        }

        if ($this instanceof \Iterator) {
            return new SortedIterator(
                conversions\mixed_to_value_getter($strategy),
                $this,
                $reverse
            );
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib;

use Zicht\Itertools\lib\Interfaces\FiniteIterableInterface;
use Zicht\Itertools\lib\Traits\FiniteIterableTrait;

class SliceIterator extends \IteratorIterator implements FiniteIterableInterface
{
    use FiniteIterableTrait;

    /** @var int */
    private $index;

    /** @var int */
    private $start;

    /** @var null|int */
    private $end;

    /**
     * @param \Iterator $iterable
     * @param int $start
     * @param null|int $end
     */
    public function __construct(\Iterator $iterable, $start, $end = null)
    {
        if ($start < 0 || $end < 0) {
            $length = iterator_count($iterable);
        } else {
            // length is not needed.  still, we will define it for code cleanliness
            $length = 0;
        }

        $this->index = 0;
        $this->start = $start < 0 ? $length + $start : $start;
        $this->end = $end === null ? null : ($end < 0 ? $length + $end : $end);
        parent::__construct($iterable);
    }

    /**
     * {@inheritdoc}
     */
    public function valid()
    {
        while ($this->index < $this->start) {
            if (parent::valid()) {
                $this->index += 1;
                parent::next();
            } else {
                return false;
            }
        }

        if (null === $this->end || $this->index < $this->end) {
            return parent::valid();
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function next()
    {
        $this->index += 1;
        parent::next();
    }

    /**
     * {@inheritdoc}
     */
    public function rewind()
    {
        $this->index = 0;
        parent::rewind();
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\conversions;

trait AllTrait
{
    /**
     * Returns true when all elements of this iterable are not empty, otherwise returns false
     *
     * When the optional $STRATEGY argument is given, this argument is used to obtain the
     * value which is tested to be empty.
     *
     * > iterable([1, 'hello world', true])->all()
     * true
     *
     * > iterable([1, null, 3])->all()
     * false
     *
     * @param null|string|\Closure $strategy Optional, when not specified !empty will be used
     * @return null|bool
     */
    public function all($strategy = null)
    {
        if ($this instanceof \Iterator) {
            $strategy = conversions\mixed_to_value_getter($strategy);

            foreach ($this as $item) {
                $tempVarPhp54 = call_user_func($strategy, $item);
                if (empty($tempVarPhp54)) {
                    return false;
                }
            }

            return true;
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\reductions;
use Zicht\Itertools\lib\AccumulateIterator;

trait AccumulateTrait
{
    /**
     * Returns an iterator that containing accumulated elements
     *
     * If the optional $closure argument is supplied, it should be a string:
     * add, sub, mul, min, or max.  Or it can be a Closure taking two
     * arguments that will be used to instead of addition.
     *
     * > iter\iterable([1,2,3,4,5])->accumulate()
     * 1 3 6 10 15
     *
     * > iter\iterable(['One', 'Two', 'Three'])->accumulate(function ($a, $b) { return $a . $b; })
     * 'One' 'OneTwo' 'OneTwoThree'
     *
     * @param string|\Closure $closure
     * @return null|AccumulateIterator
     */
    public function accumulate($closure = 'add')
    {
        if ($this instanceof \Iterator) {
            return new AccumulateIterator(
                $this,
                $closure instanceof \Closure ? $closure : reductions\get_reduction($closure)
            );
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib;

use Zicht\Itertools\lib\Interfaces\FiniteIterableInterface;
use Zicht\Itertools\lib\Traits\FiniteIterableTrait;

class SortedIterator extends \IteratorIterator implements FiniteIterableInterface
{
    use FiniteIterableTrait;

    /**
     * @param \Closure $func
     * @param \Iterator $iterable
     * @param bool $reverse
     */
    public function __construct(\Closure $func, \Iterator $iterable, $reverse = false)
    {
        if ($reverse) {
            $cmp = function ($a, $b) use ($func) {
                $orderA = $a['order'];
                $orderB = $b['order'];
                return $orderA == $orderB ? 0 : ($orderA < $orderB ? 1 : -1);
            };
        } else {
            $cmp = function ($a, $b) use ($func) {
                $orderA = $a['order'];
                $orderB = $b['order'];
                return $orderA == $orderB ? 0 : ($orderA < $orderB ? -1 : 1);
            };
        }

        $data = [];
        foreach ($iterable as $key => $value) {
            $data [] = ['key' => $key, 'value' => $value, 'order' => call_user_func($func, $value, $key)];
        }

        $this->mergeSort($data, $cmp);

        parent::__construct(new \ArrayIterator($data));
    }

    /**
     * {@inheritdoc}
     */
    public function key()
    {
        return $this->getInnerIterator()->current()['key'];
    }

    /**
     * {@inheritdoc}
     */
    public function current()
    {
        return $this->getInnerIterator()->current()['value'];
    }

    /**
     * As the manual says, "If two members compare as equal, their
     * order in the sorted array is undefined."  This means that the
     * sort used is not "stable" and may change the order of elements
     * that compare equal.
     *
     * Sometimes you really do need a stable sort. For example, if you
     * sort a list by one field, then sort it again by another field,
     * but don't want to lose the ordering from the previous field.
     * In that case it is better to use usort with a comparison
     * function that takes both fields into account, but if you can't
     * do that then use the function below. It is a merge sort, which
     * is guaranteed O(n*log(n)) complexity, which means it stays
     * reasonably fast even when you use larger lists (unlike
     * bubblesort and insertion sort, which are O(n^2)).
     *
     * http://www.php.net/manual/en/function.usort.php#38827
     *
     * @param array $array
     * @param \Closure $cmp_function
     */
    protected function mergeSort(array &$array, \Closure $cmp_function)
    {
        // Arrays of size < 2 require no action.
        if (count($array) < 2) {
            return;
        }

        // Split the array in half
        $halfway = count($array) / 2;
        $array1 = array_slice($array, 0, $halfway);
        $array2 = array_slice($array, $halfway);

        // Recurse to sort the two halves
        $this->mergesort($array1, $cmp_function);
        $this->mergesort($array2, $cmp_function);

        // If all of $array1 is <= all of $array2, just append them.
        if (call_user_func($cmp_function, end($array1), $array2[0]) < 1) {
            $array = array_merge($array1, $array2);
            return;
        }

        // Merge the two sorted arrays into a single sorted array
        $array = [];
        $ptr1 = $ptr2 = 0;
        while ($ptr1 < count($array1) && $ptr2 < count($array2)) {
            if (call_user_func($cmp_function, $array1[$ptr1], $array2[$ptr2]) < 1) {
                $array[] = $array1[$ptr1++];
            } else {
                $array[] = $array2[$ptr2++];
            }
        }

        // Merge the remainder
        while ($ptr1 < count($array1)) {
            $array[] = $array1[$ptr1++];
        }
        while ($ptr2 < count($array2)) {
            $array[] = $array2[$ptr2++];
        }
        return;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\lib\Interfaces\FiniteIterableInterface;

trait ToArrayTrait
{
    /**
     * Returns an unsafe array built from this iterator
     *
     * Note that when the iterator contains overlapping keys, that
     * these will be *lost* when converting to an array.
     *
     * A safer option is to use either
     * - $iterable->keys(),
     * - $iterable->values(), or
     * - $iterable->items()
     *
     * @return array
     */
    public function toArray()
    {
        if ($this instanceof \Traversable) {
            $array = iterator_to_array($this);
            array_walk(
                $array,
                function (&$value) {
                    if ($value instanceof FiniteIterableInterface) {
                        $value = $value->toArray();
                    }
                }
            );
            return $array;
        } else {
            return [];
        }
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

trait KeysTrait
{
    /**
     * Returns an array with keys from this iterator
     *
     * @return array
     */
    public function keys()
    {
        $keys = [];
        if ($this instanceof \Traversable) {
            foreach ($this as $key => $value) {
                $keys [] = $key;
            }
        }
        return $keys;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib;

use Zicht\Itertools\lib\Interfaces\FiniteIterableInterface;
use Zicht\Itertools\lib\Traits\FiniteIterableTrait;

class UniqueIterator extends \FilterIterator implements FiniteIterableInterface
{
    use FiniteIterableTrait;

    /** @var \Closure */
    private $func;

    /** @var array */
    private $seen;

    /**
     * @param \Closure $func
     * @param \Iterator $iterable
     */
    public function __construct(\Closure $func, \Iterator $iterable)
    {
        $this->func = $func;
        $this->seen = [];
        parent::__construct($iterable);
    }

    /**
     * {@inheritdoc}
     */
    public function accept()
    {
        $checkValue = call_user_func($this->func, $this->current(), $this->key());
        if (in_array($checkValue, $this->seen)) {
            return false;
        } else {
            $this->seen [] = $checkValue;
            return true;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function rewind()
    {
        $this->seen = [];
        parent::rewind();
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\conversions;
use Zicht\Itertools\lib\FilterIterator;

trait FilterTrait
{
    /**
     * Make an iterator that returns values from this iterable where the
     * $strategy determines that the values are not empty.
     *
     * @param null|string|\Closure $strategy Optional, when not specified !empty will be used
     * @return FilterIterator
     */
    public function filter($strategy = null)
    {
        if ($this instanceof \Iterator) {
            $strategy = conversions\mixed_to_value_getter($strategy);
            $isValid = function ($value, $key) use ($strategy) {
                $tempVarPhp54 = $strategy($value, $key);
                return !empty($tempVarPhp54);
            };

            return new FilterIterator($isValid, $this);
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

// phpcs:disable Zicht.Commenting.PropertyComment.VarTypeAvoidMixed

namespace Zicht\Itertools\lib;

use Zicht\Itertools;
use Zicht\Itertools\lib\Interfaces\FiniteIterableInterface;
use Zicht\Itertools\lib\Traits\FiniteIterableTrait;

class IntersectionIterator extends \FilterIterator implements FiniteIterableInterface
{
    use FiniteIterableTrait;

    /** @var \Closure */
    private $func;

    /** @var mixed[] */
    private $includes;

    /**
     * @param \Iterator $iterable
     * @param \Iterator $includesIterator
     * @param \Closure $func
     */
    public function __construct(\Iterator $iterable, \Iterator $includesIterator, \Closure $func)
    {
        $this->func = $func;
        $this->includes = Itertools\iterable($includesIterator)->map($this->func)->values();
        parent::__construct($iterable);
    }

    /**
     * {@inheritdoc}
     */
    public function accept()
    {
        return in_array(
            call_user_func_array($this->func, [$this->current(), $this->key()]),
            $this->includes
        );
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

// phpcs:disable Zicht.Commenting.PropertyComment.VarTypeAvoidMixed

namespace Zicht\Itertools\lib;

use Zicht\Itertools;
use Zicht\Itertools\lib\Interfaces\FiniteIterableInterface;
use Zicht\Itertools\lib\Traits\FiniteIterableTrait;

class DifferenceIterator extends \FilterIterator implements FiniteIterableInterface
{
    use FiniteIterableTrait;

    /** @var \Closure */
    private $func;

    /** @var mixed[] */
    private $excludes;

    /**
     * @param \Iterator $iterable
     * @param \Iterator $excludesIterable
     * @param \Closure $func
     */
    public function __construct(\Iterator $iterable, \Iterator $excludesIterable, \Closure $func)
    {
        $this->func = $func;
        $this->excludes = Itertools\iterable($excludesIterable)->map($this->func)->values();
        parent::__construct($iterable);
    }

    /**
     * {@inheritdoc}
     */
    public function accept()
    {
        return !in_array(
            call_user_func_array($this->func, [$this->current(), $this->key()]),
            $this->excludes
        );
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

trait LastTrait
{
    /**
     * Returns the last element of this iterable or
     * returns $default when this iterable is empty
     *
     * @param mixed $default
     * @return mixed
     */
    public function last($default = null)
    {
        if ($this instanceof \Iterator) {
            $item = $default;
            foreach ($this as $item) {
            }
            return $item;
        }

        return null;
    }

    /**
     * Returns the key of the last element of this iterable or
     * returns $DEFAULT when this iterable is empty
     *
     * @param mixed $default
     * @return mixed
     */
    public function lastKey($default = null)
    {
        if ($this instanceof \Iterator) {
            $key = $default;
            foreach ($this as $key => $value) {
            }
            return $key;
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

trait FirstTrait
{
    /**
     * Returns the first element of this iterable or
     * returns $default when this iterable is empty
     *
     * @param mixed $default
     * @return mixed
     */
    public function first($default = null)
    {
        if ($this instanceof \Iterator) {
            $item = $default;
            foreach ($this as $item) {
                break;
            }
            return $item;
        }

        return null;
    }

    /**
     * Returns the key of the first element of this iterable or
     * returns $DEFAULT when this iterable is empty
     *
     * @param mixed $default
     * @return mixed
     */
    public function firstKey($default = null)
    {
        if ($this instanceof \Iterator) {
            $key = $default;
            foreach ($this as $key => $value) {
                break;
            }
            return $key;
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

// phpcs:disable Zicht.Commenting.PropertyComment.VarTypeAvoidMixed

namespace Zicht\Itertools\lib;

use Zicht\Itertools\lib\Interfaces\FiniteIterableInterface;
use Zicht\Itertools\lib\Traits\FiniteIterableTrait;

class AccumulateIterator implements FiniteIterableInterface
{
    use FiniteIterableTrait;

    /** @var \Iterator */
    protected $iterable;

    /** @var \Closure */
    protected $func;

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

    /**
     * @param \Iterator $iterable
     * @param \Closure $func
     */
    public function __construct(\Iterator $iterable, \Closure $func)
    {
        $this->iterable = $iterable;
        $this->func = $func;
        $this->value = null;
    }

    /**
     * {@inheritdoc}
     */
    public function rewind()
    {
        $this->iterable->rewind();
        $this->value = $this->iterable->valid() ? $this->iterable->current() : null;
    }

    /**
     * {@inheritdoc}
     */
    public function current()
    {
        return $this->value;
    }

    /**
     * {@inheritdoc}
     */
    public function key()
    {
        return $this->iterable->key();
    }

    /**
     * {@inheritdoc}
     */
    public function next()
    {
        $this->iterable->next();
        if ($this->iterable->valid()) {
            // must assign $this->func to $func before calling the closure
            // because otherwise it will try fo find a method called func,
            // which doesn't exist
            $func = $this->func;
            $this->value = $func($this->value, $this->iterable->current());
        }
    }

    /**
     * {@inheritdoc}
     */
    public function valid()
    {
        return $this->iterable->valid();
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\lib\SliceIterator;

trait SliceTrait
{
    /**
     * TODO: document!
     *
     * @param int $start
     * @param null|int $end
     * @return SliceIterator
     */
    public function slice($start, $end = null)
    {
        if (!is_int($start)) {
            throw new \InvalidArgumentException('Argument $start must be an integer');
        }

        if (!(is_null($end) || is_int($end))) {
            throw new \InvalidArgumentException('Argument $end must be an integer or null');
        }

        if ($this instanceof \Iterator) {
            return new SliceIterator($this, $start, $end);
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

// phpcs:disable Zicht.Commenting.FunctionComment.ExtraParamComment

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\lib\ZipIterator;

trait ZipTrait
{
    /**
     * Returns an iterator where one or more iterables are zipped together
     *
     * This function returns a list of tuples, where the i-th tuple contains
     * the i-th element from each of the argument sequences or iterables.
     *
     * The returned list is truncated in length to the length of the
     * shortest argument sequence.
     *
     * > zip([1, 2, 3], ['a', 'b', 'c'])
     * [1, 'a'] [2, 'b'] [3, 'c']
     *
     * @param array|string|\Iterator $iterable
     * @param array|string|\Iterator $iterable2
     * @return ZipIterator
     */
    public function zip(/* $iterable, $iterable2, ... */)
    {
        if ($this instanceof \Iterator) {
            $iterables = array_map(
                '\Zicht\Itertools\conversions\mixed_to_iterator',
                func_get_args()
            );
            $reflectorClass = new \ReflectionClass('\Zicht\Itertools\lib\ZipIterator');
            return $reflectorClass->newInstanceArgs(array_merge([$this], $iterables));
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

// phpcs:disable Zicht.Commenting.FunctionComment.ExtraParamComment

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\conversions;
use Zicht\Itertools\lib\MapIterator;

trait MapTrait
{
    /**
     * Make an iterator that applies $strategy to every entry in this iterable
     *
     * If additional iterables are passed, $strategy is called for each entry
     * in the $iterable, where the first argument is the value and the
     * second argument is the key of the entry
     *
     * If additional iterabvles are passed, $strategy is called with the
     * values and the keys from the iterables.  For example, the first
     * call to $strategy will be:
     * $strategy($value_iterable1, $value_iterable2, $key_iterable2, $key_iterable2)
     *
     * With multiple iterables, the iterator stops when the shortest
     * iterable is exhausted.
     *
     * > $minimal = function ($value) { return min(3, $value); };
     * > iter\iterable([1, 2, 3, 4])->map($minimal);
     * 3 3 3 4
     *
     * > $average = function ($value1, $value2) { return ($value1 + $value2) / 2; };
     * > iter\iterable([1, 2, 3])->map($average, [4, 5, 6]);
     * 2.5 3.5 4.5
     *
     * @param null|string|\Closure $strategy
     * @param array|string|\Iterator $iterable
     * @param array|string|\Iterator $iterable2
     * @return MapIterator
     */
    public function map($strategy /*, $iterable, $iterable2, ... */)
    {
        if ($this instanceof \Iterator) {
            if (func_num_args() > 1) {
                $iterables = array_map(
                    '\Zicht\Itertools\conversions\mixed_to_iterator',
                    array_slice(func_get_args(), 1)
                );
            } else {
                $iterables = [];
            }
            $reflectorClass = new \ReflectionClass('\Zicht\Itertools\lib\MapIterator');
            return $reflectorClass->newInstanceArgs(
                array_merge([conversions\mixed_to_value_getter($strategy), $this], $iterables)
            );
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\conversions;
use Zicht\Itertools\lib\UniqueIterator;

trait UniqueTrait
{
    /**
     * Returns an iterator where the values from $strategy are unique
     *
     * The $strategy is used to get values for every element in this iterable,
     * when this value has already been encountered the element is not
     * part of the returned iterator
     *
     * > iter\iterable([1, 1, 2, 2, 3, 3])->unique()
     * 1 2 3
     *
     * > iter\iterable([['id' => 1, 'value' => 'a'], ['id' => 1, 'value' => 'b']])->unique('id')
     * ['id' => 1, 'value' => 'a']  # one element in this list
     *
     * @param null|string|\Closure $strategy
     * @return UniqueIterator
     */
    public function unique($strategy = null)
    {
        if ($this instanceof \Iterator) {
            return new UniqueIterator(
                conversions\mixed_to_value_getter($strategy),
                $this
            );
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\lib\Interfaces\FiniteIterableInterface;

trait ValuesTrait
{
    /**
     * Returns an array with values from this iterator
     *
     * @return array
     */
    public function values()
    {
        $values = [];
        if ($this instanceof \Traversable) {
            foreach ($this as $key => $value) {
                $values [] = $value instanceof FiniteIterableInterface ? $value->values() : $value;
            }
        }
        return $values;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools;

trait ReduceTrait
{
    /**
     * Reduce an iterator to a single value
     *
     * > iter\iterable([1,2,3])->reduce()
     * 6
     *
     * > iter\iterable([1,2,3])->reduce('max')
     * 3
     *
     * > iter\iterable([1,2,3])->reduce('sub', 10)
     * 4
     *
     * > iter\iterable([])->reduce('min', 1)
     * 1
     *
     * @param string|\Closure $closure
     * @param mixed $initializer
     * @return mixed
     */
    public function reduce($closure = 'add', $initializer = null)
    {
        if ($this instanceof \Iterator) {
            $closure = $closure instanceof \Closure ? $closure : Itertools\reductions\get_reduction($closure);
            $this->rewind();

            if (null === $initializer) {
                if ($this->valid()) {
                    $initializer = $this->current();
                    $this->next();
                }
            }

            $accumulatedValue = $initializer;
            while ($this->valid()) {
                $accumulatedValue = $closure($accumulatedValue, $this->current());
                $this->next();
            }

            return $accumulatedValue;
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\conversions;
use Zicht\Itertools\lib\IntersectionIterator;

trait IntersectionTrait
{
    /**
     * Returns an IntersectionIterator containing elements in $this that are also in $iterable
     *
     * @param array|string|\Iterator $iterable
     * @param null|string|\Closure $strategy Optional
     * @return null|IntersectionIterator
     */
    public function intersection($iterable, $strategy = null)
    {
        if ($this instanceof \Iterator) {
            return new IntersectionIterator(
                $this,
                conversions\mixed_to_iterator($iterable),
                conversions\mixed_to_value_getter($strategy)
            );
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

// phpcs:disable Zicht.Commenting.PropertyComment.VarTypeAvoidMixed

namespace Zicht\Itertools\lib\Containers;

class KeyValuePair implements \ArrayAccess
{
    /** @var mixed */
    public $key;

    /** @var mixed */
    public $value;

    /**
     * Pair constructor.
     *
     * @param mixed $key
     * @param mixed $value
     */
    public function __construct($key = null, $value = null)
    {
        $this->key = $key;
        $this->value = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function offsetExists($offset)
    {
        return in_array($offset, [0, 1, 'key', 'value']);
    }

    /**
     * {@inheritdoc}
     */
    public function offsetGet($offset)
    {
        if ($offset === 0 || $offset === 'key') {
            return $this->key;
        }

        if ($offset === 1 || $offset === 'value') {
            return $this->value;
        }

        throw new \InvalidArgumentException('$OFFSET must be either 0, 1, "key", or "value"');
    }

    /**
     * {@inheritdoc}
     */
    public function offsetSet($offset, $value)
    {
        if ($offset === 0 || $offset === 'key') {
            $this->key = $value;
            return;
        }

        if ($offset === 1 || $offset === 'value') {
            $this->value = $value;
            return;
        }

        throw new \InvalidArgumentException('$OFFSET must be either 0, 1, "key", or "value"');
    }

    /**
     * {@inheritdoc}
     */
    public function offsetUnset($offset)
    {
        return $this->offsetSet($offset, null);
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\conversions;
use Zicht\Itertools\lib\MapByIterator;

trait MapByTrait
{
    /**
     * Make an iterator returning values from  this iterable and keys
     * from $strategy.
     *
     * When $strategy is a string, the key is obtained through one of
     * the following:
     * 1. $value->{$strategy}, when $value is an object and
     *    $strategy is an existing property,
     * 2. call $value->{$strategy}(), when $value is an object and
     *    $strategy is an existing method,
     * 3. $value[$strategy], when $value is an array and $strategy
     *    is an existing key,
     * 4. otherwise the key will default to null.
     *
     * Alternatively $strategy can be a closure.  In this case the
     * $strategy closure is called with each value in $iterable and the
     * key will be its return value.
     *
     * > $list = [['id'=>1, 'title'=>'one'], ['id'=>2, 'title'=>'two']]
     * > iter\iterable($list)->mapBy('id')
     * 1=>['id'=>1, 'title'=>'one'] 2=>['id'=>2, 'title'=>'two']
     *
     * @param null|string|\Closure $strategy
     * @return MapByIterator
     */
    public function mapBy($strategy)
    {
        if ($this instanceof \Iterator) {
            return new MapByIterator(
                conversions\mixed_to_value_getter($strategy),
                $this
            );
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib;

use Zicht\Itertools\lib\Interfaces\FiniteIterableInterface;
use Zicht\Itertools\lib\Traits\FiniteIterableTrait;

class MapIterator extends \MultipleIterator implements FiniteIterableInterface
{
    use FiniteIterableTrait;

    /** @var \Closure */
    private $valueFunc;

    /** @var \Closure */
    private $keyFunc;

    /**
     * @param \Closure $valueFunc
     */
    public function __construct(\Closure $valueFunc /* [\Closure $keyFunc], \Iterator $iterable1, [\Iterator $iterable2, [...]] */)
    {
        parent::__construct(\MultipleIterator::MIT_NEED_ALL | \MultipleIterator::MIT_KEYS_NUMERIC);
        $args = func_get_args();
        $argsContainsKeyFunc = $args[1] instanceof \Closure;
        $this->valueFunc = $args[0];

        if ($argsContainsKeyFunc) {
            $this->keyFunc = $args[1];
        } else {
            $this->keyFunc = function () {
                return $this->genericKeysToKey(func_get_args());
            };
        }

        foreach (array_slice($args, $argsContainsKeyFunc ? 2 : 1) as $iterable) {
            if (!$iterable instanceof \Iterator) {
                throw new \InvalidArgumentException(sprintf('Not all arguments are iterators'));
            }
            $this->attachIterator($iterable);
        }
    }

    /**
     * Compute the key for an element
     *
     * When multiple iterables are given, the key for an element is computed by taking
     * the keys of the element of all iterables, when they are the same, this key is used,
     * otherwise a compound key is created by concatenating the keys together.
     *
     * @param array $keysAndValues
     * @return mixed
     */
    protected function genericKeysToKey($keysAndValues)
    {
        $keys = array_splice($keysAndValues, 0, count($keysAndValues) / 2);

        if (count($keys) == 1) {
            return $keys[0];
        }

        $value = $keys[0];
        foreach ($keys as $key) {
            if ($key !== $value) {
                // the keys are different, we will make a new string identifying this entry
                return join(
                    ':',
                    array_map(
                        function ($key) {
                            return (string)$key;
                        },
                        $keys
                    )
                );
            }
        }

        // all values are the same, use it
        return $value;
    }

    /**
     * {@inheritdoc}
     */
    public function current()
    {
        return call_user_func_array($this->valueFunc, array_merge(parent::current(), parent::key()));
    }

    /**
     * {@inheritdoc}
     */
    public function key()
    {
        return call_user_func_array($this->keyFunc, array_merge(parent::key(), parent::current()));
    }

    /**
     * {@inheritdoc}
     */
    public function next()
    {
        parent::next();
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\lib\Traits;

use Zicht\Itertools\conversions;
use Zicht\Itertools\lib\GroupbyIterator;

trait GroupByTrait
{
    /**
     * Make an iterator that returns consecutive groups from this
     * iterable.  Generally, this iterable needs to already be sorted on
     * the same key function.
     *
     * When $strategy is a string, the key is obtained through one of
     * the following:
     * 1. $value->{$strategy}, when $value is an object and
     *    $strategy is an existing property,
     * 2. call $value->{$strategy}(), when $value is an object and
     *    $strategy is an existing method,
     * 3. $value[$strategy], when $value is an array and $strategy
     *    is an existing key,
     * 4. otherwise the key will default to null.
     *
     * Alternatively $strategy can be a closure.  In this case the
     * $strategy closure is called with each value in this iterable and the
     * key will be its return value.  $strategy is called with two
     * parameters: the value and the key of the iterable as the first and
     * second parameter, respectively.
     *
     * The operation of groupBy() is similar to the uniq filter in Unix.
     * It generates a break or new group every time the value of the key
     * function changes (which is why it is usually necessary to have
     * sorted the data using the same key function).  That behavior
     * differs from SQL's GROUP BY which aggregates common elements
     * regardless of their input order.
     *
     * > $list = [['type'=>'A', 'title'=>'one'], ['type'=>'A', 'title'=>'two'], ['type'=>'B', 'title'=>'three']]
     * > iter\iterable($list)->groupBy('type')
     * 'A'=>[['type'=>'A', 'title'=>'one'], ['type'=>'A', 'title'=>'two']] 'B'=>[['type'=>'B', 'title'=>'three']]
     *
     * @param null|string|\Closure $strategy
     * @param bool $sort
     * @return GroupbyIterator
     */
    public function groupBy($strategy, $sort = true)
    {
        if (!is_bool($sort)) {
            throw new \InvalidArgumentException('Argument $sort must be a boolean');
        }

        if ($this instanceof \Iterator) {
            return new GroupbyIterator(
                conversions\mixed_to_value_getter($strategy),
                $sort ? $this->sorted($strategy) : $this
            );
        }

        return null;
    }
}


<?php
/**
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Itertools\twig;

use Zicht\Itertools;

/**
 * Twig extension.
 */
class Extension extends \Twig_Extension
{
    /**
     * {@inheritdoc}
     */
    public function getFilters()
    {
        return [
            // filter names are case-sensitive
            new \Twig_SimpleFilter('all', '\Zicht\Itertools\all'),
            new \Twig_SimpleFilter('any', '\Zicht\Itertools\any'),
            new \Twig_SimpleFilter('chain', '\Zicht\Itertools\chain'),
            new \Twig_SimpleFilter('collapse', '\Zicht\Itertools\collapse'),
            new \Twig_SimpleFilter('filter', [$this, 'filter']),
            new \Twig_SimpleFilter('first', '\Zicht\Itertools\first'),
            new \Twig_SimpleFilter('group_by', [$this, 'groupBy']),
            new \Twig_SimpleFilter('last', '\Zicht\Itertools\last'),
            new \Twig_SimpleFilter('map', [$this, 'map']),
            new \Twig_SimpleFilter('map_by', [$this, 'mapBy']),
            new \Twig_SimpleFilter('reduce', '\Zicht\Itertools\reduce'),
            new \Twig_SimpleFilter('reversed', '\Zicht\Itertools\reversed'),
            new \Twig_SimpleFilter('sorted', [$this, 'sorted']),
            new \Twig_SimpleFilter('unique', [$this, 'unique']),
            new \Twig_SimpleFilter('zip', '\Zicht\Itertools\zip'),

            // deprecated filters
            new \Twig_SimpleFilter('filterby', [$this, 'deprecatedFilterBy'], ['deprecated' => true, 'alternative' => 'filter']),
            new \Twig_SimpleFilter('groupBy', [$this, 'deprecatedGroupBy'], ['deprecated' => true, 'alternative' => 'group_by']),
            new \Twig_SimpleFilter('groupby', [$this, 'deprecatedGroupBy'], ['deprecated' => true, 'alternative' => 'group_by']),
            new \Twig_SimpleFilter('mapBy', [$this, 'deprecatedMapBy'], ['deprecated' => true, 'alternative' => 'map_by']),
            new \Twig_SimpleFilter('mapby', [$this, 'deprecatedMapBy'], ['deprecated' => true, 'alternative' => 'map_by']),
            new \Twig_SimpleFilter('sum', [$this, 'deprecatedSum'], ['deprecated' => true, 'alternative' => 'reduce']),
            new \Twig_SimpleFilter('uniqueby', [$this, 'deprecatedUniqueBy'], ['deprecated' => true, 'alternative' => 'unique']),
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getFunctions()
    {
        return [
            new \Twig_SimpleFunction('chain', '\Zicht\Itertools\chain'),
            new \Twig_SimpleFunction('first', '\Zicht\Itertools\first'),
            new \Twig_SimpleFunction('last', '\Zicht\Itertools\last'),

            // functions to create closures
            new \Twig_SimpleFunction('reducing', [$this, 'reducing']),
            new \Twig_SimpleFunction('mapping', [$this, 'mapping']),
            new \Twig_SimpleFunction('filtering', [$this, 'filtering']),

            // deprecated functions
            new \Twig_SimpleFunction('reduction', [$this, 'deprecatedGetReduction'], ['deprecated' => true, 'alternative' => 'reducing']),
        ];
    }

    /**
     * Takes an iterable and returns another iterable that is unique.
     *
     * @param array|string|\Iterator $iterable
     * @param mixed $strategy
     * @return Itertools\lib\UniqueIterator
     */
    public function unique($iterable, $strategy = null)
    {
        return Itertools\unique($strategy, $iterable);
    }

    /**
     * Reduce an iterable to a single value
     *
     * Simple examples:
     * {{ [1,2,3]|reduce }} --> 6
     * {{ [1,2,3]|reduce('max') }} --> 3
     *
     * Sro example to get the prices for all items in the basket:
     * {{ transaction_snapshot.Basket.Items|map('TotalPrice.Amount')|reduce }}
     *
     * @param array|string|\Iterator $iterable
     * @param string|\Closure $closure
     * @param mixed $initializer
     * @return mixed
     */
    public function reduce($iterable, $closure = 'add', $initializer = null)
    {
        return Itertools\reduce($iterable, $closure, $initializer);
    }

    /**
     * Make an iterator that returns consecutive groups from the
     * $iterable.  Generally, the $iterable needs to already be sorted on
     * the same key function.
     *
     * @param array|string|\Iterator $iterable
     * @param string|\Closure $strategy
     * @param boolean $sort
     * @return Itertools\lib\GroupbyIterator
     */
    public function groupBy($iterable, $strategy, $sort = true)
    {
        return Itertools\group_by($strategy, $iterable, $sort);
    }

    /**
     * Make an iterator that returns values from $iterable where the
     * $strategy determines that the values are not empty.
     *
     * @param array|string|\Iterator $iterable
     * @param null $strategy
     * @return Itertools\lib\FilterIterator
     */
    public function filter($iterable, $strategy = null)
    {
        return Itertools\filter($strategy, $iterable);
    }

    /**
     * Make an iterator that returns the values from $iterable sorted by
     * $strategy.
     *
     * @param array|string|\Iterator $iterable
     * @param string|\Closure $strategy
     * @param bool $reverse
     * @return Itertools\lib\SortedIterator
     */
    public function sorted($iterable, $strategy = null, $reverse = false)
    {
        return Itertools\sorted($strategy, $iterable, $reverse);
    }

    /**
     * Make an iterator that applies $func to every entry in the $iterables.
     *
     * @param array|string|\Iterator $iterable
     * @param string|\Closure $strategy
     * @return Itertools\lib\MapIterator
     */
    public function map($iterable, $strategy)
    {
        return Itertools\map($strategy, $iterable);
    }

    /**
     * Make an iterator returning values from $iterable and keys from
     * $strategy.
     *
     * @param array|string|\Iterator $iterable
     * @param string|\Closure $strategy
     * @return Itertools\lib\MapByIterator
     */
    public function mapBy($iterable, $strategy)
    {
        return Itertools\map_by($strategy, $iterable);
    }

    /**
     * Returns a reduction closure
     *
     * Any parameters provided, beyond $name, are passed directly to the underlying
     * reduction.  This can be used to, for example, provide a $glue when using join.
     *
     * @param string $name
     * @return \Closure
     * @throws \InvalidArgumentException
     */
    public function reducing($name)
    {
        // note, once we stop supporting php 5.5, we can rewrite the code below
        // to the reducing($name, ...$args) structure.
        // http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list

        if (is_string($name) &&
            in_array(
                $name,
                [
                    'add',
                    'chain',
                    'join',
                    'max',
                    'min',
                    'mul',
                    'sub',
                ]
            )) {
            return call_user_func_array(sprintf('\Zicht\Itertools\reductions\%s', $name), array_slice(func_get_args(), 1));
        }

        throw new \InvalidArgumentException(sprintf('$NAME "%s" is not a valid reduction.', $name));
    }

    /**
     * Returns a mapping closure
     *
     * Any parameters provided, beyond $name, are passed directly to the underlying
     * mapping.  This can be used to, for example, provide a $glue when using join.
     *
     * @param string $name
     * @return \Closure
     * @throws \InvalidArgumentException
     */
    public function mapping($name)
    {
        // note, once we stop supporting php 5.5, we can rewrite the code below
        // to the reducing($name, ...$args) structure.
        // http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list

        if (is_string($name) &&
            in_array(
                $name,
                [
                    'json_decode',
                    'json_encode',
                    'key',
                    'length',
                    'lower',
                    'lstrip',
                    'random',
                    'rstrip',
                    'select',
                    'strip',
                    'type',
                    'upper',
                ]
            )) {
            return call_user_func_array(sprintf('\Zicht\Itertools\mappings\%s', $name), array_slice(func_get_args(), 1));
        }

        throw new \InvalidArgumentException(sprintf('$NAME "%s" is not a valid mapping.', $name));
    }

    /**
     * Returns a filter closure
     *
     * Any parameters provided, beyond $name, are passed directly to the underlying
     * filter.  This can be used to, for example, provide a $glue when using join.
     *
     * @param string $name
     * @return \Closure
     * @throws \InvalidArgumentException
     */
    public function filtering($name)
    {
        // note, once we stop supporting php 5.5, we can rewrite the code below
        // to the reducing($name, ...$args) structure.
        // http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list

        if (is_string($name) &&
            in_array(
                $name,
                [
                    'equals',
                    'in',
                    'match',
                    'not',
                    'not_in',
                    'type',
                ]
            )) {
            return call_user_func_array(sprintf('\Zicht\Itertools\filters\%s', $name), array_slice(func_get_args(), 1));
        }

        throw new \InvalidArgumentException(sprintf('$NAME "%s" is not a valid filter.', $name));
    }

    /**
     * Make an iterator that returns values from $iterable where the
     * $strategy determines that the values are not empty.
     *
     * @param array|string|\Iterator $iterable
     * @param string|\Closure $strategy
     * @return Itertools\lib\FilterIterator
     *
     * @deprecated Use filter instead!
     */
    public function deprecatedFilterBy($iterable, $strategy)
    {
        return Itertools\filter($strategy, $iterable);
    }

    /**
     * Make an iterator that returns consecutive groups from the
     * $iterable.  Generally, the $iterable needs to already be sorted on
     * the same key function.
     *
     * @param array|string|\Iterator $iterable
     * @param string|\Closure $strategy
     * @return Itertools\lib\GroupbyIterator
     *
     * @deprecated Use group_by instead!
     */
    public function deprecatedGroupBy($iterable, $strategy)
    {
        return Itertools\group_by($strategy, $iterable);
    }

    /**
     * Make an iterator returning values from $iterable and keys from
     * $strategy.
     *
     * @param array|string|\Iterator $iterable
     * @param string|\Closure $strategy
     * @return Itertools\lib\MapByIterator
     *
     * @deprecated Use map_by instead!
     */
    public function deprecatedMapBy($iterable, $strategy)
    {
        return Itertools\mapBy($strategy, $iterable);
    }

    /**
     * Create a reduction
     *
     * @param array|string|\Iterator $iterable
     * @param int $default
     * @return int
     *
     * @deprecated Use reduce instead!
     */
    public function deprecatedSum($iterable, $default = 0)
    {
        $result = $default;
        foreach (Itertools\accumulate($iterable) as $result) {
        };
        return $result;
    }

    /**
     * Takes an iterable and returns another iterable that is unique.
     *
     * @param array|string|\Iterator $iterable
     * @param mixed $strategy
     * @return Itertools\lib\UniqueIterator
     *
     * @deprecated Use unique instead!
     */
    public function deprecatedUniqueBy($iterable, $strategy = null)
    {
        return Itertools\unique($strategy, $iterable);
    }

    /**
     * Returns a reduction closure
     *
     * @param string $name
     * @return \Closure
     * @throws \InvalidArgumentException
     *
     * @deprecated Use reducing instead!
     */
    public function deprecatedGetReduction($name)
    {
        return call_user_func_array([$this, 'reducing'], func_get_args());
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'zicht_itertools';
    }
}


<?php

namespace WP_CLI\AutoloadSplitter;

use Composer\Composer;
use Composer\Config;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
use Composer\Script\Event;
use Composer\Script\ScriptEvents;

/**
 * Class ComposerPlugin.
 *
 * Composer plugin class that hooks into the Composer plugin events.
 *
 * @package WP_CLI\AutoloadSplitter
 */
final class ComposerPlugin implements PluginInterface, EventSubscriberInterface
{

    const EXTRA_KEY = 'autoload-splitter';
    const LOGIC_CLASS_KEY = 'splitter-logic';
    const LOGIC_CLASS_LOCATION_KEY = 'splitter-location';
    const SPLIT_TARGET_PREFIX_TRUE_KEY = 'split-target-prefix-true';
    const SPLIT_TARGET_PREFIX_FALSE_KEY = 'split-target-prefix-false';

    private static $extra;

    /**
     * Get the event subscriber configuration for this plugin.
     *
     * @return array<string,string> The events to listen to, and their
     *                              associated handlers.
     */
    public static function getSubscribedEvents()
    {
        return array(
            ScriptEvents::PRE_AUTOLOAD_DUMP => 'dump',
        );
    }

    /**
     * Dump the event for discovery purposes.
     *
     * @param Event $event Event that was triggered.
     */
    public static function dump(Event $event)
    {
        $composer            = $event->getComposer();
        $installationManager = $composer->getInstallationManager();
        $repoManager         = $composer->getRepositoryManager();
        $localRepo           = $repoManager->getLocalRepository();
        $package             = $composer->getPackage();
        $config              = $composer->getConfig();

        // To enable class-based filtering, we force `--optimize` flag for our
        // custom autoloaders, so that all PSR-0 and PSR-4 namespaces are
        // iterated and translated to arrays of classes.
        $optimize = true;

        $vendorDir       = $config->get('vendor-dir', Config::RELATIVE_PATHS);
        $defaultLocation = "{$vendorDir}/wp-cli/wp-cli/php/WP_CLI/AutoloadSplitter.php";
        $suffix          = $config->get('autoloader-suffix');

        self::$extra = $event->getComposer()
            ->getPackage()
            ->getExtra();

        $splitterLogic    = self::getExtraKey(self::LOGIC_CLASS_KEY, 'WP_CLI\AutoloadSplitter');
        $splitterLocation = self::getExtraKey(self::LOGIC_CLASS_LOCATION_KEY, $defaultLocation);
        $filePrefixTrue   = self::getExtraKey(self::SPLIT_TARGET_PREFIX_TRUE_KEY, 'autoload_commands');
        $filePrefixFalse  = self::getExtraKey(self::SPLIT_TARGET_PREFIX_FALSE_KEY, 'autoload_framework');

        if (!class_exists($splitterLogic)) {
            $splitterClassPath = sprintf('%s/%s', getcwd(), $splitterLocation);

            // Avoid proceeding if the splitter class file does not exist.
            if (!is_readable( $splitterClassPath)) {
                return;
            }

            include_once $splitterClassPath;
        }

        $generator = new AutoloadGenerator(
            $composer->getEventDispatcher(),
            $event->getIO(),
            $splitterLogic,
            $filePrefixTrue,
            $filePrefixFalse
        );

        $generator->dump(
            $config,
            $localRepo,
            $package,
            $installationManager,
            'composer',
            $optimize,
            $suffix
        );
    }

    /**
     * Activate the Composer plugin.
     *
     * @param Composer    $composer Reference to the Composer instance.
     * @param IOInterface $io       Reference to the IO interface.
     */
    public function activate(Composer $composer, IOInterface $io)
    {
    }

    /**
     * Get the value of a config key from the "Extra" section.
     *
     * @param string $key      Key to look for.
     * @param string $fallback Fallback value to use if the key was not provided.
     *
     * @return string
     */
    private static function getExtraKey($key, $fallback)
    {
        static $autosplitter = null;

        if (null === $autosplitter) {
            $autosplitter = isset(self::$extra[self::EXTRA_KEY])
                ? self::$extra[self::EXTRA_KEY]
                : array();
        }

        return isset($autosplitter[$key]) ? $autosplitter[$key] : $fallback;
    }
}


<?php
/**
 * Matomo - free/libre analytics platform
 *
 * @link https://matomo.org
 * @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
 *
 */
namespace Matomo\Cache\Backend;

use Doctrine\Common\Cache\PhpFileCache;
use Matomo\Cache\Backend;

/**
 * This class is used to cache data on the filesystem.
 *
 * This cache creates one file per id. Every time you try to read the value it will load the cache file again. It will
 * try to invalidate the Opcache for a specific cache file if needed.
 */
class File extends PhpFileCache implements Backend
{
    // for testing purposes since tests run on both CLI/FPM (changes in CLI can't invalidate
    // opcache in FPM, so we have to invalidate before reading)
    public static $invalidateOpCacheBeforeRead = false;

    /**
     * Constructor.
     *
     * @param string      $directory The cache directory.
     * @param string|null $extension The cache file extension.
     *
     * @throws \InvalidArgumentException
     */
    public function __construct($directory, $extension = '.php')
    {
        if (!is_dir($directory)) {
            $this->createDirectory($directory);
        }

        parent::__construct($directory, $extension);
    }

    public function doFetch($id)
    {
        if (self::$invalidateOpCacheBeforeRead) {
            $this->invalidateCacheFile($id);
        }

        return parent::doFetch($id);
    }

    public function doContains($id)
    {
        return parent::doContains($id);
    }

    public function doSave($id, $data, $lifeTime = 0)
    {
        if (!is_dir($this->directory)) {
            $this->createDirectory($this->directory);
        }

        $success = parent::doSave($id, $data, $lifeTime);

        if ($success) {
            $this->invalidateCacheFile($id);
        }

        return $success;
    }

    public function doDelete($id)
    {
        $this->invalidateCacheFile($id);

        $success = parent::doDelete($id);

        $this->invalidateCacheFile($id); // in case file was cached by another request between invalidate and doDelete()

        return $success;
    }

    public function doFlush()
    {
        // if the directory does not exist, do not bother to continue clearing
        if (!is_dir($this->directory)) {
            return false;
        }

        foreach ($this->getFileIterator() as $name => $file) {
            $this->opCacheInvalidate($name);
        }

        return parent::doFlush();
    }

    private function invalidateCacheFile($id)
    {
        $filename = $this->getFilename($id);
        $this->opCacheInvalidate($filename);
    }

    /**
     * @param string $id
     *
     * @return string
     */
    public function getFilename($id)
    {
        $path = $this->directory . DIRECTORY_SEPARATOR;
        $id   = preg_replace('@[\\\/:"*?<>|]+@', '', $id);

        return $path . $id . $this->getExtension();
    }

    private function opCacheInvalidate($filepath)
    {
        if (is_file($filepath)) {
            if (function_exists('opcache_invalidate')) {
                @opcache_invalidate($filepath, $force = true);
            }
            if (function_exists('apc_delete_file')) {
                @apc_delete_file($filepath);
            }
        }
    }

    /**
     * @return \Iterator
     */
    private function getFileIterator()
    {
        $pattern = '/^.+\\' . $this->getExtension() . '$/i';
        $iterator = new \RecursiveDirectoryIterator($this->directory);
        $iterator = new \RecursiveIteratorIterator($iterator);
        return new \RegexIterator($iterator, $pattern);
    }

    private function createDirectory($path)
    {
        if (!is_dir($path)) {
            // the mode in mkdir is modified by the current umask
            @mkdir($path, 0750, $recursive = true);
        }

        // try to overcome restrictive umask (mis-)configuration
        if (!is_writable($path)) {
            @chmod($path, 0755);
            if (!is_writable($path)) {
                @chmod($path, 0775);
                // enough! we're not going to make the directory world-writeable
            }
        }
    }
}


<?php

namespace Drupal\Jsonld;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceModifierInterface;

/**
 * Adds JSON-LD as known format.
 */
class JsonldServiceProvider implements ServiceModifierInterface {

  /**
   * {@inheritdoc}
   */
  public function alter(ContainerBuilder $container) {

    if ($container->has('http_middleware.negotiation') && is_a(
        $container->getDefinition('http_middleware.negotiation')
          ->getClass(), '\Drupal\Core\StackMiddleware\NegotiationMiddleware', TRUE
    )
    ) {
      $container->getDefinition('http_middleware.negotiation')
        ->addMethodCall('registerFormat', ['jsonld', ['application/ld+json']]);
    }
  }

}


<?php

namespace Drupal\jsonld\Normalizer;

use Drupal\serialization\Normalizer\NormalizerBase as SerializationNormalizerBase;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

/**
 * Base class for Normalizers.
 */
abstract class NormalizerBase extends SerializationNormalizerBase implements DenormalizerInterface {

  /**
   * The formats that the Normalizer can handle.
   *
   * @var array
   */
  protected $formats = ['jsonld'];

  /**
   * {@inheritdoc}
   */
  public function supportsNormalization($data, $format = NULL) {

    return in_array($format, $this->formats) && parent::supportsNormalization($data, $format);
  }

  /**
   * {@inheritdoc}
   */
  public function supportsDenormalization($data, $type, $format = NULL) {

    if (in_array($format, $this->formats) && (class_exists($this->supportedInterfaceOrClass) || interface_exists($this->supportedInterfaceOrClass))) {
      $target = new \ReflectionClass($type);
      $supported = new \ReflectionClass($this->supportedInterfaceOrClass);
      if ($supported->isInterface()) {
        return $target->implementsInterface($this->supportedInterfaceOrClass);
      }
      else {
        return ($target->getName() == $this->supportedInterfaceOrClass || $target->isSubclassOf($this->supportedInterfaceOrClass));
      }
    }

    return FALSE;
  }

  /**
   * Escapes namespace prefixes in predicates.
   *
   * @param string $predicate
   *   The predicate whose namespace you wish to escape.
   * @param array $namespaces
   *   Associative array of namespaces keyed by prefix.
   *
   * @return string
   *   The predicate with escaped namespace prefix.
   */
  public static function escapePrefix($predicate, array $namespaces) {

    $exploded = explode(":", $predicate);
    if (!isset($namespaces[$exploded[0]])) {
      return $predicate;
    }
    return $namespaces[$exploded[0]] . $exploded[1];
  }

}


<?php
/**
 * Matomo - free/libre analytics platform
 *
 * @link https://matomo.org
 * @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
 *
 */
namespace Matomo\Cache;

use Matomo\Cache\Backend;

/**
 * This class is used to cache data during one request.
 *
 * Compared to the lazy cache it does not support setting any lifetime. To be a fast cache it does
 * not validate any cache id etc.
 */
class Transient implements Cache
{
    /**
     * @var array $data
     */
    private $data = array();

    /**
     * Fetches an entry from the cache.
     *
     * Make sure to call the method {@link has()} to verify whether there is actually any content set under this
     * cache id.
     *
     * @param string $id The cache id.
     * @return mixed
     */
    public function fetch($id)
    {
        if ($this->contains($id)) {
            return $this->data[$id];
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function contains($id)
    {
        return isset($this->data[$id]) || array_key_exists($id, $this->data);
    }

    /**
     * Puts data into the cache.
     *
     * @param string $id The cache id.
     * @param mixed $content
     * @param int $lifeTime Setting a lifetime is not supported by this cache and the parameter will be ignored.
     * @return boolean
     */
    public function save($id, $content, $lifeTime = 0)
    {
        $this->data[$id] = $content;

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function delete($id)
    {
        if (!$this->contains($id)) {
            return false;
        }

        unset($this->data[$id]);
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function flushAll()
    {
        $this->data = array();

        return true;
    }
}


<?php

namespace Drupal\jsonld\Normalizer;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\hal\LinkManager\LinkManagerInterface;
use GuzzleHttp\ClientInterface;

/**
 * Converts the Drupal entity object structure to a JSON-LD array structure.
 */
class FileEntityNormalizer extends ContentEntityNormalizer {

  /**
   * The interface or class that this Normalizer supports.
   *
   * @var string
   */
  protected $supportedInterfaceOrClass = 'Drupal\file\FileInterface';

  /**
   * The HTTP client.
   *
   * @var \GuzzleHttp\ClientInterface
   */
  protected $httpClient;

  /**
   * The Drupal file system.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * Constructs a FileEntityNormalizer object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager
   *   The entity manager.
   * @param \GuzzleHttp\ClientInterface $http_client
   *   The HTTP Client.
   * @param \Drupal\hal\LinkManager\LinkManagerInterface $link_manager
   *   The hypermedia link manager.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Core\File\FileSystemInterface $file_system
   *   The file system handler.
   */
  public function __construct(EntityTypeManagerInterface $entity_manager,
                              ClientInterface $http_client,
                              LinkManagerInterface $link_manager,
                              ModuleHandlerInterface $module_handler,
                              FileSystemInterface $file_system) {

    parent::__construct($link_manager, $entity_manager, $module_handler);

    $this->httpClient = $http_client;
    $this->fileSystem = $file_system;
  }

  /**
   * {@inheritdoc}
   */
  public function normalize($entity, $format = NULL, array $context = []) {

    $data = parent::normalize($entity, $format, $context);
    // Replace the file url with a full url for the file.
    $data['uri'][0]['value'] = $this->getEntityUri($entity);

    return $data;
  }

  /**
   * {@inheritdoc}
   */
  public function denormalize($data, $class, $format = NULL, array $context = []) {

    $file_data = (string) $this->httpClient->get($data['uri'][0]['value'])->getBody();

    $path = 'temporary://' . $this->fileSystem->basename($data['uri'][0]['value']);
    $data['uri'] = file_unmanaged_save_data($file_data, $path);

    return $this->entityManager->getStorage('file')->create($data);
  }

}