<?php
/**
 * Lord of the Files - Debug tool.
 *
 * Gather data on a file upload and environment.
 *
 * @package blob-mimes
 * @author  Blobfolio, LLC <hello@blobfolio.com>
 */

namespace blobfolio\wp\bm;

class debug {

	protected $errors = array();
	protected $tests = array();
	protected $file;
	protected $filename;

	protected static $wordpress;
	protected static $php;
	protected static $mimes;

	const LINE_LENGTH = 70;

	const ASCII_VALIDATION = '
__   __ _    _     ___  ___    _  _____  ___  ___   _  _
\ \ / //_\  | |   |_ _||   \  /_\|_   _||_ _|/ _ \ | \| |
 \ V // _ \ | |__  | | | |) |/ _ \ | |   | || (_) || .` |
  \_//_/ \_\|____||___||___//_/ \_\|_|  |___|\___/ |_|\_|';
	const ASCII_SYSTEM = '
 ___ __   __ ___  _____  ___  __  __
/ __|\ \ / // __||_   _|| __||  \/  |
\__ \ \ V / \__ \  | |  | _| | |\/| |
|___/  |_|  |___/  |_|  |___||_|  |_|';

	// ---------------------------------------------------------------------
	// Init
	// ---------------------------------------------------------------------

	/**
	 * Load and test a file!
	 *
	 * @param string $file File path.
	 * @param string $filename File name.
	 * @return bool True/false.
	 */
	public function __construct($file, $filename=null) {
		$this->errors = array();
		$this->tests = array();

		// Make sure the file is readable.
		try {
			if (!is_string($file) || !$file || !@is_file($file)) {
				$this->errors[] = __('The file could not be read.', 'blob-mimes');
				return false;
			}
		} catch (\Throwable $e) {
			$this->errors[] = __('The file could not be read.', 'blob-mimes');
			return false;
		} catch (\Exception $e) {
			$this->errors[] = __('The file could not be read.', 'blob-mimes');
			return false;
		}

		// Make sure we have a name.
		if (!is_string($filename) || !$filename) {
			$filename = basename($file);
		}

		$this->file = $file;
		$this->filename = $filename;

		// Load static data.
		static::load_mimes();
		static::load_wordpress();
		static::load_php();

		// Run tests.
		$this->test_wp_check_filetype();
		$this->test_fileinfo();
		$this->test_self();

		return !count($this->errors);
	}

	// --------------------------------------------------------------------- end init

	// ---------------------------------------------------------------------
	// Tests
	// ---------------------------------------------------------------------

	/**
	 * Test: WP Check Filetype
	 *
	 * @return bool True/false.
	 */
	protected function test_wp_check_filetype() {
		if (!$this->filename) {
			return false;
		}

		$info = wp_check_filetype($this->filename);
		$test = array(
			'pass'=>((false !== $info['ext']) && (false !== $info['type'])),
			'test'=>$info,
			'result'=>'',
		);

		if (!$test['pass']) {
			$test['result'] = __('[error]', 'blob-mimes') . ' ' . __('WordPress will not process this file based on its name.', 'blob-mimes');
			$this->errors[] = __('WordPress will not process this file based on its name.', 'blob-mimes');
		}
		else {
			$test['result'] = __('[pass]', 'blob-mimes') . ' ' . __('The file extension is allowed.', 'blob-mimes');
		}

		$this->tests['WORDPRESS'] = $test;
		return $test['pass'];
	}

	/**
	 * Test: fileinfo
	 *
	 * @return bool True/false.
	 */
	protected function test_fileinfo() {
		if (!$this->file) {
			return false;
		}

		$test = array(
			'pass'=>false,
			'test'=>array(
				'type'=>false,
				'match_alias'=>false,
				'allowed_explicit'=>false,
				'allowed_alias'=>false,
			),
			'result'=>'',
		);

		// Test if we can.
		if (static::has_fileinfo()) {
			$finfo = finfo_open(FILEINFO_MIME_TYPE);
			$test['test']['type'] = finfo_file($finfo, $this->file);

			// Couldn't get any info.
			if (!is_string($test['test']['type']) || !$test['test']['type']) {
				$test['test']['type'] = false;
				$test['result'] = __('[warning]', 'blob-mimes') . ' ' . __('The type could not be determined.', 'blob-mimes');
			}
			// Look deeper.
			else {
				$test['test']['type'] = strtolower(sanitize_mime_type($test['test']['type']));
				$ext = pathinfo($this->filename, PATHINFO_EXTENSION);

				$test['test']['match_alias'] = mime::check_alias($ext, $test['test']['type']);
				$test['test']['allowed_explicit'] = $test['test']['allowed_alias'] = in_array($test['test']['type'], static::$mimes, true);
				if (false === $test['test']['allowed_alias']) {
					$tmp = mime::check_real_filetype($this->file, $this->filename, static::$mimes);
					$test['test']['allowed_alias'] = !!$tmp['ext'];
				}

				if ($test['test']['match_alias'] && $test['test']['allowed_explicit']) {
					$test['pass'] = true;
					$test['result'] = __('[pass]', 'blob-mimes') . ' ' . __('The file type and extension are both allowed.', 'blob-mimes');
				}
				elseif ($test['test']['match_alias'] && $test['test']['allowed_alias']) {
					$test['pass'] = true;
					$test['result'] = __('[warning]', 'blob-mimes') . ' ' . __('The file type is not explicitly whitelisted, but is an alias of a whitelisted type.', 'blob-mimes');
				}
				elseif ($test['test']['allowed_alias']) {
					$test['pass'] = true;
					$test['result'] = __('[warning]', 'blob-mimes') . ' ' . __('The file extension does not match the type, however the type is allowed.', 'blob-mimes');
				}
				else {
					$test['result'] = __('[error]', 'blob-mimes') . ' ' . __('The file type is not allowed.', 'blob-mimes');
					$this->errors[] = __('The file type is not allowed.', 'blob-mimes');
				}
			}

			finfo_close($finfo);
		}
		// Skip it.
		else {
			$test['result'] = __('[skipped]', 'blob-mimes') . ' ' . __('The `fileinfo.so` PHP extension is not installed. Detailed file information is not available.', 'blob-mimes');
		}

		$this->tests['FILEINFO'] = $test;
		return $test['pass'];
	}

	/**
	 * Test: blob-mimes
	 *
	 * @return bool True/false.
	 */
	protected function test_self() {
		if (!$this->file) {
			return false;
		}

		$test = array(
			'pass'=>false,
			'test'=>array(
				'name'=>$this->filename,
				'type'=>false,
				'ext'=>false,
				'renamed'=>false,
			),
			'result'=>'',
		);

		$info = mime::check_real_filetype($this->file, $this->filename, static::$mimes);
		$test['pass'] = ((false !== $info['type']) && (false !== $info['ext']));

		if ($test['pass']) {
			$test['test']['type'] = $info['type'];
			$test['test']['ext'] = $info['ext'];

			$ext = strtolower(pathinfo($this->filename, PATHINFO_EXTENSION));
			if ($ext !== $info['ext']) {
				$test['test']['renamed'] = true;
				$test['test']['name'] = mime::update_filename_extension($this->filename, $info['ext']);
				$test['result'] = __('[warning]', 'blob-mimes') . ' ' . __('The file extension does not match the type; it will be renamed.', 'blob-mimes');
			}
			else {
				$test['result'] = __('[pass]', 'blob-mimes') . ' ' . __('The file type and extension are both allowed.', 'blob-mimes');
			}
		}
		else {
			$test['result'] = __('[error]', 'blob-mimes') . ' ' . __('The file type is not allowed.', 'blob-mimes');
			$this->errors[] = __('The file type is not allowed.', 'blob-mimes');
		}

		$version = admin::get_version();
		$this->tests["BLOB-MIMES ($version)"] = $test;
		return $test['pass'];
	}

	// --------------------------------------------------------------------- end tests

	// ---------------------------------------------------------------------
	// Environment
	// ---------------------------------------------------------------------

	/**
	 * Supports Fileinfo?
	 *
	 * @return bool True/false.
	 */
	public static function has_fileinfo() {
		return (extension_loaded('fileinfo') && defined('FILEINFO_MIME_TYPE'));
	}

	/**
	 * Allowed MIME Types
	 *
	 * Statically cache the allowed MIME types
	 * to vaguely improve performance.
	 *
	 * @return bool True.
	 */
	protected static function load_mimes() {
		if (!is_array(static::$mimes)) {
			static::$mimes = get_allowed_mime_types();
		}

		return true;
	}

	/**
	 * WordPress Details
	 *
	 * Gather a list of installed plugins and themes.
	 *
	 * @return bool True.
	 */
	protected static function load_wordpress() {
		if (is_array(static::$wordpress)) {
			return true;
		}

		// Have to pull a bunch of data.
		require_once(trailingslashit(ABSPATH) . 'wp-admin/includes/plugin.php');
		$plugins_all = get_plugins();
		$plugins_active = get_option('active_plugins');
		$themes_all = wp_get_themes();
		$theme_active = basename(get_stylesheet_directory());

		static::$wordpress = array(
			'version'=>get_bloginfo('version'),
			'locale'=>get_locale(),
			'plugins'=>array(),
			'theme'=>'',
		);

		// Only include active plugins.
		if (is_array($plugins_active) && count($plugins_active)) {
			foreach ($plugins_all as $k=>$v) {
				if (in_array($k, $plugins_active, true)) {
					static::$wordpress['plugins'][] = "{$v['TextDomain']} [{$v['Version']}]";
				}
			}
			sort(static::$wordpress['plugins']);
		}

		// Only include the active theme.
		foreach ($themes_all as $k=>$v) {
			if ($k === $theme_active) {
				static::$wordpress['theme'] = "{$theme_active} [{$v['Version']}]";
				break;
			}
		}

		return true;
	}

	/**
	 * PHP Details
	 *
	 * Gather a list of PHP extensions.
	 *
	 * @return bool True.
	 */
	protected static function load_php() {
		if (!is_array(static::$php)) {
			static::$php = array(
				'version'=>phpversion(),
				'os'=>php_uname('a'),
				'extensions'=>get_loaded_extensions(),
			);
			sort(static::$php['extensions']);
		}

		return true;
	}

	// --------------------------------------------------------------------- end environment

	// ---------------------------------------------------------------------
	// Output
	// ---------------------------------------------------------------------

	/**
	 * Get Results!
	 *
	 * @param bool $collapsed Collapsed as string.
	 * @return mixed Results or false.
	 */
	public function get_results($collapsed=true) {
		if (!$this->file) {
			return false;
		}

		$results = array(
			'RESULT'=>'',
			'TESTS'=>$this->tests,
			'ENVIRONMENT'=>array(
				'WORDPRESS'=>static::$wordpress,
				'WHITELIST'=>array(
					'EXTENSIONS'=>array(),
					'MIME TYPES'=>array(),
				),
				'PHP'=>static::$php,
			),
		);

		// Populate the authorized file types.
		foreach (static::$mimes as $k=>$v) {
			$results['ENVIRONMENT']['WHITELIST']['MIME TYPES'][] = strtolower(sanitize_mime_type($v));
			$exts = explode('|', $k);
			foreach ($exts as $v2) {
				$v2 = trim(strtolower($v2));
				$v2 = ltrim($v2, '.');
				$results['ENVIRONMENT']['WHITELIST']['EXTENSIONS'][] = $v2;
			}
		}
		foreach ($results['ENVIRONMENT']['WHITELIST'] as $k=>$v) {
			$results['ENVIRONMENT']['WHITELIST'][$k] = array_unique($results['ENVIRONMENT']['WHITELIST'][$k]);
			sort($results['ENVIRONMENT']['WHITELIST'][$k]);
		}

		$this->errors = array_values(array_unique($this->errors));
		if (count($this->errors)) {
			$results['RESULT'] = __('The following error(s) were found:', 'blob-mimes') . "\n * " . implode("\n * ", $this->errors);
		}
		else {
			$results['RESULT'] = __('You *should* be able to upload this file. If not, a plugin or theme might be interfering with the process.', 'blob-mimes');
		}

		// If we aren't collapsing, we're done.
		if (!$collapsed) {
			return $results;
		}

		$out = array();
		foreach ($results as $k=>$v) {
			if ('RESULT' !== $k) {
				$out[] = '';
				$out[] = '';
			}

			// Section header.
			$out[] = $this->make_results_header($k);

			// Just print results.
			if ('RESULT' === $k) {
				$out[] = $this->make_results_value($v);
				continue;
			}

			if (!is_array($v) || !count($v)) {
				continue;
			}

			// Other Subsections.
			foreach ($v as $k2=>$v2) {
				if (!is_array($v2) || !count($v2)) {
					continue;
				}

				// Subsection header.
				$out[] = '';
				$out[] = $this->make_results_header($k2, false);

				foreach ($v2 as $k3=>$v3) {
					$key = str_replace('_', ' ', strtoupper($k3));

					// An array.
					if (is_array($v3)) {
						if (!count($v3)) {
							$out[] = $this->make_results_value('N/A', false, $key);
						}
						else {
							// Indexed array.
							$indexed = true;
							foreach (array_keys($v3) as $k4) {
								if (!is_numeric($k4)) {
									$indexed = false;
									break;
								}
							}

							// Keys don't matter, just group everything.
							if ($indexed) {
								// PHP extensions don't need separate lines.
								if (
									('EXTENSIONS' === $key) ||
									('MIME TYPES' === $key)
								) {
									$value = implode('; ', $v3);
								}
								else {
									$value = implode("\n", $v3);
								}
								$out[] = $this->make_results_value($value, true, $key);
							}
							// Break out associative arrays.
							else {
								$tmp = array();
								foreach ($v3 as $k4=>$v4) {
									$k4 = '[' . str_replace('_', ' ', strtolower($k4)) . ']';
									if (is_bool($v4)) {
										$value = $v4 ? 'TRUE' : 'FALSE';
									}
									else {
										$value = $v4;
									}
									$tmp[] = __($k4, 'blob-mimes') . " $value";
								}
								$out[] = $this->make_results_value(implode("\n", $tmp), true, $key);
							}
						}
					}
					// A string.
					else {
						$out[] = $this->make_results_value($v3, true, $key);
					}
				}
			}
		}

		return implode("\n", $out);
	}

	/**
	 * Result Header
	 *
	 * @param string $header Header.
	 * @param bool $major Major section.
	 * @return string Header.
	 */
	protected function make_results_header($header, $major=true) {
		if (!$header) {
			return '';
		}

		if ($major) {
			if ('TESTS' === $header) {
				return static::ASCII_VALIDATION;
			}
			elseif ('ENVIRONMENT' === $header) {
				return static::ASCII_SYSTEM;
			}
		}
		else {
			$header = "  $header  ";
			$length = strlen($header);
			$border = str_repeat('-', $length);
			return "+{$border}+\n|{$header}|\n+{$border}+";
		}

		return '';
	}

	/**
	 * Result Value
	 *
	 * @param mixed $value Value.
	 * @param bool $indent Indent subsequent lines.
	 * @param string $key Include the key.
	 * @return string Value.
	 */
	protected function make_results_value($value, $indent=false, $key='') {
		if (is_bool($value)) {
			$value = $value ? 'TRUE' : 'FALSE';
		}
		else {
			try {
				$value = (string) $value;
				$value = trim($value);
			} catch (\Throwable $e) {
				$value = '';
			} catch (\Exception $e) {
				$value = '';
			}
		}

		if ($key) {
			$key = __($key, 'blob-mimes');
			$value = str_pad("$key:", 15, ' ', STR_PAD_RIGHT) . $value;
		}

		// Nothing to wrap.
		if (strlen($value) <= static::LINE_LENGTH && (false === strpos($value, "\n"))) {
			return $value;
		}

		$wrap = $indent ? static::LINE_LENGTH - 15 : static::LINE_LENGTH;
		$value = wordwrap($value, $wrap);
		$value = explode("\n", $value);
		foreach ($value as $k=>$v) {
			$value[$k] = trim($value[$k]);

			if ($k > 0 && $indent) {
				$value[$k] = str_repeat(' ', 15) . $value[$k];
			}
		}

		return implode("\n", $value);
	}

	// --------------------------------------------------------------------- end output
}


<?php

/**
 * This file is part of the m1\vars library
 *
 * (c) m1 <hello@milescroxford.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package     m1/vars
 * @version     1.1.0
 * @author      Miles Croxford <hello@milescroxford.com>
 * @copyright   Copyright (c) Miles Croxford <hello@milescroxford.com>
 * @license     http://github.com/m1/vars/blob/master/LICENSE
 * @link        http://github.com/m1/vars/blob/master/README.MD Documentation
 */

namespace M1\Vars\Traits;

/**
 * Path trait gives common operation functions needed for Paths in Vars
 *
 * @since 0.1.0
 */
trait PathTrait
{
    /**
     * The base path for the Vars config and cache folders
     *
     * @var string $path
     */
    public $path;

    /**
     * Get the path
     *
     * @return string The path
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * Set the Vars base path
     *
     * @param string  $path            The  path to set
     * @param boolean $check_writeable Check whether dir is writeable

     * @throws \InvalidArgumentException If the path does not exist or is not writable
     *
     * @return \M1\Vars\Vars
     */
    public function setPath($path, $check_writeable = false)
    {
        if (is_null($path)) {
            return;
        }

        if (!is_dir($path) || ($check_writeable && !is_writable($path))) {
            throw new \InvalidArgumentException(sprintf(
                "'%s' base path does not exist or is not writable",
                $path
            ));
        }

        $this->path = realpath($path);
        return $this;
    }
}


<?php

/**
 * This file is part of the m1\vars library
 *
 * Copyright (c) Miles Croxford <hello@milescroxford.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package     m1/vars
 * @version     1.1.0
 * @author      Miles Croxford <hello@milescroxford.com>
 * @copyright   Copyright (c) Miles Croxford <hello@milescroxford.com>
 * @license     http://github.com/m1/vars/blob/master/LICENSE
 * @link        http://github.com/m1/vars/blob/master/README.MD Documentation
 */

namespace M1\Vars\Resource;

use M1\Vars\Traits\FileTrait;
use M1\Vars\Traits\ResourceFlagsTrait;
use Symfony\Component\Filesystem\Filesystem;

/**
 * File Resource enables interaction with files as resources
 *
 * @since 0.1.0
 */
class FileResource extends AbstractResource
{
    /**
     * Basic file interaction logic
     */
    use FileTrait;

    /**
     * Enables the resource flags logic
     */
    use ResourceFlagsTrait;

    /**
     * The filename of the loaded file
     *
     * @var string
     */
    private $filename;

    /**
     * The parent ResourceProvider
     *
     * @var \M1\Vars\Resource\ResourceProvider
     */
    private $provider;

    /**
     * The raw content from the passed file
     *
     * @var mixed
     */
    private $raw_content = array();

    /**
     * The VariableProvider
     *
     * @var \M1\Vars\Variables\VariableProvider
     */
    private $variables;

    /**
     * The file resource constructor to get and parse the content from files
     *
     * @param \M1\Vars\Resource\ResourceProvider $provider The parent ResourceProvider
     * @param string                             $file     The passed file
     */
    public function __construct(ResourceProvider $provider, $file)
    {
        $this->provider = $provider;
        $this->vars = $provider->vars;
        $this->variables = $this->vars->variables;

        $this->makePaths($file);
        $this->validate();

        $store_prefix = $this->variables->vstore->getPrefix();

        $content = $this->loadContent($this->file);
        $this->raw_content = $content;

        if ($content) {
            $this->content = $this->searchForResources($content, $store_prefix);
        }
    }

    /**
     * Make the paths used for the filename variable
     *
     * @param string $file The passed file
     */
    private function makePaths($file)
    {
        $file = realpath($file);

        $base_path = $this->provider->vars->getPath();

        $filesystem = new Filesystem();
        $abs_path = $filesystem->makePathRelative(
            $file,
            $base_path
        );

        $this->file = $file;
        $this->filename = rtrim($abs_path, "/");
    }

    /**
     * Search for imports in the files and does the replacement variables
     *
     * @param mixed  $content The file content received from the loader
     * @param string $prefix  The array prefix for the entity
     *
     * @return array Returns the parsed content
     */
    private function searchForResources($content = array(), $prefix = '')
    {
        $returned_content = array();

        foreach ($content as $ck => $cv) {
            $this->variables->vstore->setCurrentPrefix($prefix);
            $returned_content = $this->parseContent($ck, $cv, $returned_content, $prefix);
        }

        return $returned_content;
    }

    /**
     * Parses the contents inside the content array
     *
     * @param mixed  $key              The key of the content array
     * @param mixed  $value            The value of the key
     * @param array  $returned_content The modified content array to return
     * @param string $prefix           The array prefix for the entity
     *
     * @return array Returns the modified content array
     */
    private function parseContent($key, $value, $returned_content, $prefix)
    {
        if ($key === 'imports' && !is_null($value) && !empty($value)) {
            $imported_resource = $this->useImports($value);

            if ($imported_resource) {
                $returned_content = array_replace_recursive($returned_content, $imported_resource);
            }
        } elseif (is_array($value)) {
            $returned_content[$key] = $this->searchForResources(
                $value,
                $this->variables->vstore->createPrefixName($prefix, $key)
            );
        } else {
            $value = $this->parseText($value);
            $this->variables->vstore->set($prefix.$key, $value);
            $returned_content[$key] = $value;
        }

        return $returned_content;
    }

    /**
     * Parses the text for option and environment replacements and replaces the text
     *
     * @param string $text The text to be parsed
     *
     * @return string|null The parsed string
     */
    private function parseText($text)
    {
        if (is_string($text)) {
            return $this->variables->parse($text);
        }

        return $text;
    }

    /**
     * Use the import arrays to import resources
     *
     * @param mixed $imports The resources wanting to be imported
     *
     * @return array The parsed imported resources
     */
    private function useImports($imports)
    {
        $imported_resources = array();

        if ((is_array($imports) && $this->isAssoc($imports)) || is_string($imports)) {
            $imports = array($imports);
        }

        foreach ($imports as $import) {
            $imported_resources = $this->processImport($this->parseText($import), $imported_resources);
        }

        return $imported_resources;
    }

    /**
     * Processes the import and gets individual import if set and passes them off to import2Resources()
     *
     * @param mixed $import The import to be processed
     * @param array $imported_resources The array of imported resources
     *
     * @return array The parsed imported resources
     */
    private function processImport($import, array $imported_resources)
    {
        if (is_array($import) && array_key_exists('resource', $import) && is_array($import['resource'])) {
            foreach ($import['resource'] as $resource) {
                $temp = array(
                    'resource' => $resource,
                    'relative' => $this->checkBooleanValue('relative', $import),
                    'recursive' => $this->checkBooleanValue('recursive', $import),
                );

                $imported_resources = $this->import2Resource($temp, $imported_resources);
            }
        } else {
            $imported_resources = $this->import2Resource($import, $imported_resources);
        }

        return $imported_resources;
    }

    /**
     * Creates the resource from the import then imports it
     *
     * @param array|string $import The string|array to be converted to a resource
     * @param array $imported_resources The array of imported resources
     *
     * @return array The imported resources
     */
    private function import2Resource($import, array $imported_resources)
    {
        $resource = $this->createResource($import);

        if ($resource) {
            $imported_resources = $this->importResource($resource, $imported_resources);
        }

        return $imported_resources;
    }

    /**
     * Creates resource from the import
     *
     * @param array|string $import The import to create a resource from
     *
     * @return \M1\Vars\Resource\ResourceProvider The resource of the import
     */
    private function createResource($import)
    {
        if (is_array($import) && array_key_exists('resource', $import)) {
            $import_resource = $import;
            $import_resource['relative'] = $this->checkBooleanValue('relative', $import_resource);
            $import_resource['recursive'] = $this->checkBooleanValue('recursive', $import_resource);
        } elseif (is_string($import)) {
            $import_resource = array('resource' => $import, 'relative' => true, 'recursive' => true);
        }

        $import_resource = new ResourceProvider(
            $this->provider->vars,
            $this->createImportName($import_resource['resource']),
            $import_resource['relative'],
            $import_resource['recursive']
        );

        return $import_resource;
    }

    /**
     * Creates the correctly formatted resource name with paths
     *
     * @param string $resource The resource to create the import name for
     *
     * @return string The parsed resource
     */
    private function createImportName($resource)
    {
        $resource = $this->explodeResourceIfElse($resource);
        $resource_pieces = array();

        foreach ($resource as $r) {
            $parsed_r = $this->trimFlags($r);
            $parsed_r = sprintf('%s/%s', dirname($this->file), $parsed_r);
            $parsed_r = $this->replicateFlags($parsed_r, $r);

            $resource_pieces[] = $parsed_r;
        }

        return $this->implodeResourceIfElse($resource_pieces);
    }

    /**
     * Import resource into the imported resources and merge contents
     *
     * @param ResourceProvider $provider The new imported resource
     * @param array            $imported_resources The imported resources
     *
     * @return array The modified imported resources
     */
    private function importResource(ResourceProvider $provider, $imported_resources)
    {
        $content = $provider->getContent();
        $parent_content = $provider->getParentContent();

        if (!empty($content)) {
            $imported_resources = array_replace_recursive($imported_resources, $content);
        }

        if (!empty($parent_content)) {
            $this->provider->addParentContent($parent_content);
        }

        return $imported_resources;
    }

    /**
     * Returns whether the passed array is associative
     *
     * @param array $array The passed array
     *
     * @return bool Is the passed array associative
     */
    private function isAssoc(array $array)
    {
        return array_keys($array) !== range(0, count($array) - 1);
    }

    /**
     * Checks if the passed boolean value is true or false
     *
     * @param string $value  The value to check
     * @param mixed  $import The passed import
     *
     * @return bool Returns the value of the boolean
     */
    public function checkBooleanValue($value, $import)
    {
        $default = false;

        if ($value === 'relative') {
            $default = true;
        }

        $value = (isset($import[$value])) ? $import[$value] : $default;

        return $this->getBooleanValue($value);
    }

    /**
     * Gets the boolean value from the string
     *
     * @param string $value  The value to check
     *
     * @return bool Returns the value of the boolean
     */
    private function getBooleanValue($value)
    {
        $value = strtolower($value);

        if (!$value || $value === "false" || $value === "no") {
            return false;
        }

        return true;
    }

    /**
     * Returns the filename of the resource
     *
     * @return string The filename
     */
    public function getFilename()
    {
        return $this->filename;
    }

    /**
     * Returns the raw content of the resource
     *
     * @return array|mixed The raw content
     */
    public function getRawContent()
    {
        return $this->raw_content;
    }
}


<?php
/**
 * Lord of the Files - MIMEs
 *
 * MIME and filetype management.
 *
 * @package blob-mimes
 * @author  Blobfolio, LLC <hello@blobfolio.com>
 */

namespace blobfolio\wp\bm;

class mime {
	/**
	 * Return MIME aliases for a particular file extension.
	 *
	 * @see {https://www.iana.org/assignments/media-types}
	 * @see {https://raw.githubusercontent.com/apache/httpd/trunk/docs/conf/mime.types}
	 * @see {http://hg.nginx.org/nginx/raw-file/default/conf/mime.types}
	 * @see {https://cgit.freedesktop.org/xdg/shared-mime-info/plain/freedesktop.org.xml.in}
	 * @see {https://raw.githubusercontent.com/apache/tika/master/tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml}
	 * @see {https://github.com/Blobfolio/blob-mimes}
	 *
	 * @param string $ext File extension.
	 * @return array|bool MIME types or false.
	 */
	public static function get_aliases($ext='') {
		$ext = trim(strtolower($ext));
		$ext = ltrim($ext, '.');
		if (strlen($ext) && array_key_exists($ext, mime\aliases::$data)) {
			$match = mime\aliases::$data[$ext];
		} else {
			$match = false;
		}

		// Filter.
		return apply_filters('blobmimes_get_mime_aliases', $match, $ext);
	}

	/**
	 * Assign a new extension to a filename.
	 *
	 * @param string $filename The original filename.
	 * @param string $ext The new extension.
	 * @return string The renamed file.
	 */
	public static function update_filename_extension($filename, $ext) {
		$ext = strtolower($ext);
		$ext = rtrim($ext, '.');
		$ext = ltrim($ext, '.');

		$filename_parts = explode('.', $filename);

		// Remove the old extension.
		if (count($filename_parts) > 1) {
			array_pop($filename_parts);
		}

		// Add the new extension.
		if (strlen($ext)) {
			$filename_parts[] = $ext;
		}

		return implode('.', $filename_parts);
	}

	/**
	 * Check extension and MIME pairing.
	 *
	 * @see { https://github.com/php/php-src/blob/3ef069d26c7863b059325b9a0d26cac31c97fe4b/ext/fileinfo/libmagic/readcdf.c }
	 *
	 * @param string $ext File extension.
	 * @param string $mime MIME type.
	 * @return bool True/false.
	 */
	public static function check_alias($ext='', $mime='') {
		// Standardize inputs.
		$mime = strtolower(sanitize_mime_type($mime));
		$ext = trim(strtolower($ext));
		$ext = ltrim($ext, '.');

		// Can't continue if the extension is not in the database.
		if (false === ($mimes = static::get_aliases($ext))) {
			// Filter.
			return apply_filters('blobmimes_check_mime_alias', false, $ext, $mime);
		}

		// Before looking for matches, convert any generic CDFV2
		// types into an equally generic, but less variable type.
		if (0 === strpos($mime, 'application/cdfv2')) {
			$mime = 'application/vnd.ms-office';
		}

		// "Default" MIME might not need to trigger a failure.
		if ('application/octet-stream' === $mime) {
			// Filter.
			if (false === apply_filters('blobmimes_check_application_octet_stream', false)) {
				return apply_filters('blobmimes_check_mime_alias', true, $ext, $mime);
			}
		}
		// An empty file doesn't really have a type.
		elseif ('inode/x-empty' === $mime) {
			return apply_filters('blobmimes_check_mime_alias', true, $ext, $mime);
		}

		// Test for the literal MIME, but also certain generic
		// variations like x-subtype and vnd.subtype.
		$test = array($mime);

		$parts = explode('/', $mime);
		$subtype = count($parts) - 1;
		if ('x-' === substr($parts[$subtype], 0, 2)) {
			$parts[$subtype] = substr($parts[$subtype], 2);
		} else {
			$parts[$subtype] = 'x-' . $parts[$subtype];
		}
		$test[] = implode('/', $parts);

		$parts = explode('/', $mime);
		$subtype = count($parts) - 1;
		if ('vnd.' === substr($parts[$subtype], 0, 4)) {
			$parts[$subtype] = substr($parts[$subtype], 4);
		} else {
			$parts[$subtype] = 'vnd.' . $parts[$subtype];
		}
		$test[] = implode('/', $parts);

		// Overlap is success!
		$found = array_intersect($test, $mimes);
		$match = count($found) > 0;

		// Filter.
		return apply_filters('blobmimes_check_mime_alias', $match, $ext, $mime);
	}

	/**
	 * Check Allowed Aliases
	 *
	 * This will cycle through each allowed ext/MIME pair to see if an
	 * alias matches anything.
	 *
	 * @param string $alias MIME alias.
	 * @param array $mimes Allowed MIME types.
	 * @return array|bool Array containing ext and type keys or false.
	 */
	public static function check_allowed_aliases($alias, $mimes=null) {
		// Default MIMEs.
		if (empty($mimes)) {
			$mimes = get_allowed_mime_types();
		}

		$alias = strtolower(sanitize_mime_type($alias));

		$ext = $type = false;

		// Early bail opportunity.
		if (!$alias || !count($mimes)) {
			return false;
		}

		// Direct hit!
		if (false !== $extensions = array_search($alias, $mimes, true)) {
			$extensions = explode('|', $extensions);
			$ext = $extensions[0];
			$type = $alias;

			return compact('ext', 'type');
		}

		// Try all extensions.
		foreach ($mimes as $extensions=>$mime) {
			$extensions = explode('|', $extensions);
			foreach ($extensions as $extension) {
				if (static::check_alias($extension, $alias)) {
					$ext = $extension;
					$type = $mime;

					return compact('ext', 'type');
				}
			}
		}

		return false;
	}

	/**
	 * Retrieve the "real" file type from the file.
	 *
	 * This extends `wp_check_filetype()` to additionally
	 * consider content-based indicators of a file's
	 * true type.
	 *
	 * The content-based type will override the name-based
	 * type if available and included in the $mimes list.
	 *
	 * A false response will be set if the extension is
	 * not allowed, or if a "real MIME" was found and
	 * that MIME is not allowed.
	 *
	 * @see wp_check_filetype()
	 * @see wp_check_filetype_and_ext()
	 *
	 * @param string $file Full path to the file.
	 * @param string $filename The name of the file (may differ from $file due to $file being in a tmp directory).
	 * @param array  $mimes Optional. Key is the file extension with value as the mime type.
	 * @return array Values with extension first and mime type.
	 */
	public static function check_real_filetype($file, $filename=null, $mimes= null) {
		// Default filename.
		if (empty($filename)) {
			$filename = basename($file);
		}

		// Default MIMEs.
		if (empty($mimes)) {
			$mimes = get_allowed_mime_types();
		}

		// Run a name-based check first.
		$checked = wp_check_filetype($filename, $mimes);

		// Only dig deeper if we can.
		if (
			false !== $checked['ext'] &&
			false !== $checked['type'] &&
			@file_exists($file) &&
			@filesize($file) > 0
		) {
			$real_mime = false;

			try {
				// Fall back to fileinfo, if available.
				if (
					false === $real_mime &&
					extension_loaded('fileinfo') &&
					defined('FILEINFO_MIME_TYPE')
				) {
					$finfo = finfo_open(FILEINFO_MIME_TYPE);
					$real_mime = finfo_file($finfo, $file);
					finfo_close($finfo);

					// Account for inconsistent return values.
					if (!is_string($real_mime) || !$real_mime) {
						$real_mime = false;
					}
					else {
						$real_mime = strtolower(sanitize_mime_type($real_mime));
					}
				}
			} catch (\Throwable $e) {
				$real_mime = false;
			} catch (\Exception $e) {
				$real_mime = false;
			}

			// We might need to override fileinfo.
			if ((false !== $real_mime) && ($real_mime !== $checked['type'])) {
				// SVGs are often misidentified if they are missing the
				// leading XML tag and/or DOCTYPE declarations.
				if ('image/svg+xml' === $checked['type']) {
					$tmp = @file_get_contents($file);
					if (
						is_string($tmp) &&
						preg_match('/\s*<svg/iu', $tmp)
					) {
						$real_mime = 'image/svg+xml';
					}
				}

				// JSON doesn't have much magic for fileinfo to work
				// with, but we can assume if it parses, it's JSON.
				elseif (
					function_exists('json_decode') &&
					static::check_alias($checked['ext'], 'application/json')
				) {
					$tmp = @file_get_contents($file);
					if (is_string($tmp) && $tmp) {
						$tmp2 = @json_decode($tmp, true);
						if (null !== $tmp2) {
							$real_mime = $checked['type'];
						}
					}
				}

				// It is easy to confuse XML with HTML, usually because
				// a document is missing a leading XML tag.
				elseif (
					('text/html' === $real_mime) &&
					static::check_alias($checked['ext'], 'text/xml')
				) {
					$real_mime = $checked['type'];
				}
			}

			// Evaluate our real MIME.
			if ((false !== $real_mime) && ($real_mime !== $checked['type'])) {
				if (!static::check_alias($checked['ext'], $real_mime)) {
					// Maybe this type belongs to another allowed extension.
					if (false !== $result = static::check_allowed_aliases($real_mime, $mimes)) {
						$checked['ext'] = $result['ext'];
						$checked['type'] = $result['type'];
					}
					// Otherwise reject the results.
					else {
						$checked['ext'] = false;
						$checked['type'] = false;
					}
				}
			}
		}// End content-based type checking.

		// Filter.
		return apply_filters(
			'blobmimes_check_real_filetype',
			$checked,
			$file,
			$filename,
			$mimes
		);
	}
}


<?php

/**
 * This file is part of the m1\vars library
 *
 * (c) m1 <hello@milescroxford.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package     m1/vars
 * @version     1.1.0
 * @author      Miles Croxford <hello@milescroxford.com>
 * @copyright   Copyright (c) Miles Croxford <hello@milescroxford.com>
 * @license     http://github.com/m1/vars/blob/master/LICENSE
 * @link        http://github.com/m1/vars/blob/master/README.MD Documentation
 */

namespace M1\Vars\Variables;

use \M1\Vars\Resource\AbstractResource;
use M1\Vars\Traits\FileTrait;

/**
 * Stores the replacement variables
 *
 * @since 1.0.0
 */
class ReplacementStore extends AbstractResource
{
    /**
     * Basic file interaction logic
     */
    use FileTrait;

    /**
     * Creates new instance of VariableResource
     *
     * @param \M1\Vars\Vars $vars Instance of the calling Vars
     */
    public function __construct($vars)
    {
        $this->vars = $vars;
    }

    /**
     * Creates the replacement variable content from a file or array
     *
     * @param array $replacements The replacements

     * @throws \RuntimeException If variable data is not array or a file
     *
     * @return  array The replacement variables
     */
    public function load($replacements)
    {
        if (is_array($replacements)) {
            $variables = $replacements;
        } elseif (is_file($replacements)) {
            $variables = $this->loadContent($replacements);
        } else {
            throw new \RuntimeException('Config replacements must be a array or a file');
        }

        $this->content = $variables;
    }
}


<?php

/**
 * This file is part of the m1\vars library
 *
 * (c) m1 <hello@milescroxford.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package     m1/vars
 * @version     1.1.0
 * @author      Miles Croxford <hello@milescroxford.com>
 * @copyright   Copyright (c) Miles Croxford <hello@milescroxford.com>
 * @license     http://github.com/m1/vars/blob/master/LICENSE
 * @link        http://github.com/m1/vars/blob/master/README.MD Documentation
 */

namespace M1\Vars\Loader;

/**
 * This file provides dir loading support for Vars
 *
 * @since 0.1.1
 */
class DirectoryLoader extends AbstractLoader
{
    /**
     * Construct the loader with the passed entity
     *
     * @param string $entity    The passed entity
     * @param bool   $recursive Search the directories   recursively
     */
    public function __construct($entity, $recursive)
    {
        parent::__construct($entity);
        $this->recursive = $recursive;
    }

    /**
     * {@inheritdoc}
     */
    public function load()
    {
        $paths = array();
        $files = ($this->recursive) ? $this->getSupportedFilesRecursively() : $this->getSupportedFiles();

        foreach ($files as $path => $file) {
            if ($file->isFile()) {
                $paths[] = $path;
            }
        }
        $this->content = $this->makeResources($paths);

        return $this;
    }

    /**
     * Returns the supported files in the directory recursively
     *
     * @return \RegexIterator The supported files in the directories
     */
    private function getSupportedFilesRecursively()
    {
        $dir_it = new \RecursiveDirectoryIterator($this->entity, \RecursiveDirectoryIterator::SKIP_DOTS);

        $files = new \RecursiveIteratorIterator(
            $dir_it,
            \RecursiveIteratorIterator::LEAVES_ONLY,
            \RecursiveIteratorIterator::CATCH_GET_CHILD
        );

        return $this->createRegexIterator($files);
    }

    /**
     * Returns the supported files in the directory
     *
     * @return \RegexIterator The supported files in the directory
     */
    private function getSupportedFiles()
    {
        $files = new \FilesystemIterator($this->entity);

        return $this->createRegexIterator($files);
    }

    /**
     * Returns the supported files in the directory
     *
     * @param \FilesystemIterator|\RecursiveIteratorIterator $files The found files in the directory/ies
     *
     * @return \RegexIterator The supported files in the directory using the regexiterator
     */
    private function createRegexIterator($files)
    {
        return new \RegexIterator(
            $files,
            '/^.*\.(' . implode('|', static::$supported) . ')$/i'
        );
    }

    /**
     * Makes usable resource paths from path strings
     *
     * @param array $paths The path strings
     *
     * @return array|bool  The usable resources if any, else false
     */
    private function makeResources($paths)
    {
        if ($paths && !empty($paths)) {
            $resources = array();

            foreach ($paths as $path) {
                $resources[] = $path;
            }

            return $resources;
        }

        return false;
    }
}


<?php

/**
 * This file is part of the m1\vars library
 *
 * (c) m1 <hello@milescroxford.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package     m1/vars
 * @version     1.1.0
 * @author      Miles Croxford <hello@milescroxford.com>
 * @copyright   Copyright (c) Miles Croxford <hello@milescroxford.com>
 * @license     http://github.com/m1/vars/blob/master/LICENSE
 * @link        http://github.com/m1/vars/blob/master/README.MD Documentation
 */

namespace M1\Vars\Cache;

use M1\Vars\Traits\PathTrait;
use M1\Vars\Vars;

/**
 * The CacheProvider provides the cached file if one exists and is requested
 *
 * @since 0.1.0
 */
class CacheProvider
{
    /**
     * Used for path functions and variables
     */
    use PathTrait;

    /**
     * Has the cache been attempted
     *
     * @var bool $attempted
     */
    private $attempted = false;

    /**
     * How long for the cache to be fresh
     *
     * @var int $expire
     */
    private $expire;

    /**
     * Has the cache been found
     *
     * @var bool $hit
     */
    private $hit = false;

    /**
     * The loaded Vars object from cache
     *
     * @var \M1\Vars\Vars $loaded_vars
     */
    private $loaded_vars;

    /**
     * The cache file name
     *
     * @var string $name
     */
    private $name;

    /**
     * Is the cache turned on
     *
     * @var boolean $provide
     */
    private $provide;

    /**
     * If cached, the time when the class was cached
     *
     * @var int $time
     */
    private $time;

    /**
     * Creates a new instance of the cacheProvider for Vars
     *
     * @param string|array $resource The main configuration resource
     * @param array        $options  The options being used for Vars
     */
    public function __construct($resource, $options)
    {
        $this->setProvide($options['cache']);
        $this->setPath($options['cache_path'], true);

        $this->expire = $options['cache_expire'];
        $this->name = md5(serialize($resource));
    }

    /**
     * Checks the cache to see if there is a valid cache available
     *
     * @return bool Returns true if has the cached resource
     */
    public function checkCache()
    {
        if ($this->provide && $this->path && !$this->getAttempted()) {
            $file = sprintf('%s/vars/%s.php', $this->path, $this->name);
            $this->attempted = true;

            if (is_file($file) &&
                filemtime($file) >= (time() - $this->expire)) {
                $this->hit = true;
                return true;
            }
        }
        return false;
    }

    /**
     * Load the cached file into $this->loaded_vars
     */
    public function load()
    {
        $cached_file = sprintf('%s/vars/%s.php', $this->path, $this->name);
        $this->loaded_vars = unserialize(file_get_contents($cached_file));
    }

    /**
     * Transfer the contents of the parent Vars object into a file for cache
     *
     * @param \M1\Vars\Vars $vars Parent vars object
     *
     * @codeCoverageIgnore
     */
    public function makeCache(Vars $vars)
    {
        if ($this->provide) {
            $cache_folder = sprintf("%s/vars", $this->path);
            if (!file_exists($cache_folder)) {
                mkdir($cache_folder, 0777, true);
            }

            $cache_file = sprintf('%s/%s.php', $cache_folder, $this->name);
            file_put_contents($cache_file, serialize($vars));
        }
    }

    /**
     * Returns if cache is on or off
     *
     * @return bool Is cache on or off
     */
    public function getProvide()
    {
        return $this->provide;
    }

    /**
     * Set the cache on or off
     *
     * @param bool $provide Does the cache want to be on or off
     *
     * @return \M1\Vars\Cache\CacheProvider
     */
    public function setProvide($provide)
    {
        $this->provide = $provide;
        return $this;
    }

    /**
     * Returns if the cache has been attempted
     *
     * @return bool Has the cache been attempted
     */
    public function getAttempted()
    {
        return $this->attempted;
    }

    /**
     * Returns how long the cache lasts for
     *
     * @return int Cache expire time
     */
    public function getExpire()
    {
        return $this->expire;
    }

    /**
     * Returns how long the cache lasts for
     *
     * @return int Cache expire time
     */
    public function isHit()
    {
        return $this->hit;
    }

    /**
     * Returns when the cache was made
     *
     * @return int Cache creation time
     */
    public function getTime()
    {
        return $this->time;
    }

    /**
     * Sets the time when the Vars was cached
     *
     * @param int $time Time when vars cached was created
     *
     * @return \M1\Vars\Cache\CacheProvider The cacheProvider object
     */
    public function setTime($time)
    {
        $this->time = $time;
        return $this;
    }

    /**
     * Returns the loaded Vars object
     *
     * @return \M1\Vars\Vars The loaded Vars object
     */
    public function getLoadedVars()
    {
        return $this->loaded_vars;
    }
}


<?php

/**
 * This file is part of the m1\vars library
 *
 * (c) m1 <hello@milescroxford.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package     m1/vars
 * @version     1.1.0
 * @author      Miles Croxford <hello@milescroxford.com>
 * @copyright   Copyright (c) Miles Croxford <hello@milescroxford.com>
 * @license     http://github.com/m1/vars/blob/master/LICENSE
 * @link        http://github.com/m1/vars/blob/master/README.MD Documentation
 */

namespace M1\Vars\Traits;

/**
 * Vars transformer class for to*() functions
 *
 * @since 0.2.0
 */
trait TransformerTrait
{
    /**
     * Makes it so the content is available in getenv()
     */
    public function toEnv()
    {
        $dots = $this->toDots();

        if (is_array($dots)) {
            foreach ($dots as $dot_k => $dot_v) {
                putenv(sprintf('%s=%s', $dot_k, $dot_v));
            }
        }
    }

    /**
     * Converts the array into a flat dot notation array
     *
     * @param bool $flatten_array Flatten arrays into none existent keys
     *
     * @return array The dot notation array
     */
    public function toDots($flatten_array = true)
    {
        return (!is_null($this->content)) ? $this->dotTransformer($this->content, $flatten_array) : $this->content;
    }

    /**
     * Converts the array into a flat dot notation array
     *
     * @param array  $content       The content array
     * @param bool   $flatten_array Flatten arrays into none existent keys
     * @param string $prefix        The prefix for the key
     *
     * @return array The dot notation array
     */
    private function dotTransformer($content, $flatten_array, $prefix = '')
    {
        $parsed = array();
        foreach ($content as $arr_k => $arr_v) {
            if (is_array($arr_v)) {
                if (!$flatten_array) {
                    $parsed[$prefix.$arr_k] = $arr_v;
                }

                $parsed = array_merge($parsed, $this->dotTransformer($arr_v, $flatten_array, $prefix.$arr_k."."));
            } else {
                $parsed[$prefix.$arr_k] = $arr_v;
            }
        }

        return $parsed;
    }
}


<?php
namespace Kachkaev\AssetsVersionBundle;

use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\HttpFoundation\File\Exception\FileException;

/**
 * Works with parameters.yml at a low level, extracts and writes back value of assets_version
 * Because parsing is done using regular expressions insead of YamlParser, formatting is preserved when writing value to file.
 *
 * @author Alexander Kachkaev <alexander@kachkaev.ru>
 *
 */
class AssetsVersionManager
{
    static protected $versionParameterMask = '[a-zA-Z_][a-zA-Z0-9_-]*';
    static protected $versionValueMask = '[a-zA-Z0-9_\.-]*';

    protected $filePath;
    protected $parameterName;

    protected $fileContents;
    protected $versionValue;
    protected $versionStartPos;

    public function __construct($filePath, $parameterName)
    {
        $this->filePath = $filePath;

        if (!preg_match('/^' . static::$versionParameterMask . '$/', $parameterName)) {
            throw new \InvalidArgumentException(sprintf(
                    'Wrong value for parameter name %s - it should consist only of characters, numbers and dash or underscore',
                    var_export($parameterName, true)
                ));
        }
        $this->parameterName = $parameterName;

        $this->readFile();
    }

    /**
     * Gets the value of assets version found in parameters file
     *
     * @param boolean $rereadFile - if true, re-reads the file first
     */
    public function getVersion($rereadFile = false)
    {
        if ($rereadFile) {
            $this->readFile();
        }

        return $this->versionValue;
    }

    /**
     * Sets a new value for the assets version parameter
     *
     * Assets version must consist only of letters, numbers and the following characters: .-_
     */
    public function setVersion($value)
    {
        if (!is_string($value) && !is_numeric($value)) {
            throw new \InvalidArgumentException(sprintf(
                    'Wrong value for assets version: %s - it must be string or numeric',
                    var_export($value, true)
                ));
        }

        if (!preg_match('/^' . static::$versionValueMask . '$/', $value)) {
            throw new \InvalidArgumentException(sprintf(
                    'Wrong value for assets version: %s - it must be empty or consist only of letters, numbers and the following characters: .-_',
                    var_export($value, true)
                ));
        }

        $this->fileContents = substr_replace(
                $this->fileContents,
                $value,
                $this->versionStartPos,
                strlen($this->versionValue)
            );
        $this->versionValue = $value;

        try {
            file_put_contents($this->filePath, $this->fileContents);
        } catch (\Exception $e) {
            throw new FileException(sprintf(
                    'Could not write to write "%s"; make sure it exists and you have enough permissions',
                    $this->filePath
                ));
        }
    }

    /**
     * Increments value for assets version found in parameters file
     * Only works when current value is integer or has integer ending, e.g. v42
     * If delta is given, incrementing is done by that value.
     *
     * @param int $delta number to increment (default is 1)
     * @param boolean $rereadFile if true, re-reads the file first
     */
    public function incrementVersion($delta = 1, $rereadFile = false)
    {
        if ($rereadFile) {
            $this->readFile();
        }

        if (!is_numeric($delta) || round($delta) != $delta) {
            throw new \InvalidArgumentException(sprintf(
                    'Delta must be integer, %s given',
                    var_export($delta, true)
                ));
        }

        preg_match('/^(.*)(\d+)$/U', $this->versionValue, $matches);
        if (!array_key_exists(2, $matches)) {
            throw new \UnexpectedValueException(sprintf(
                    'Could not increment assets version %s - it should be integer or at least have integer ending',
                    var_export($this->versionValue, true)
                ));
        }

        $newValue = max(0, $matches[2] + $delta) . '';

        // Preserve leading zeros
        if ($matches[2][0] == '0') {
            $newValue = str_pad($newValue, strlen($matches[2]), '0', STR_PAD_LEFT);
        }

        $this->setVersion($matches[1] . $newValue);
    }

    /**
     * Reads and parses file with parameters
     *
     * @throws InvalidConfigurationException
     * @throws FileException
     * @throws \Exception
     */
    protected function readFile()
    {

        $fileExtension = pathinfo($this->filePath, PATHINFO_EXTENSION);
        if ($fileExtension != 'yml' && $fileExtension != 'yaml')
            throw new InvalidConfigurationException(sprintf(
                    'Could not use "%s" - only yml files are supported by AssetsVersionManager',
                    var_export($this->versionValue, true)
                ));

        try {
            $this->fileContents = file_get_contents($this->filePath);
        } catch (\Exception $e) {
            throw new FileException(sprintf(
                    'Could not read file "%s"; make sure it exists and you have enough permissions',
                    $this->filePath
                ));
        }

        // Find a row with the parameter
        preg_match(
                '/(\s+' . $this->parameterName . '\:[^\S\n]*)(' . static::$versionValueMask . ')\s*(\n|#)/',
                $this->fileContents . "\n",
                $matches
            );

        if (array_key_exists(2, $matches)) {
            $this->versionValue = $matches[2];
            $this->versionStartPos = strpos($this->fileContents."\n", $matches[0]) + strlen($matches[1]);
            return;
        }

        throw new \Exception(sprintf(
                'Could not find definition of parameter "%s"; make sure it exists in "%s"',
                $this->parameterName,
                $this->filePath
            ));
    }
}


<?php
/**
 * Sniff to prohibit some file comment tags on WooCommerce
 */

namespace WooCommerce\Core\Sniffs\Commenting;

use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;

/**
 * Comment tags sniff.
 */
class CommentTagsSniff implements Sniff
{

    /**
     * A list of tokenizers this sniff supports.
     *
     * @var array
     */
    public $supportedTokenizers = [
        'PHP',
        'JS',
    ];

    /**
     * Returns an array of tokens this test wants to listen for.
     *
     * @return array
     */
    public function register(): array
    {
        return [T_DOC_COMMENT_TAG];
    }

    /**
     * List of prohibited tags.
     *
     * @return array
     */
    protected function getProhibitedTags(): array
    {
        return [
            '@author'    => 'AuthorTag',
            '@category'  => 'CategoryTag',
            '@license'   => 'LicenseTag',
            '@copyright' => 'CopyrightTag',
            '@access'    => 'AccessTag',
        ];
    }

    /**
     * Get error message.
     *
     * @param string $key
     * @return string
     */
    protected function getErrorMessage(string $key): string
    {
        if ($key === '@category') {
            return "@category is deprecated, use @package instead";
        }

        return "{$key} tags are prohibited";
    }

    /**
     * Processes this test, when one of its tokens is encountered.
     *
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
     * @param int                         $stackPtr  The position of the current token
     *                                               in the stack passed in $tokens.
     */
    public function process(File $phpcsFile, $stackPtr)
    {
        $tokens = $phpcsFile->getTokens();

        foreach ($this->getProhibitedTags() as $key => $value) {
            if ($key === $tokens[$stackPtr]['content']) {
                $phpcsFile->addError($this->getErrorMessage($key), $stackPtr, $value);
            }
        }
    }
}


<?php

/*
 * This file is based on the code of the Sylius ResourceBundle.
 *
 * (c) Paweł Jędrzejewski
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Joschi127\DoctrineEntityOverrideBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
 * Resolves given target entities with container parameters.
 * Usable only with *doctrine/orm* driver.
 *
 * Based on the code of the Sylius ResourceBundle.
 *
 * @author Paweł Jędrzejewski <pjedrzejewski@sylius.pl>
 */
class DoctrineTargetEntitiesResolver
{
    /**
     * {@inheritdoc}
     */
    public function resolve(ContainerBuilder $container)
    {
        if (!$container->hasDefinition('doctrine.orm.listeners.resolve_target_entity')) {
            throw new \RuntimeException('Cannot find Doctrine RTEL');
        }

        $overriddenEntities = $container->getParameter('joschi127_doctrine_entity_override.config.overridden_entities');
        $resolveTargetEntityListener = $container->findDefinition('doctrine.orm.listeners.resolve_target_entity');

        foreach ($overriddenEntities as $interface => $class) {
            $interface = $this->getInterface($container, $interface);
            $class = $this->getClass($container, $class);

            $resolveTargetEntityListener
                ->addMethodCall('addResolveTargetEntity', array(
                    $interface,
                    $class,
                    array()
                ))
            ;
        }

        if (!$resolveTargetEntityListener->hasTag('doctrine.event_listener')) {
            $resolveTargetEntityListener->addTag('doctrine.event_listener', array('event' => 'loadClassMetadata'));
        }
    }

    /**
     * @param ContainerBuilder $container
     * @param string           $key
     *
     * @return string
     * @throws \InvalidArgumentException
     */
    private function getInterface(ContainerBuilder $container, $key)
    {
        if ($container->hasParameter($key)) {
            return $container->getParameter($key);
        }

        if (interface_exists($key) || class_exists($key)) {
            return $key;
        }

        throw new \InvalidArgumentException(
            sprintf('The interface or class %s does not exists.', $key)
        );
    }

    /**
     * @param ContainerBuilder $container
     * @param string           $key
     *
     * @return string
     * @throws \InvalidArgumentException
     */
    private function getClass(ContainerBuilder $container, $key)
    {
        if ($container->hasParameter($key)) {
            return $container->getParameter($key);
        }

        if (class_exists($key)) {
            return $key;
        }

        throw new \InvalidArgumentException(
            sprintf('The class %s does not exists.', $key)
        );
    }
}


<?php

/*
 * This file is based on the code of the Sylius ResourceBundle.
 *
 * (c) Paweł Jędrzejewski
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Joschi127\DoctrineEntityOverrideBundle\EventListener;

use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Id\BigIntegerIdentityGenerator;
use Doctrine\ORM\Id\IdentityGenerator;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\ORMException;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Doctrine listener used to manipulate mappings.
 *
 * Based on the code of the Sylius ResourceBundle.
 *
 * @author Ivan Molchanov <ivan.molchanov@opensoftdev.ru>
 */
class LoadORMMetadataSubscriber implements EventSubscriber
{
    /**
     * @var ContainerInterface
     */
    protected $container;

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

    /**
     * @var array
     */
    protected $parentClassesByClass = [];

    /**
     * Constructor
     *
     * @param array $overriddenEntities
     */
    public function __construct(ContainerInterface $container, array $overriddenEntities)
    {
        $this->container = $container;
        $this->overriddenEntities = $overriddenEntities;
        foreach ($overriddenEntities as $interface => $class) {
            $class = $this->getClass($class);
            $this->parentClassesByClass[$class] = array_values(class_parents($class));
        }
    }

    /**
     * @return array
     */
    public function getSubscribedEvents()
    {
        return array(
            'loadClassMetadata'
        );
    }

    /**
     * @param LoadClassMetadataEventArgs $eventArgs
     */
    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
    {
        $em = $eventArgs->getEntityManager();

        /** @var ClassMetadata $metadata */
        $metadata = $eventArgs->getClassMetadata();

        $wasMappedSuperclass = $metadata->isMappedSuperclass;
        $this->setIsMappedSuperclass($metadata);

        if (!$metadata->isMappedSuperclass) {
            $this->setCustomRepositoryClasses($metadata, $eventArgs->getEntityManager()->getConfiguration());
            $this->setAssociationMappings($metadata, $eventArgs->getEntityManager()->getConfiguration());
            $this->setFieldMappings($metadata, $eventArgs->getEntityManager()->getConfiguration(), $em);
        } else {
            $this->unsetAssociationMappings($metadata);
            $this->unsetFieldMappings($metadata, $wasMappedSuperclass);
            $this->unsetOverriddenFieldMappings($metadata, $eventArgs->getEntityManager()->getConfiguration());
        }

        $this->updateAssociationMappingsToMappedSuperclasses($metadata);
    }

    protected function setIsMappedSuperclass(ClassMetadataInfo $metadata)
    {
        foreach ($this->overriddenEntities as $interface => $class) {
            $interface = $this->getInterface($interface);
            $class = $this->getClass($class);

            // set isMappedSuperclass = false for the actually used class
            if ($class === $metadata->getName()) {
                $metadata->isMappedSuperclass = false;

                break;
            }

            // set isMappedSuperclass = true for the super class / interface which is overridden
            if ($interface === $metadata->getName()) {
                $metadata->isMappedSuperclass = true;

                break;
            }

            // set isMappedSuperclass = true for all other parent classes of the actually used class to allow
            // overriding with multiple levels of inheritance
            foreach($this->parentClassesByClass[$class] as $parentClass) {
                if ($parentClass === $metadata->getName()) {
                    $metadata->isMappedSuperclass = true;

                    break 2;
                }
            }
        }
    }

    protected function setCustomRepositoryClasses(ClassMetadataInfo $metadata, $configuration)
    {
        if ($metadata->customRepositoryClassName) {
            return;
        }

        foreach ($this->overriddenEntities as $interface => $class) {
            $class = $this->getClass($class);

            if ($class === $metadata->getName()) {
                // inherit custom repository class of first parent classes which has one for the actually used class
                foreach ($this->parentClassesByClass[$class] as $parentClass) {
                    $parentMetadata = $this->getClassMetadata($parentClass, $configuration);

                    if ($parentMetadata->customRepositoryClassName) {
                        $metadata->setCustomRepositoryClass($parentMetadata->customRepositoryClassName);

                        return;
                    }
                }
            }
        }
    }

    protected function setAssociationMappings(ClassMetadataInfo $metadata, $configuration)
    {
        foreach ($this->overriddenEntities as $interface => $class) {
            $class = $this->getClass($class);

            if ($class === $metadata->getName()) {
                // inherit association mappings of all parent classes for the actually used class
                foreach ($this->parentClassesByClass[$class] as $parentClass) {
                    $parentMetadata = $this->getClassMetadata($parentClass, $configuration);

                    if (in_array($parentClass, $configuration->getMetadataDriverImpl()->getAllClassNames())) {
                        $configuration->getMetadataDriverImpl()->loadMetadataForClass($parentClass, $parentMetadata);

                        if ($this->classIsOverridden($parentClass)) {
                            foreach ($parentMetadata->getAssociationMappings() as $name => $mapping) {
                                //if ($this->typeIsRelation($mapping['type'])) {
                                    // update sourceEntity of association mapping
                                    if (isset($mapping['sourceEntity']) && $mapping['sourceEntity'] == $parentClass) {
                                        $mapping['sourceEntity'] = $class;
                                    }

                                    // if target entity is provided by relative classname, make it absolute
                                    if (!class_exists($mapping['targetEntity']) && strpos($mapping['targetEntity'], '\\') === false) {
                                        $reflClass = new \ReflectionClass($parentClass);
                                        $namespace = $reflClass->getNamespaceName();
                                        $absoluteTargetEntity = $namespace . '\\' . $mapping['targetEntity'];
                                        if (class_exists($absoluteTargetEntity)) {
                                            $mapping['targetEntity'] = $absoluteTargetEntity;
                                        }
                                    }

                                    // add association mapping for actually used class
                                    $metadata->associationMappings[$name] = $mapping;
                                //}
                            }
                        }
                    }
                }
            }
        }
    }

    protected function setFieldMappings(ClassMetadataInfo $metadata, $configuration, EntityManager $em)
    {
        foreach ($this->overriddenEntities as $interface => $class) {
            $class = $this->getClass($class);

            if ($class === $metadata->getName()) {
                // inherit association mappings of all parent classes for the actually used class
                foreach ($this->parentClassesByClass[$class] as $parentClass) {
                    $parentMetadata = $this->getClassMetadata($parentClass, $configuration);
                    $parentWasMappedSuperclass = $parentMetadata->isMappedSuperclass;

                    if (in_array($parentClass, $configuration->getMetadataDriverImpl()->getAllClassNames())) {
                        $configuration->getMetadataDriverImpl()->loadMetadataForClass($parentClass, $parentMetadata);

                        // if parent class is overridden and class is not the interface itself ...
                        // (parent class is "in between" in a multi level inheritance)
                        if ($this->classIsOverridden($parentClass) && !isset($this->overriddenEntities[$parentClass])) {
                            // ... add field mapping of the parent class to the the actually used class - it was removed
                            // from the parent class in unsetFieldMappings()
                            foreach ($parentMetadata->fieldMappings as $name => $mapping) {
                                // only inherit mapping if class does not have an own mapping for this field itself
                                // (this makes it possible to just override mappings, without having to use
                                // @ORM\AttributeOverrides)
                                if (!isset($metadata->fieldMappings[$mapping['fieldName']])) {
                                    unset($mapping['declared']);
                                    unset($mapping['inherited']);
                                    $metadata->mapField($mapping);
                                }
                            }
                            foreach ($parentMetadata->reflFields as $name => $field) {
                                if (!isset($metadata->reflFields[$name])) {
                                    $metadata->reflFields[$name] = $field;
                                }
                            }
                        }

                        // if parent class is overridden and class is the interface itself, but was originally not
                        // defined as mapped superclass ... (class was set to isMappedSuperclass = true by this
                        // listener)
                        else if ($this->classIsOverridden($parentClass) && !$parentWasMappedSuperclass) {
                            // ... re-add field mapping of the parent class to the the actually used class - the fields
                            // were set to declared / inherited in unsetFieldMappings(), but this was to late, after
                            // loading the metadata, so to correctly apply the changes, we have to re-add the mapping
                            // here
                            foreach ($metadata->fieldMappings as $name => $mapping) {
                                if (isset($mapping['declared']) && $mapping['declared'] == $parentClass) {
                                    // remove existing mapping for these fields
                                    unset($metadata->fieldMappings[$mapping['fieldName']]);
                                    unset($metadata->columnNames[$mapping['fieldName']]);
                                    unset($metadata->fieldNames[$mapping['columnName']]);

                                    // re-add mapping for these fields (as if it were fields of the class itself, not
                                    // inherited fields)
                                    // keep declared flag and unset inherited flag!
                                    unset($mapping['inherited']);
                                    $metadata->mapField($mapping);
                                }
                            }
                            foreach ($parentMetadata->reflFields as $name => $field) {
                                if (!isset($metadata->reflFields[$name])) {
                                    $metadata->reflFields[$name] = $field;
                                }
                            }
                            if ($parentMetadata->generatorType != ClassMetadata::GENERATOR_TYPE_NONE
                                && $metadata->generatorType == ClassMetadata::GENERATOR_TYPE_NONE)
                            {
                                $metadata->setIdGeneratorType($parentMetadata->generatorType);
                                $this->completeIdGeneratorMapping($metadata, $em);
                            }
                        }
                    }
                }
            }
        }
    }

    protected function unsetAssociationMappings(ClassMetadataInfo $metadata)
    {
        if ($this->classIsOverridden($metadata->getName())) {
            // remove all association mappings from mapped super classes, which are not allowed to have association mappings
            foreach ($metadata->getAssociationMappings() as $name => $mapping) {
                //if ($this->typeIsRelation($mapping['type'])) {
                    unset($metadata->associationMappings[$name]);
                //}
            }
        }
    }

    protected function unsetFieldMappings(ClassMetadataInfo $metadata, $wasMappedSuperclass)
    {
        // if class is overridden and class is not the interface itself ...
        // (class is "in between" in a multi level inheritance)
        if ($this->classIsOverridden($metadata->getName()) && !isset($this->overriddenEntities[$metadata->getName()])) {
            // ... unset all mapped fields (otherwise a MappingException "Duplicate definition of column" will be thrown
            // when loading the metadata of the actually used class)
            // it will later be added as mapping for the actually used class in setFieldMappings()
            foreach ($metadata->fieldMappings as $name => $mapping) {
                if (!isset($mapping['declared']) || $mapping['declared'] === $metadata->getName()) {
                    unset($metadata->fieldMappings[$mapping['fieldName']]);
                    unset($metadata->columnNames[$mapping['fieldName']]);
                    unset($metadata->fieldNames[$mapping['columnName']]);
                }
            }
        }

        // if class is overridden and class is the interface itself, but was originally not defined as mapped
        // superclass ... (class was set to isMappedSuperclass = true by this listener)
        else if ($this->classIsOverridden($metadata->getName()) && !$wasMappedSuperclass) {

            // ... set fields to declared / inherited to avoid MappingException "Duplicate definition of column" when
            // loading the metadata of sub classes (this only happens if this class originally was defined as entity but
            // changed to a mapped superclass by this listener - it is actually a "hack" to fix it like that but we did
            // not find a better way to get this working)
            // to make this work correctly, these fields will later be re-added in setFieldMappings() to the actual
            // class (but we will keep the declared / inherited flags this time)
            foreach ($metadata->fieldMappings as $name => $mapping) {
                if (!isset($mapping['declared'])) { // only if not already set
                    if (!$metadata->getReflectionClass()->getProperty($name)->isPrivate()) {
                        $metadata->fieldMappings[$mapping['fieldName']]['declared'] = $metadata->getName();
                        $metadata->fieldMappings[$mapping['fieldName']]['inherited'] = $metadata->getName();
                    }
                }
            }
        }

        return;
    }

    protected function unsetOverriddenFieldMappings(ClassMetadataInfo $metadata, $configuration)
    {
        if ($this->classIsOverridden($metadata->getName())) {
            $allOverridingClasses = $this->getAllOverridingClasses($metadata->getName());

            foreach($allOverridingClasses as $overridingClass) {
                $overridingMetadata = $this->getClassMetadata($overridingClass, $configuration);
                if (in_array($overridingClass, $configuration->getMetadataDriverImpl()->getAllClassNames())) {
                    $configuration->getMetadataDriverImpl()->loadMetadataForClass($overridingClass, $overridingMetadata);

                    foreach ($metadata->fieldMappings as $name => $mapping) {
                        if ($overridingMetadata->hasField($name) && !$overridingMetadata->isInheritedField($name)) {
                            unset($metadata->fieldMappings[$name]);
                        }
                    }
                }
            }
        }
    }

    protected function updateAssociationMappingsToMappedSuperclasses(ClassMetadataInfo $metadata)
    {
        // update association mappings of other classes, which are pointing to a class which is overridden
        foreach ($metadata->getAssociationMappings() as $name => $mapping) {
            if (isset($mapping['targetEntity']) && $this->classIsOverridden($mapping['targetEntity'])) {
                $overridingClass = $this->getOverridingClass($mapping['targetEntity']);

                // update targetEntity of association mapping
                $mapping['targetEntity'] = $overridingClass;
                $metadata->associationMappings[$name] = $mapping;
            }
        }
    }

    protected function classIsOverridden($className)
    {
        foreach ($this->overriddenEntities as $interface => $class) {
            $interface = $this->getInterface($interface);
            $class = $this->getClass($class);

            if ($interface === $className) {
                return true;
            }

            foreach($this->parentClassesByClass[$class] as $parentClass) {
                if ($parentClass === $className) {
                    return true;
                }
            }
        }

        return false;
    }

    protected function getOverridingClass($className)
    {
        foreach ($this->overriddenEntities as $interface => $class) {
            $interface = $this->getInterface($interface);
            $class = $this->getClass($class);

            if ($interface === $className) {
                return $class;
            }

            foreach($this->parentClassesByClass[$class] as $parentClass) {
                if ($parentClass === $className) {
                    return $class;
                }
            }
        }

        return null;
    }

    protected function getAllOverridingClasses($className)
    {
        $overridingClass = $this->getOverridingClass($className);
        $allOverridingClasses = [ $overridingClass ];
        foreach($this->parentClassesByClass[$overridingClass] as $parentClass) {
            if ($parentClass == $className) {
                break;
            }

            array_unshift($allOverridingClasses, $parentClass);
        }

        return $allOverridingClasses;
    }

    protected function typeIsRelation($type)
    {
        return in_array(
            $type,
            array(
                ClassMetadataInfo::MANY_TO_MANY,
                ClassMetadataInfo::ONE_TO_MANY,
                ClassMetadataInfo::ONE_TO_ONE
            ),
            true
        );
    }

    protected function getClassMetadata($class, $configuration)
    {
        $metadata = new ClassMetadata(
            $class,
            $configuration->getNamingStrategy()
        );

        return $metadata;
    }

    /**
     * Completes the ID generator mapping. If "auto" is specified we choose the generator
     * most appropriate for the targeted database platform.
     *
     * Most of the code in this method is a copy of the code from
     * vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php.
     *
     * @return void
     *
     * @throws ORMException
     */
    protected function completeIdGeneratorMapping(ClassMetadataInfo $class, EntityManager $em)
    {
        $idGenType = $class->generatorType;
        if ($idGenType == ClassMetadata::GENERATOR_TYPE_AUTO) {
            if ($em->getConnection()->getDatabasePlatform()->prefersSequences()) {
                $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_SEQUENCE);
            } else if ($em->getConnection()->getDatabasePlatform()->prefersIdentityColumns()) {
                $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
            } else {
                $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_TABLE);
            }
        }

        // Create & assign an appropriate ID generator instance
        switch ($class->generatorType) {
            case ClassMetadata::GENERATOR_TYPE_IDENTITY:
                // For PostgreSQL IDENTITY (SERIAL) we need a sequence name. It defaults to
                // <table>_<column>_seq in PostgreSQL for SERIAL columns.
                // Not pretty but necessary and the simplest solution that currently works.
                $sequenceName = null;
                $fieldName    = $class->identifier ? $class->getSingleIdentifierFieldName() : null;

                if ($em->getConnection()->getDatabasePlatform() instanceof Platforms\PostgreSQLPlatform) {
                    $columnName     = $class->getSingleIdentifierColumnName();
                    $quoted         = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']);
                    $sequenceName   = $class->getTableName() . '_' . $columnName . '_seq';
                    $definition     = array(
                        'sequenceName' => $em->getConnection()->getDatabasePlatform()->fixSchemaElementName($sequenceName)
                    );

                    if ($quoted) {
                        $definition['quoted'] = true;
                    }

                    $sequenceName = $em
                        ->getConfiguration()
                        ->getQuoteStrategy()
                        ->getSequenceName($definition, $class, $em->getConnection()->getDatabasePlatform());
                }

                $generator = ($fieldName && $class->fieldMappings[$fieldName]['type'] === 'bigint')
                    ? new BigIntegerIdentityGenerator($sequenceName)
                    : new IdentityGenerator($sequenceName);

                $class->setIdGenerator($generator);

                break;

            case ClassMetadata::GENERATOR_TYPE_SEQUENCE:
                // If there is no sequence definition yet, create a default definition
                $definition = $class->sequenceGeneratorDefinition;

                if ( ! $definition) {
                    $fieldName      = $class->getSingleIdentifierFieldName();
                    $columnName     = $class->getSingleIdentifierColumnName();
                    $quoted         = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']);
                    $sequenceName   = $class->getTableName() . '_' . $columnName . '_seq';
                    $definition     = array(
                        'sequenceName'      => $em->getConnection()->getDatabasePlatform()->fixSchemaElementName($sequenceName),
                        'allocationSize'    => 1,
                        'initialValue'      => 1,
                    );

                    if ($quoted) {
                        $definition['quoted'] = true;
                    }

                    $class->setSequenceGeneratorDefinition($definition);
                }

                $sequenceGenerator = new \Doctrine\ORM\Id\SequenceGenerator(
                    $em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $em->getConnection()->getDatabasePlatform()),
                    $definition['allocationSize']
                );
                $class->setIdGenerator($sequenceGenerator);
                break;

            case ClassMetadata::GENERATOR_TYPE_NONE:
                $class->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());
                break;

            case ClassMetadata::GENERATOR_TYPE_UUID:
                $class->setIdGenerator(new \Doctrine\ORM\Id\UuidGenerator());
                break;

            case ClassMetadata::GENERATOR_TYPE_TABLE:
                throw new ORMException("TableGenerator not yet implemented.");
                break;

            case ClassMetadata::GENERATOR_TYPE_CUSTOM:
                $definition = $class->customGeneratorDefinition;
                if ( ! class_exists($definition['class'])) {
                    throw new ORMException("Can't instantiate custom generator : " .
                        $definition['class']);
                }
                $class->setIdGenerator(new $definition['class']);
                break;

            default:
                throw new ORMException("Unknown generator type: " . $class->generatorType);
        }
    }

    /**
     * @param string           $key
     *
     * @return string
     * @throws \InvalidArgumentException
     */
    protected function getInterface($key)
    {
        if ($this->container->hasParameter($key)) {
            return $this->container->getParameter($key);
        }

        if (interface_exists($key) || class_exists($key)) {
            return $key;
        }

        throw new \InvalidArgumentException(
            sprintf('The interface or class %s does not exists.', $key)
        );
    }

    /**
     * @param string           $key
     *
     * @return string
     * @throws \InvalidArgumentException
     */
    protected function getClass($key)
    {
        if ($this->container->hasParameter($key)) {
            return $this->container->getParameter($key);
        }

        if (class_exists($key)) {
            return $key;
        }

        throw new \InvalidArgumentException(
            sprintf('The class %s does not exists.', $key)
        );
    }
}


<?php
/*
Copyright 2002-2012 MarkLogic Corporation.  All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
namespace MarkLogic\MLPHP;

use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

/**
 * Represents a REST client.
 *
 * @package MLPHP
 * @author Mike Wooldridge <mike.wooldridge@marklogic.com>
 */
class RESTClient
{
    private $host; // @var string
    private $port; // @var int
    private $version; // @var string
    private $username; // @var string
    private $password; // @var string
    private $auth; // @var string
    private $prefix; // @var string
    private $request; // @var RESTRequest
    private $logger; // @var LoggerInterface
    static protected $extension_cache = array();

    /*
     * Create a REST client object and configure REST server and authentication information.
     *
     * @param string host The host (examples: 'localhost', 'test.marklogic.com', '192.162.5.1').
     * @param int port The port (example: 8003). Set to 0 for no port.
     * @param string path An additional path prefix.
     * @param string version The API version (example: 'v1' (default)).
     * @param string username The username for REST authentication.
     * @param string password The password for REST authentication.
     * @param string auth The authentication scheme ('basic' or 'digest' (default)).
     * @param LoggerInterface logger
     */
    public function __construct($host = '', $port = 0, $path = '', $version = '', $username = '', $password = '', $auth = 'digest',
        $logger = null)
    {
        if (is_null($logger)) {
            $this->logger = new NullLogger();
        } else {
            $this->logger = $logger;
        }
        $this->host = (string)$host;
        $this->port = (int)$port;
        $this->path = (string)$path;
        $this->version = (string)$version;
        $this->username = (string)$username;
        $this->password = (string)$password;
        switch ($auth) {
            case 'basic':
                $this->auth = CURLAUTH_BASIC;
                break;
            case 'digest':
                $this->auth = CURLAUTH_DIGEST;
                break;
            default:
                   $this->auth = CURLAUTH_ANY;
        }
        $this->prefix = '';
        // Construct: http://<host>:<port>/<path>/<version>/
        if (!empty($this->host)) {
            $this->prefix = 'http://' . $this->host;
            $this->prefix .= (!empty($this->port)) ? ':' . $this->port : '';
            $this->prefix .= (!empty($this->path)) ? '/' . $this->path : '';
            $this->prefix .= (!empty($this->version)) ? '/' . $this->version : '';
            $this->prefix .= '/';
        }
    }

    /**
     * Set the host.
     *
     * @param string host The host (examples: 'localhost', 'test.marklogic.com', '192.162.5.1').
     */
    public function setHost($host)
    {
        $this->host = $host;
    }

    /**
     * Set the port.
     *
     * @param string port The port (example: 8003).
     */
    public function setPort($port)
    {
        $this->port = $port;
    }

    /**
     * Set the path.
     *
     * @param string path The path (example: 'api').
     */
    public function setPath($path)
    {
        $this->path = $path;
    }

    /**
     * Set the version.
     *
     * @param string version The API version (example: 'v1').
     */
    public function setVersion($version)
    {
        $this->version = $version;
    }

    /**
     * Set the username.
     *
     * @param string username The username for REST authentication.
     */
    public function setUsername($username)
    {
        $this->username = $username;
    }

    /**
     * Set the password.
     *
     * @param string password The password for REST authentication.
     */
    public function setPassword($password)
    {
        $this->password = $password;
    }

    /**
     * Set the authentication scheme.
     *
     * @param string auth The authentication scheme (examples: 'basic', 'digest').
     */
    public function setAuth($auth)
    {
        $this->auth = $auth;
    }

    /**
     * Set the URL prefix. Allows you to create a custom URL when no arguments are passed at construction.
     *
     * @param string prefix The URL prefix.
     */
    public function setPrefix($prefix)
    {
        $this->prefix = $prefix;
    }

    /**
     * Get the URL prefix.
     *
     * @return string The URL prefix.
     */
    public function getPrefix()
    {
        return $this->prefix;
    }

    /**
     * Set the logger.
     *
     * @param LoggerInterface logger
     */
    public function setLogger($logger)
    {
        $this->logger = $logger;
    }

    /**
     * Get the logger.
     *
     * @return LoggerInterface
     */
    public function getLogger()
    {
        return $this->logger;
    }

    /**
     * Send a REST request.
     *
     * @param RESTRequest request A RESTRequest object
     * @return RESTResponse A RESTResponse object.
     */
    public function send($request)
    {

        $this->request = $request;

        switch (strtoupper($request->getVerb())) {
            case 'GET':
                $this->response = $this->get($request);
                break;
            case 'PUT':
                $this->response = $this->put($request);
                break;
            case 'DELETE':
                $this->response = $this->delete($request);
                break;
            case 'POST':
                $this->response = $this->post($request);
                break;
            case 'HEAD':
                $this->response = $this->head($request);
                break;
            default:
                throw new \Exception($verb . ' is an invalid or unsupported REST verb.');
        }
        return $this->response;
    }

    /**
     * Set cURL options common to all requests
     *
     * @param resource ch The cURL handle.
     * @param string url The REST URL string (example: 'documents').
     * @param array headers Optional assoc array of headers.
     * @return array An array of cURL options.
     */
    protected function setOptions(&$ch, $urlStr, $headers)
    {
        $url = $this->prefix . $urlStr; // Build full URL
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPAUTH, $this->auth);
        curl_setopt($ch, CURLOPT_USERPWD, $this->username . ':' . $this->password);

        curl_setopt($ch, CURLINFO_HEADER_OUT, true); // For debugging; TODO otherwise disable

        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true );
        curl_setopt($ch, CURLOPT_MAXREDIRS, 10); // Expose ?
        curl_setopt($ch, CURLOPT_TIMEOUT, 10); // Expose ?

        $headerArr = array();
        foreach ($headers as $key => $val) {
            $headerArr[] = $key . ':' . $val;
        }
        // If headers exist, add them
        if (count($headerArr) > 0) {
          curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArr);
        }

    }

    /**
     * Perform a GET request with cURL
     *
     * @param RESTRequest request A REST request.
     * @return RESTResponse A REST response.
     */
    public function get($request)
    {

        $ch = curl_init();

        $this->setOptions($ch, $request->getUrlStr(), $request->getHeaders());

        // Options specific to GET
        curl_setopt($ch, CURLOPT_HTTPGET, true);

        $this->logger->debug("GET");

        return $this->execute($ch);
    }

    /**
     * Perform a PUT request with cURL
     *
     * @param RESTRequest request A REST request.
     * @return RESTResponse A REST response.
     */
    public function put($request)
    {
        $this->logger->debug("PUT ");

        $ch = curl_init();

        // Handle body
        $body = $request->getBody();
        $requestLength = strlen($body);
        $fh = fopen('php://temp', 'rw');

        fwrite($fh, $body);
        rewind($fh);

        $this->setOptions($ch, $request->getUrlStr(), $request->getHeaders());

        // Options specific to PUT
        curl_setopt($ch, CURLOPT_INFILE, $fh);
        curl_setopt($ch, CURLOPT_INFILESIZE, $requestLength);
        curl_setopt($ch, CURLOPT_PUT, true);

        $ret = $this->execute($ch);

        fclose($fh);

        return $ret;
    }

    /**
     * Perform a DELETE request with cURL
     *
     * @param RESTRequest request A REST request.
     * @return RESTResponse A REST response.
     */
    public function delete($request)
    {
        $this->logger->debug("DELETE");

        $ch = curl_init();

        $this->setOptions($ch, $request->getUrlStr(), $request->getHeaders());

        // Options specific to DELETE
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');

        return $this->execute($ch);
    }

    /**
     * Perform a POST request with cURL
     *
     * @param RESTRequest request A REST request.
     * @return RESTResponse A REST response.
     */
    public function post($request)
    {
        $ch = curl_init();

        $this->setOptions($ch, $request->getUrlStr(), $request->getHeaders());

        curl_setopt($ch, CURLOPT_POST, true);

        if ($request->isWWWFormURLEncodedPost()) {

            $this->logger->debug("POST www-form-urlencoded");
            $requestBody = http_build_query($request->getParams());

            curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody);

        } else {

            $this->logger->debug("POST ");

            $requestLength = strlen($request->getBody());

            curl_setopt($ch, CURLOPT_POSTFIELDS, $request->getBody());
        }

        return $this->execute($ch);
    }

    /**
     * Perform a HEAD request with cURL
     *
     * @param RESTRequest request A REST request.
     * @return RESTResponse A REST response.
     */
    public function head($request)
    {
        $ch = curl_init();

        $this->setOptions($ch, $request->getUrlStr(), $request->getHeaders());

        // Options specific to HEAD
        curl_setopt($ch, CURLOPT_NOBODY, true);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'HEAD');

        $this->logger->debug("HEAD");

        return $this->execute($ch);
    }

    /**
     * Execute a cURL request.
     *
     * @todo Handle more response codes
     *
     * @param resource ch The REST URL string (example: 'documents')
     * @return RESTResponse A RESTResponse object.
     */
    protected function execute(&$ch)
    {
        $response = new RESTResponse();
        $curl_exec = curl_exec($ch);
        // print('******* START $curl_exec (body) *******' . PHP_EOL);
        // print_r($curl_exec);
        // print('******* END   $curl_exec (body) *******' . PHP_EOL);
        $response->setBody($curl_exec);
        $curl_getinfo = curl_getinfo($ch);
        // print('******* START $curl_getinfo *******' . PHP_EOL);
        // print_r($curl_getinfo);
        // print('******* END   $curl_getinfo *******' . PHP_EOL);
        $response->setInfo($curl_getinfo);
        //print_r($response);
        $this->logger->debug("Response code: " . $response->getHttpCode());
        if ($response->getHttpCode() === 0) {
            curl_close ($ch);
            throw new \Exception(
              'No connection: ' . $response->getUrl(),
              $response->getHttpCode()
            );
        } else if ($response->getHttpCode() >= 400) {
            curl_close ($ch);
            $this->logger->debug("HTTP Error " . $response->getHttpCode());
            throw new \Exception(
              $response->getErrorMessage(),
              $response->getHttpCode()
            );
        } else {
            curl_close ($ch);
            // print('***** RESPONSE *****' . PHP_EOL);
            // print_r($response);
            return $response;
        }
    }

    /**
     * Install a REST API XQuery extension
     *
     * @param $resource URL
     * @param $params resource parameters (adds in provider=MLPHP if provider not set)
     * @param $filename file system name of contents of the XQuery module.
     */
    public function installExtension($resource, $params, $filename)
    {
        if (!isset($params["provider"])) {
            $params["provider"] = 'MLPHP';
        }

        $path = __DIR__ . DIRECTORY_SEPARATOR . "xquery" . DIRECTORY_SEPARATOR . $filename;

        $body = file_get_contents($path);
        $key = serialize(array($this->host, $this->port, $resource, $params, $body));
        if (array_key_exists($key, self::$extension_cache)) {
            return;
        }
        $this->logger->debug("installExtension");
        $method = 'put';
        $headers = array(
            'Content-type' => 'application/xquery'
        );
        $request = new RESTRequest($method, $resource, $params, $body, $headers);
        $this->put($request);
        self::$extension_cache[$key] = 1;
    }
}