<?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;

/**
 * File trait gives common operation functions needed for files in Vars
 *
 * @since 0.1.0
 */
trait FileTrait
{
    /**
     * The file used in the trait
     *
     * @var string
     */
    private $file;

    /**
     * The parent Vars instance
     *
     * @var \M1\Vars\Vars
     */
    private $vars;

    /**
     * Validates the file passed to see if it is a file and is readable
     *
     * @throws \RuntimeException If the file passed is not a file
     * @throws \RuntimeException If the file passed is not readable
     */
    private function validate()
    {
        $file = $this->file;

        if (!is_file($file)) {
            throw new \RuntimeException(sprintf("'%s' is not a file", $file));
        }

        if (!is_readable($file)) {
            // @codeCoverageIgnoreStart
            throw new \RuntimeException(sprintf("'%s' is not a readable file", $file));
            // @codeCoverageIgnoreEnd
        }
    }

    /**
     * Gets the supported loader for the passed file
     *
     * @see \M1\Vars\Vars::getLoaders() \M1\Vars\Vars::getLoaders()
     *
     * @param string $data The passed file
     *
     * @return mixed Returns the loader class or false if no loader calls was found
     */
    private function getSupportedLoader($data)
    {
        $loaders = $this->vars->loader->getLoaders();

        foreach ($loaders as $loader) {
            $class_loader = new \ReflectionClass($loader);
            $class_loader = $class_loader->newInstanceArgs(array($data));

            if ($class_loader->supports()) {
                return $class_loader;
            }
        }
        return false;
    }

    /**
     * Loads raw content from the file
     *
     * @param string $data The passed file
     *
     * @throws \InvalidArugmentException If the file passed is not supported by the current loaders
     *
     * @return mixed The content from the file via the loader
     */
    private function loadContent($data)
    {
        $loader = $this->getSupportedLoader($data);

        if (!$loader) {
            throw new \InvalidArgumentException(sprintf("'%s' is not supported by the current loaders", $this->file));
        }

        $loader->load();
        return $loader->getContent();
    }

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


<?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\Provider\Silex;

use Silex\Application;
use Silex\ServiceProviderInterface;
use M1\Vars\Vars;

/**
 * The Silex service provider for Vars
 *
 * @since 0.1.0
 */
class VarsServiceProvider implements ServiceProviderInterface
{
    /**
     * The entity to use Vars with
     *
     * @var null
     */
    private $entity;

    /**
     * The available option keys
     *
     * @var array
     */
    private $option_keys = array(
        'cache',
        'cache_path',
        'cache_expire',
        'loaders',
        'replacements',
        'merge_globals',
    );

    /**
     * The service provider constructor sets the entity to use with vars
     *
     * @param mixed $entity The entity
     */
    public function __construct($entity = null)
    {
        $this->entity = $entity;
    }

    /**
     * Registers the service provider, sets the user defined options and returns the vars instance
     *
     * @param \Silex\Application $app The silex app
     */
    public function register(Application $app)
    {
        $app['vars'] = function ($app) {
            return new Vars($this->entity, $this->createOptions($app));
        };

        $app['vars.merge'] = $app->protect(function () use ($app) {
            static $initialized = false;
            if ($initialized) {
                return;
            }
            $initialized = true;

            foreach ($app['vars']->getGlobals() as $key => $value) {
                $app[$key] = $value;
            }

            foreach ($app['vars']->toDots(false) as $key => $value) {
                $app['vars.'.$key] = $value;
            }
        });
    }

    /**
     * Creates the defined options into a way that Vars can use
     *
     * @param \Silex\Application $app The silex app
     *
     * @return array The created options
     */
    private function createOptions($app)
    {
        $options = array();

        if (isset($app['vars.path'])) {
            $options['path'] = $app['vars.path'];
        }

        if (isset($app['vars.options'])) {
            $options = $this->createKeyedOptions($options, $app['vars.options']);
        }

        if (!isset($options['merge_globals']) || is_null($options['merge_globals'])) {
            $options['merge_globals'] = false;
        }

        if (isset($app['debug']) && $app['debug']) {
            $options['cache'] = false;
        }

        return $options;
    }

    /**
     * Registers the service provider, sets the user defined options and returns the vars instance
     *
     * @param array $options      The already parsed options
     * @param array $vars_options The options defined in the Silex app
     *
     * @return array The keyed options
     */
    private function createKeyedOptions($options, $vars_options)
    {
        foreach ($this->option_keys as $option) {
            $options[$option] = (isset($vars_options[$option])) ? $vars_options[$option] : null;
        }

        return $options;
    }

    /**
     * The silex service provider boot function
     *
     * @param \Silex\Application $app The silex app
     *
     * @codeCoverageIgnore
     */
    public function boot(Application $app)
    {
    }
}


<?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;

/**
 * The json file loader
 *
 * @since 0.1.0
 */
class JsonLoader extends AbstractLoader
{
    /**
     * {@inheritdoc}
     */
    public static $supported = array('json');

    /**
     * {@inheritdoc}
     *
     * @throws \RunntimeException If the json file fails to load
     */
    public function load()
    {
        $content = json_decode(file_get_contents($this->entity), true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            $message = function_exists('json_last_error_msg') ? json_last_error_msg() : 'Parse error';

            throw new \RuntimeException(
                sprintf("'%s' failed to load with the error '%s'", $this->entity, $message)
            );
        }

        $this->content = $content;
        return $this;
    }
}


<?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;

/**
 * The XML file loader
 *
 * @since 0.1.0
 */
class XmlLoader extends AbstractLoader
{
    /**
     * {@inheritdoc}
     */
    public static $supported = array('xml');

    /**
     * {@inheritdoc}
     *
     * @throws \RuntimeException If the xml file fails to load
     */
    public function load()
    {
        libxml_use_internal_errors(true);
        $content = simplexml_load_file($this->entity);

        if (!$content) {
            throw new \RuntimeException(
                sprintf("'%s' failed to load with the error '%s'", $this->entity, libxml_get_errors()[0]->message)
            );
        }

        $this->content = json_decode(json_encode($content), true);
        return $this;
    }
}


<?php
/**
 * Lord of the Files - Multibyte Helpers
 *
 * @package blob-mimes
 * @author  Blobfolio, LLC <hello@blobfolio.com>
 */

namespace blobfolio\wp\bm;

class mb {

	/**
	 * A strpos() wrapper
	 *
	 * @param string  $haystack Haystack.
	 * @param string  $needle Needle.
	 * @param int $offset Offset.
	 * @return int|bool  Return the first occurrence. False on error.
	 */
	public static function strpos($haystack, $needle, $offset=0) {
		if (
			(\seems_utf8($haystack) || \seems_utf8($needle)) &&
			\function_exists('mb_strpos')
		) {
			return \mb_strpos($haystack, $needle, $offset, 'UTF-8');
		}

		return \strpos($haystack, $needle, $offset);
	}

	/**
	 * A strrpos() wrapper
	 *
	 * @param string  $haystack Haystack.
	 * @param string  $needle Needle.
	 * @param int $offset Offset.
	 * @return int|bool  Return the last occurrence. False on error.
	 */
	public static function strrpos($haystack, $needle, $offset=0) {
		if (
			(\seems_utf8($haystack) || \seems_utf8($needle)) &&
			\function_exists('mb_strrpos')
		) {
			return \mb_strrpos($haystack, $needle, $offset, 'UTF-8');
		}

		return \strrpos($haystack, $needle, $offset);
	}

	/**
	 * A strtolower() wrapper
	 *
	 * @param string $str String.
	 * @return string  Return string in lower case.
	 */
	public static function strtolower($str) {
		if (\seems_utf8($str) && \function_exists('mb_strtolower')) {
			return \mb_strtolower($str, 'UTF-8');
		}

		return \strtolower($str);
	}

	/**
	 * A substr() wrapper
	 *
	 * @param string  $str String.
	 * @param string  $start From.
	 * @param int $length To.
	 * @return string|bool  Return the substring. False on failure.
	 */
	public static function substr($str, $start = 0, $length = null) {
		if (\seems_utf8($str) && \function_exists('mb_substr')) {
			return \mb_substr($str, $start, $length, 'UTF-8');
		}

		return \substr($str, $start, $length);
	}

}


<?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;

/**
 * The PHP file loader
 *
 * @since 0.1.0
 */
class PhpLoader extends AbstractLoader
{
    /**
     * {@inheritdoc}
     */
    public static $supported = array('php', 'php5');

    /**
     * {@inheritdoc}
     *
     * @throws \RuntimeException If the php file doesn't return an array
     */
    public function load()
    {
        $content = require $this->entity;

        if (is_callable($content)) {
            $content = call_user_func($content);
        }

        if (!is_array($content)) {
            throw new \RuntimeException(sprintf("'%s' does not return an array", $this->entity));
        }

        $this->content = $content;
        return $this;
    }
}


<?php
/**
 * Lord of the Files - SVG base class
 *
 * This is extended by either DOMDocument or the fallback.
 *
 * @package blob-mimes
 * @author  Blobfolio, LLC <hello@blobfolio.com>
 */

namespace blobfolio\wp\bm\svg;

use \blobfolio\wp\bm\mb;

abstract class svg_base {

	const XMLNS_NAMESPACE = 'http://www.w3.org/2000/svg';
	const XMLTAG = '<?xml version="1.0" encoding="utf-8" ?>';

	/**
	 * Return SVG DOCTYPE
	 *
	 * Defaults to 1.1 spec.
	 *
	 * @see { https://www.w3.org/TR/SVG/ }
	 *
	 * @return string DOCTYPE.
	 */
	public static function get_doctype() {
		$doctype = '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';

		$doctype = apply_filters('blobmimes_svg_doctype', $doctype);
		return is_string($doctype) ? trim($doctype) : '';
	}

	/**
	 * Return whitelisted tags
	 *
	 * Based on SVG spec, minus anything scripty.
	 *
	 * @see { https://developer.mozilla.org/en-US/docs/Web/SVG/Element }
	 *
	 * @return array Array of valid tags.
	 */
	public static function get_allowed_tags() {
		$tags = array(
			'a',
			'altGlyph',
			'altGlyphDef',
			'altGlyphItem',
			'animate',
			'animateColor',
			'animateMotion',
			'animateTransform',
			'audio',
			'canvas',
			'circle',
			'clipPath',
			'color-profile',
			'cursor',
			'defs',
			'desc',
			'discard',
			'ellipse',
			'feBlend',
			'feColorMatrix',
			'feComponentTransfer',
			'feComposite',
			'feConvolveMatrix',
			'feDiffuseLighting',
			'feDisplacementMap',
			'feDistantLight',
			'feDropShadow',
			'feFlood',
			'feFuncA',
			'feFuncB',
			'feFuncG',
			'feFuncR',
			'feGaussianBlur',
			'feImage',
			'feMerge',
			'feMergeNode',
			'feMorphology',
			'feOffset',
			'fePointLight',
			'feSpecularLighting',
			'feSpotLight',
			'feTile',
			'feTurbulence',
			'filter',
			'font',
			'font-face',
			'font-face-format',
			'font-face-name',
			'font-face-src',
			'font-face-uri',
			'g',
			'glyph',
			'glyphRef',
			'hatch',
			'hatchpath',
			'hkern',
			'image',
			'line',
			'linearGradient',
			'marker',
			'mask',
			'mesh',
			'meshgradient',
			'meshpatch',
			'meshrow',
			'metadata',
			'missing-glyph',
			'mpath',
			'path',
			'pattern',
			'polygon',
			'polyline',
			'radialGradient',
			'rect',
			'set',
			'solidcolor',
			'stop',
			'style',
			'svg',
			'switch',
			'symbol',
			'text',
			'textPath',
			'title',
			'tref',
			'tspan',
			'unknown',
			'use',
			'video',
			'view',
			'vkern',
		);

		$tags = apply_filters('blobmimes_svg_allowed_tags', $tags);
		if (!is_array($tags) || !count($tags)) {
			return array();
		}

		// Convert to lowercase for easier comparison.
		foreach ($tags as $k=>$v) {
			$tags[$k] = mb::strtolower(trim($v));
			if (!strlen($tags[$k])) {
				unset($tags[$k]);
			}
		}

		// Pre-sorting will speed up looped checks.
		$tags = array_unique($tags);
		sort($tags);
		return $tags;
	}

	/**
	 * Return whitelisted attributes
	 *
	 * Based on SVG spec, minus anything scripty.
	 *
	 * @see { https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute }
	 *
	 * @return array Array of valid attributes.
	 */
	public static function get_allowed_attributes() {
		$attributes = array(
			'accent-height',
			'accumulate',
			'additive',
			'alignment-baseline',
			'allowReorder',
			'alphabetic',
			'amplitude',
			'arabic-form',
			'ascent',
			'attributeName',
			'attributeType',
			'autoReverse',
			'azimuth',
			'baseFrequency',
			'baseline-shift',
			'baseProfile',
			'bbox',
			'begin',
			'bias',
			'by',
			'calcMode',
			'cap-height',
			'class',
			'clip',
			'clipPathUnits',
			'clip-path',
			'clip-rule',
			'color',
			'color-interpolation',
			'color-interpolation-filters',
			'color-profile',
			'color-rendering',
			'contentStyleType',
			'cursor',
			'cx',
			'cy',
			'd',
			'decelerate',
			'descent',
			'diffuseConstant',
			'direction',
			'display',
			'divisor',
			'dominant-baseline',
			'dur',
			'dx',
			'dy',
			'edgeMode',
			'elevation',
			'enable-background',
			'end',
			'exponent',
			'externalResourcesRequired',
			'fill',
			'fill-opacity',
			'fill-rule',
			'filter',
			'filterRes',
			'filterUnits',
			'flood-color',
			'flood-opacity',
			'font-family',
			'font-size',
			'font-size-adjust',
			'font-stretch',
			'font-style',
			'font-variant',
			'font-weight',
			'format',
			'from',
			'fx',
			'fy',
			'g1',
			'g2',
			'glyph-name',
			'glyph-orientation-horizontal',
			'glyph-orientation-vertical',
			'glyphRef',
			'gradientTransform',
			'gradientUnits',
			'hanging',
			'height',
			'href',
			'horiz-adv-x',
			'horiz-origin-x',
			'id',
			'ideographic',
			'image-rendering',
			'in',
			'in2',
			'intercept',
			'k',
			'k1',
			'k2',
			'k3',
			'k4',
			'kernelMatrix',
			'kernelUnitLength',
			'kerning',
			'keyPoints',
			'keySplines',
			'keyTimes',
			'lang',
			'lengthAdjust',
			'letter-spacing',
			'lighting-color',
			'limitingConeAngle',
			'local',
			'marker-end',
			'marker-mid',
			'marker-start',
			'markerHeight',
			'markerUnits',
			'markerWidth',
			'mask',
			'maskContentUnits',
			'maskUnits',
			'mathematical',
			'max',
			'media',
			'method',
			'min',
			'mode',
			'name',
			'numOctaves',
			'offset',
			'opacity',
			'operator',
			'order',
			'orient',
			'orientation',
			'origin',
			'overflow',
			'overline-position',
			'overline-thickness',
			'panose-1',
			'paint-order',
			'pathLength',
			'patternContentUnits',
			'patternTransform',
			'patternUnits',
			'pointer-events',
			'points',
			'pointsAtX',
			'pointsAtY',
			'pointsAtZ',
			'preserveAlpha',
			'preserveAspectRatio',
			'primitiveUnits',
			'r',
			'radius',
			'refX',
			'refY',
			'rendering-intent',
			'repeatCount',
			'repeatDur',
			'requiredExtensions',
			'requiredFeatures',
			'restart',
			'result',
			'rotate',
			'rx',
			'ry',
			'scale',
			'seed',
			'shape-rendering',
			'slope',
			'spacing',
			'specularConstant',
			'specularExponent',
			'speed',
			'spreadMethod',
			'startOffset',
			'stdDeviation',
			'stemh',
			'stemv',
			'stitchTiles',
			'stop-color',
			'stop-opacity',
			'strikethrough-position',
			'strikethrough-thickness',
			'string',
			'stroke',
			'stroke-dasharray',
			'stroke-dashoffset',
			'stroke-linecap',
			'stroke-linejoin',
			'stroke-miterlimit',
			'stroke-opacity',
			'stroke-width',
			'style',
			'surfaceScale',
			'systemLanguage',
			'tabindex',
			'tableValues',
			'target',
			'targetX',
			'targetY',
			'text-anchor',
			'text-decoration',
			'text-rendering',
			'textLength',
			'to',
			'transform',
			'type',
			'u1',
			'u2',
			'underline-position',
			'underline-thickness',
			'unicode',
			'unicode-bidi',
			'unicode-range',
			'units-per-em',
			'v-alphabetic',
			'v-hanging',
			'v-ideographic',
			'v-mathematical',
			'values',
			'version',
			'vert-adv-y',
			'vert-origin-x',
			'vert-origin-y',
			'viewBox',
			'viewTarget',
			'visibility',
			'width',
			'widths',
			'word-spacing',
			'writing-mode',
			'x',
			'x-height',
			'x1',
			'x2',
			'xChannelSelector',
			'xlink:actuate',
			'xlink:arcrole',
			'xlink:href',
			'xlink:role',
			'xlink:show',
			'xlink:title',
			'xlink:type',
			'xml:base',
			'xml:lang',
			'xml:space',
			'xmlns',
			'xmlns:xlink',
			'xmlns:xml',
			'y',
			'y1',
			'y2',
			'yChannelSelector',
			'z',
			'zoomAndPan',
		);

		$attributes = apply_filters('blobmimes_svg_allowed_attributes', $attributes);
		if (!is_array($attributes) || !count($attributes)) {
			return array();
		}

		// Convert to lowercase for easier comparison.
		foreach ($attributes as $k=>$v) {
			$attributes[$k] = mb::strtolower(trim($v));
			if (!strlen($attributes[$k])) {
				unset($attributes[$k]);
			}
		}

		// Pre-sorting will speed up looped checks.
		$attributes = array_unique($attributes);
		sort($attributes);
		return $attributes;

	}

	/**
	 * Return IRI attributes
	 *
	 * @see { https://www.w3.org/TR/SVG/linking.html }
	 *
	 * @return array Array of possible attributes.
	 */
	public static function get_iri_attributes() {
		return array(
			'href',
			'src',
			'xlink:arcrole',
			'xlink:href',
			'xlink:role',
			'xml:base',
			'xmlns',
			'xmlns:xlink',
		);
	}

	/**
	 * Return whitelisted protocols
	 *
	 * @see { http://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml }
	 *
	 * @return array Array of valid protocols.
	 */
	public static function get_allowed_protocols() {
		$protocols = array(
			'http',
			'https',
		);

		$protocols = apply_filters('blobmimes_svg_allowed_protocols', $protocols);
		if (!is_array($protocols) || !count($protocols)) {
			return array();
		}

		// Convert to lowercase for easier comparison.
		foreach ($protocols as $k=>$v) {
			$protocols[$k] = mb::strtolower(trim($v));
			if (!strlen($protocols[$k])) {
				unset($protocols[$k]);
			}
		}

		// Pre-sorting will speed up looped checks.
		$protocols = array_unique($protocols);
		sort($protocols);
		return $protocols;
	}

	/**
	 * Return whitelisted domains
	 *
	 * @return array Domains.
	 */
	public static function get_allowed_domains() {
		$domains = array(
			site_url(),
			'creativecommons.org',
			'inkscape.org',
			'sodipodi.sourceforge.net',
			'w3.org',
		);

		$domains = apply_filters('blobmimes_svg_allowed_domains', $domains);
		if (!is_array($domains) || !count($domains)) {
			return array();
		}

		// Sanitize the list before moving on.
		foreach ($domains as $k=>$domain) {
			if (false === ($domains[$k] = static::sanitize_domain($domain))) {
				unset($domains[$k]);
			}
		}

		// Pre-sorting will speed up looped checks.
		$domains = array_unique($domains);
		sort($domains);
		return $domains;
	}

	/**
	 * Sanitize a domain-like string for more
	 * level comparison.
	 *
	 * @param string $domain Domain.
	 * @return string|bool Domain. False on failure.
	 */
	public static function sanitize_domain($domain = '') {
		if (!is_string($domain)) {
			return false;
		}

		$domain = mb::strtolower(trim($domain));

		// Try to parse a full URL.
		$host = wp_parse_url($domain, PHP_URL_HOST);
		if (!$host) {
			// Strip path?
			if (false !== ($start = mb::strpos($domain, '/'))) {
				$domain = mb::substr($domain, 0, $start);
			}

			// Strip query?
			if (false !== ($start = mb::strpos($domain, '?'))) {
				$domain = mb::substr($domain, 0, $start);
			}

			// Strip port?
			if (
				!filter_var($domain, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) &&
				false !== ($start = mb::strpos($domain, ':'))
			) {
				$domain = mb::substr($domain, 0, $start);
			}

			// Bail if invalid characters at this point.
			if (filter_var($domain, FILTER_SANITIZE_URL) !== $domain) {
				return false;
			}
		} else {
			$domain = $host;
		}

		if (is_string($domain)) {
			$domain = preg_replace('/^www\./u', '', $domain);
		}

		return $domain ? $domain : false;
	}

	/**
	 * Sanitize attribute value
	 *
	 * @param string $str Attribute value.
	 * @return string Attribute value.
	 */
	protected static function sanitize_attribute_value($str = '') {
		$str = wp_kses_no_null($str);

		// Decode all entities.
		$old_value = '';
		while ($old_value !== $str) {
			$old_value = $str;
			$str = wp_kses_decode_entities($str);
			$str = html_entity_decode($str, ENT_QUOTES, 'UTF-8');

			// And (full) trim while we're here.
			$str = preg_replace('/^\s+/u', '', $str);
			$str = preg_replace('/\s+$/u', '', $str);
		}

		return $str;
	}

	/**
	 * Sanitize IRI value
	 *
	 * @param string $str IRI value.
	 * @return string IRI value.
	 */
	protected static function sanitize_iri_value($str = '') {
		$allowed_protocols = static::get_allowed_protocols();
		$allowed_domains = static::get_allowed_domains();

		if (wp_kses_bad_protocol($str, $allowed_protocols) !== $str) {
			return '';
		}

		// Assign a protocol.
		$str = preg_replace('/^\/\//', 'https://', $str);

		// Is this a URLish thing?
		if (filter_var($str, FILTER_SANITIZE_URL) !== $str) {
			return '';
		}

		// Check the domain, if applicable.
		if (preg_match('/^[\w\d]+:\/\//i', $str)) {
			if (false !== ($domain = static::sanitize_domain($str))) {
				if (!in_array($domain, $allowed_domains, true)) {
					return '';
				}
			} else {
				return '';
			}
		}

		return $str;
	}

	/**
	 * Callback handler for sanitizing CSS URLs
	 *
	 * @param array $match Match.
	 * @return string Sanitized URL.
	 */
	protected static function callback_sanitize_css_iri($match) {
		$str = static::sanitize_attribute_value($match[1]);

		// Strip quotes.
		$str = ltrim($str, "'\"");
		$str = rtrim($str, "'\"");

		$str = static::sanitize_iri_value($str);

		if (strlen($str)) {
			return "url('$str')";
		}

		return 'none';
	}

	/**
	 * Read an SVG file as contents or path.
	 *
	 * @param string $svg SVG code or path.
	 * @return string|bool SVG code. False on failure.
	 */
	protected static function load_svg($svg = '') {
		// Early bail.
		if (!is_string($svg) || !strlen($svg)) {
			return false;
		}

		// Maybe a path?
		if (
			false === mb::strpos(mb::strtolower($svg), '<svg') ||
			false === mb::strrpos(mb::strtolower($svg), '</svg>')
		) {
			try {
				// @codingStandardsIgnoreStart
				$svg = @file_get_contents($svg);
				// @codingStandardsIgnoreEnd
				if (!is_string($svg) || !strlen($svg)) {
					return false;
				}
			} catch (\Throwable $e) {
				return false;
			} catch (\Exception $e) {
				return false;
			}
		}

		// Lowercase the tags.
		$svg = preg_replace('/<svg/ui', '<svg', $svg);
		$svg = preg_replace('/<\/svg>/ui', '</svg>', $svg);

		if (
			false === ($start = mb::strpos($svg, '<svg')) ||
			false === ($end = mb::strrpos($svg, '</svg>'))
		) {
			return false;
		}
		$svg = mb::substr($svg, $start, ($end - $start + 6));

		// Remove comments.
		$svg = static::strip_comments($svg);

		return $svg;
	}

	/**
	 * Remove comments and interpreter tags from an SVG file
	 *
	 * @param string $svg SVG code or path.
	 * @return string|bool SVG code. False on failure.
	 */
	protected static function strip_comments($svg = '') {
		if (!is_string($svg)) {
			return false;
		}

		// Remove XML, PHP, ASP, etc.
		$svg = preg_replace('/<\?(.*)\?>/Us', '', $svg);
		$svg = preg_replace('/<\%(.*)\%>/Us', '', $svg);

		if (
			false !== mb::strpos($svg, '<?') ||
			false !== mb::strpos($svg, '<%')
		) {
			return false;
		}

		// Remove comments.
		$svg = preg_replace('/<!--(.*)-->/Us', '', $svg);
		$svg = preg_replace('/\/\*(.*)\*\//Us', '', $svg);

		if (
			false !== mb::strpos($svg, '<!--') ||
			false !== mb::strpos($svg, '/*')
		) {
			return false;
		}

		return $svg;
	}
}


<?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;

/**
 * This trait manages the resource flags that you can use
 *
 * @since 0.3.0
 */
trait ResourceFlagsTrait
{
    /**
     * Explodes the ?: flag into pieces
     *
     * @param string $resource The resource string
     *
     * @return array The parsed resources
     */
    protected function explodeResourceIfElse($resource)
    {
        $resource = explode("?:", $resource, 2);
        $resources = array();

        foreach ($resource as $r) {
            $resources[] = trim($r);
        }

        return $resources;
    }

    /**
     * Returns the array pieces of the exploded ?: into a string
     *
     * @param array $resource The resource array
     *
     * @return string The parsed string resource
     */
    protected function implodeResourceIfElse($resource)
    {
        if (count($resource) > 1) {
            return implode(" ?: ", $resource);
        }

        return $resource[0];
    }

    /**
     * Returns trimmed resource string
     *
     * @param string $resource The resource
     *
     * @return string The parsed string resource
     */
    protected function trimFlags($resource)
    {
        $resource = trim($resource, '@');
        $resource = trim($resource, '*');

        return $resource;
    }

    /**
     * Duplicates the flags from one string to another
     *
     * @param string $resource The resource to add the flags to
     * @param string $resource The resource to duplicate the flags from
     *
     * @return string The parsed string resource
     */
    protected function replicateFlags($resource, $original_resource)
    {
        return sprintf(
            '%s%s%s',
            ($this->checkSuppression($original_resource)) ? '@' : null,
            $resource,
            ($this->checkRecursive($original_resource)) ? '*' : null
        );
    }

    /**
     * Checks to see if the recursive flag is set
     *
     * @param string $resource The resource
     *
     * @return bool Is the recursive flag set
     */
    private function checkRecursive($resource)
    {
        return substr($resource, -1) === "*";
    }

    /**
     * Checks to see if the file not found exception is suppressed
     *
     * @param string $resource The resource
     *
     * @return bool Is the exception suppressed
     */
    private function checkSuppression($resource)
    {
        return substr($resource, 0, 1) === "@";
    }
}


<?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 the loaders and extensions supported by Vars
 *
 * @since 0.1.1
 */
class LoaderProvider
{
    /**
     * The available extensions
     *
     * @var array $extensions
     */
    private $extensions = array();

    /**
     * The available loaders
     *
     * @var array $loaders
     */
    private $loaders = array();

    /**
     * The constructor for LoaderProvider, makes the loaders and extensions
     *
     * @param array|null $options           The options being used for Vars
     * @param array      $default_loaders   The default loaders for Vars
     */
    public function __construct($options, $default_loaders)
    {
        $this->makeLoaders($options, $default_loaders);
    }

    /**
     * Get loaders and make extensions for the loaders
     *
     * @param array|null $options           The options being used for Vars
     * @param array      $default_loaders   The default loaders for Vars
     */
    private function makeLoaders($options, $default_loaders)
    {
        $loaders = $this->preParseLoaders($options, $default_loaders);
        $parsed_loaders = array();

        foreach ($loaders as $loader) {
            if ($loader === 'default') {
                $parsed_loaders = array_merge($parsed_loaders, $default_loaders);
            } else {
                $parsed_loaders[] = $loader;
            }
        }

        $parsed_loaders = array_unique($parsed_loaders);

        $this->loaders = $this->makeNameSpaceLoaders($parsed_loaders, $default_loaders);
        $this->extensions = $this->makeExtensions($this->loaders);
    }

    /**
     * Pre parse the loaders for use in make loaders
     *
     * @param array|null $options           The options being used for Vars
     * @param array      $default_loaders   The default loaders for Vars
     *
     * @return array The pre parsed loaders
     */
    private function preParseLoaders($options, $default_loaders)
    {
        $loaders = array();

        if (is_array($options['loaders']) && !empty($options['loaders'])) {
            $loaders = $options['loaders'];
        } elseif (is_string($options['loaders'])) {
            $loaders[] = $options['loaders'];
        } else {
            $loaders = $default_loaders;
        }

        return $loaders;
    }

    /**
     * Makes namespace loaders from loader strings
     *
     * @param array $loaders The options being used for Vars
     * @param array      $default_loaders   The default loaders for Vars
     *
     * @throws \InvalidArgumentException If a loader from options isn't found
     *
     * @return array The namespace loaders
     */
    private function makeNameSpaceLoaders($loaders, $default_loaders)
    {
        $parsed_loaders = array();

        foreach ($loaders as $loader) {
            if (in_array($loader, $default_loaders)) {
                $loader = sprintf('%s\%sLoader', __NAMESPACE__, ucfirst(strtolower($loader)));
            }

            if (!class_exists($loader)) {
                throw new \InvalidArgumentException(sprintf("'%s' loader class does not exist", $loader));
            }

            $parsed_loaders[] = $loader;
        }

        return $parsed_loaders;
    }

    /**
     * Get and make extensions for loaders made from makeLoaders()
     *
     * @see \M1\Vars\Vars::makeLoaders() \M1\Vars\Vars::makeLoaders()
     *
     * @param  array $loaders File loaders
     *
     * @throws \RuntimeException If no loader extensions were found
     *
     * @return array File loader supported extensions
     */
    private function makeExtensions(array $loaders)
    {
        $extensions = array();

        foreach ($loaders as $loader) {
            $extensions = array_merge($extensions, $loader::$supported);
        }

        if (empty($extensions)) {
            throw new \RuntimeException('No loader extensions were found');
        }

        return $extensions;
    }

    /**
     * Get the Vars file loaders
     *
     * @return array The Vars file loaders
     */
    public function getLoaders()
    {
        return $this->loaders;
    }

    /**
     * Get the Vars file loaders extensions
     *
     * @return array The Vars file loader extensions
     */
    public function getExtensions()
    {
        return $this->extensions;
    }
}


<?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;

use M1\Vars\Cache\CacheProvider;
use M1\Vars\Loader\LoaderProvider;
use M1\Vars\Resource\AbstractResource;
use M1\Vars\Resource\ResourceProvider;
use M1\Vars\Traits\PathTrait;
use M1\Vars\Traits\TransformerTrait;
use M1\Vars\Variables\VariableProvider;

/**
 * Vars core class
 *
 * @since 0.1.0
 */
class Vars extends AbstractResource
{
    /**
     * Used for path functions and variables
     */
    use PathTrait;

    /**
     * Used for to* functions
     */
    use TransformerTrait;

    /**
     * The cache object if the cache is wanted, else false
     *
     * @var \M1\Vars\Cache\CacheProvider $cache
     */
    public $cache;

    /**
     * The default options for Vars
     *
     * @var array $default_options
     */
    private $default_options = array(
        'path' => null,
        'cache' => true,
        'cache_path' => null,
        'cache_expire' => 300, // 5 minutes
        'loaders' => array('env', 'ini', 'json', 'php', 'toml', 'yaml', 'xml',),
        'merge_globals' => true,
    );

    /**
     * The global file variables
     *
     * @var array globals
     */
    private $globals = array();

    /**
     * The loaderProvider for Vars supplies the file loaders and the extensions that are supported
     *
     * @var \M1\Vars\Loader\LoaderProvider $loader
     */
    public $loader;

    /**
     * Have the base and cache paths been set
     *
     * @var bool $paths_loaded
     */
    private $paths_loaded = false;

    /**
     * The imported resources
     *
     * @var array $resources
     */
    private $resources = array();

    /**
     * The variable provider
     *
     * @var \M1\Vars\Variables\VariableProvider $variables
     */
    public $variables;

    /**
     * Creates a new instance of Vars
     *
     * @param string|array $resource The main configuration resource
     * @param array        $options  The options being used for Vars
     */
    public function __construct($resource, $options = array())
    {
        $options = $this->parseOptions($options);
        $this->makeCache($options, $resource);
        $this->makePaths($options);
        if (!$this->cache->checkCache()) {
            $this->makeLoader($options);
            $this->makeVariables($options);

            $resource = new ResourceProvider($this, $resource);
        }

        if ($this->cache->isHit()) {
            $this->loadFromCache();
        } else {
            $resource->mergeParentContent();
            $this->content = $this->mergeGlobals($resource->getContent(), $options);
            $this->cache->setTime(time());
            $this->cache->makeCache($this);
        }
    }

    /**
     * Parses the options so Vars can use them
     *
     * @param array $options  The options being used for Vars
     *
     * @return array The parsed options
     */
    private function parseOptions(array $options)
    {
        $parsed_options = array_merge($this->default_options, $options);
        $parsed_options['loaders'] = (isset($options['loaders'])) ?
            $options['loaders'] : $this->default_options['loaders'];

        return $parsed_options;
    }

    /**
     * Makes the CacheProvider with the options
     *
     * @param array        $options  The options being used for Vars
     * @param array|string $resource The main configuration resource
     */
    private function makeCache($options, $resource)
    {
        $cache = new CacheProvider($resource, $options);
        $this->cache = $cache;
    }

    /**
     * Sets the base path if the options have been set and the cache path if the cache path has not been set but the
     * base path has
     *
     * @param array $options The options being used for Vars
     */
    private function makePaths($options)
    {
        $this->setPath($options['path']);

        if (is_null($options['cache_path']) && !is_null($options['path'])) {
            $this->cache->setPath($options['path']);
            $this->paths_loaded = true;
        }
    }

    /**
     * Makes the LoaderProvider with the options
     *
     * @param array $options  The options being used for Vars
     */
    private function makeLoader($options)
    {
        $loader = new LoaderProvider($options, $this->default_options['loaders']);
        $this->loader = $loader;
    }

    /**
     * Sets the replacement variables if the option has been set
     *
     * @param array|null $options The options being used for Vars
     */
    private function makeVariables($options)
    {
        $this->variables = new VariableProvider($this);

        if (isset($options['replacements'])) {
            $this->variables->rstore->load($options['replacements']);
        }
    }

    /**
     * Loads the cached file into the current class
     */
    private function loadFromCache()
    {
        $this->cache->load();

        $passed_keys = array(
            'path',
            'content',
            'extensions',
            'loaders',
            'resources',
            'replacements',
            'globals',
        );

        $loaded_vars = get_object_vars($this->cache->getLoadedVars());

        foreach ($loaded_vars as $key => $value) {
            if (in_array($key, $passed_keys)) {
                $this->$key = $value;
            }
        }

        $this->cache->setTime($loaded_vars['cache']->getTime());
    }

    /**
     * Checks if the base and cache paths have been set, if not\ set then will use the $resource as the base path
     *
     * @param string $resource The resource to use to set the paths if they haven't been set
     */
    public function pathsLoadedCheck($resource)
    {
        if (!$this->paths_loaded) {
            $path = $this->getPath();

            if (!$path) {
                $file = pathinfo(realpath($resource));
                $path = $file['dirname'];
                $this->setPath($path);
            }

            if ($this->cache->getProvide() && !$this->cache->getPath()) {
                $this->cache->setPath($path);
            }

            $this->paths_loaded = true;
        }
    }

    /**
     * Gets the _globals from the file and merges them if merge_globals is true
     *
     * @param array $content The unparsed content
     * @param array $options  The options being used for Vars
     *
     * @return array $content The parsed content
     */
    private function mergeGlobals($content, $options)
    {
        if (array_key_exists('_globals', $content)) {
            $this->globals = $content['_globals'];

            if ($options['merge_globals']) {
                $content = array_replace_recursive($content, $content['_globals']);
            }

            unset($content['_globals']);
        }

        return $content;
    }

    /**
     * Adds a resource to $this->resources
     *
     * @param string $resource Resource to add to the stack
     *
     * @return int The position of the added resource
     */
    public function addResource($resource)
    {
        $r = realpath($resource);
        $pos = count($this->resources);
        $this->resources[$pos] = $r;
        return $pos;
    }

    /**
     * Updates the string resource with the FileResource
     *
     * @param \M1\Vars\Resource\FileResource $resource The FileResource to add
     * @param int                            $pos      The position of the string resource
     *
     * @return \M1\Vars\Vars
     */
    public function updateResource($resource, $pos)
    {
        $this->resources[$pos] = $resource;
        return $this;
    }

    /**
     * Tests to see if the resource has been imported already -- this is to avoid getting into a infinite loop
     *
     * @param \M1\Vars\Resource\FileResource|string $resource Resource to check
     *
     * @return bool Has resource already been imported
     */
    public function resourceImported($resource)
    {
        $resource = realpath($resource);
        foreach ($this->getResources() as $r) {
            if ((is_a($r, 'M1\Vars\Resource\FileResource') && $resource === $r->getFile()) ||
                (is_string($r) && $resource === $r)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Searches the resource stack for a certain resource
     *
     * @param string $resource The resource to search for
     *
     * @return \M1\Vars\Resource\FileResource|bool Returns the resource if found
     */
    public function getResource($resource)
    {
        foreach ($this->getResources() as $r) {
            if ($resource === $r->getFilename()) {
                return $r;
            }
        }

        return false;
    }

    /**
     * Returns the imported resources
     *
     * @return array The Vars imported resources
     */
    public function getResources()
    {
        return $this->resources;
    }

    /**
     * Returns the imported resources
     *
     * @return array The Vars imported resources
     */
    public function getGlobals()
    {
        return $this->globals;
    }

    /**
     * Returns the CacheProvider if set
     *
     * @return \M1\Vars\Cache\CacheProvider The CacheProvider
     */
    public function getCache()
    {
        return $this->cache;
    }
}


<?php
/**
 * Lord of the Files - SVG Fallback
 *
 * @package blob-mimes
 * @author  Blobfolio, LLC <hello@blobfolio.com>
 */

namespace blobfolio\wp\bm\svg;

use blobfolio\wp\bm\mb;
use SimpleXMLElement;

class svg_fallback extends svg_base {

	/**
	 * Sanitize an SVG
	 *
	 * @param string $svg SVG code or path.
	 * @param bool $header Append header.
	 * @return string|bool Sanitized SVG code or false.
	 */
	public static function sanitize($svg = '', $header=true) {
		$svg = \apply_filters('blobmimes_svg_pre_sanitize', $svg);

		if (false === $svg = static::import($svg)) {
			return false;
		}

		// Allowed.
		$allowed_tags = static::get_allowed_tags();

		// We'll need to clean up styles a bit later.
		$style_tags = array();

		// First, find and remove invalid tags and their contents.
		\preg_match_all('%(<[^>]*(>|$)|>)%', $svg, $matches);
		if (\count($matches[0])) {
			$invalid = array();
			foreach ($matches[0] as $raw) {
				// Ignore malformed.
				if (
					\substr($raw, 0, 1) !== '<' ||
					! \preg_match('%^<\s*(/\s*)?([a-zA-Z0-9-:]+)([^>]*)>?$%', $raw, $matches2)
				) {
					continue;
				}

				// Don't need closing tags now.
				if (\trim($matches2[1]) === '/') {
					continue;
				}

				$tag = mb::strtolower($matches2[2]);
				$tag_name = $tag;
				// The tag might be namespaced (ns:tag). We'll allow it if
				// the tag is allowed.
				if (
					false !== mb::strpos($tag_name, ':') &&
					! \in_array($tag_name, $allowed_tags, true)
				) {
					$tag_name = \explode(':', $tag_name);
					$tag_name = $tag_name[1];
				}

				// Bad tag: not whitelisted.
				if (! \in_array($tag_name, $allowed_tags, true)) {
					$invalid[] = $tag;
				} elseif ('style' === $tag_name) {
					$style_tags[] = $tag;
				}
			}

			// Actual removal.
			if (\count($invalid)) {
				$invalid = \array_unique($invalid);
				foreach ($invalid as $k=>$v) {
					$invalid[$k] = \preg_quote($v, '@');
				}

				// Remove open/close pairs.
				$svg = \preg_replace('@<(' . \implode('|', $invalid) . ')\b.*?>.*?</\1>@si', '', $svg);

				// Remove self-closing.
				$svg = \preg_replace('@<(' . \implode('|', $invalid) . ')\b.*?/>@si', '', $svg);
			}
		}

		// Similar to wp_kses_split(), however a separate
		// function was needed for proper XML support.
		$svg = \preg_replace_callback('%(<[^>]*(>|$)|>)%', array(static::class, 'split_tags'), $svg);

		// We need to decode entities in <style> tags.
		// Thanks XML!
		if (\count($style_tags)) {
			$style_tags = \array_unique($style_tags);
			foreach ($style_tags as $k=>$v) {
				$style_tags[$k] = \preg_quote($v, '@');
			}

			\preg_match_all('@<(' . \implode('|', $style_tags) . ')\b(.*?)>(.*?)</\1>@si', $svg, $matches2);
			if (\count($matches2[0])) {
				$replacements = array();
				foreach ($matches2[0] as $k=>$v) {
					// 0 Whole Match.
					// 1 Tag.
					// 2 Attributes.
					// 3 Contents.
					$replacements[$v] = "<{$matches2[1][$k]} {$matches2[2][$k]}>" .
						\strip_tags(static::sanitize_attribute_value($matches2[3][$k])) .
						"</{$matches2[1][$k]}>";
				}

				if (\count($replacements)) {
					$svg = \str_replace(\array_keys($replacements), \array_values($replacements), $svg);
				}
			}
		}

		// Remove comments.
		$svg = static::strip_comments($svg);

		// Sanitize CSS values (e.g. foo="url(...)").
		$svg = \preg_replace_callback('/url\s*\((.*)\s*\)/Ui', array(static::class, 'callback_sanitize_css_iri'), $svg);

		// Make sure if xmlns="" exists, it is correct. Can't alter
		// that with DOMDocument, and there is only one proper value.
		$svg = \preg_replace('/xmlns\s*=\s*"[^"]*"/', 'xmlns="' . static::XMLNS_NAMESPACE . '"', $svg);

		// Let's crunch some whitespace!
		$svg = \preg_replace('/\s+/u', ' ', $svg);
		$svg = \str_replace('> <', '><', $svg);

		if (
			false === ($start = mb::strpos($svg, '<svg')) ||
			false === ($end = mb::strrpos($svg, '</svg>'))
		) {
			return false;
		}

		// Add our headers, and we're done!
		if ($header) {
			$svg = static::XMLTAG . "\n" . static::get_doctype() . "\n$svg";
		}

		// Done!
		return \apply_filters('blobmimes_svg_post_sanitize', $svg);
	}

	/**
	 * Callback for static::sanitize() tag splitting
	 *
	 * @see { wp_kses_split2() }
	 *
	 * @param string $matches Matches.
	 * @return string Sanitized match replacement.
	 */
	public static function split_tags($matches) {
		$raw = \wp_kses_stripslashes($matches[0]);

		// It matched a ">" character.
		if (\substr($raw, 0, 1) !== '<') {
			return '>';
		}

		// It's seriously malformed.
		if (! \preg_match('%^<\s*(/\s*)?([a-zA-Z0-9-:]+)([^>]*)>?$%', $raw, $matches)) {
			return '';
		}

		$slash = \trim($matches[1]);
		$endslash = \preg_match('/\/\s*>/', $raw) ? '/' : '';
		$tag = $matches[2];
		$attributes = $matches[3];

		// Closing tag.
		if ($slash) {
			return "</$tag>";
		}

		// Allowed values.
		$allowed_attributes = static::get_allowed_attributes();

		$parsed_attributes = static::kses_hair($attributes);
		$accepted_attributes = array();
		foreach ($parsed_attributes as $attr) {
			$attribute_name = mb::strtolower($attr['name']);

			// Bad attribute: not whitelisted.
			// data-* is implicitly whitelisted.
			if (
				! \preg_match('/^data\-/', $attribute_name) &&
				! \preg_match('/^xmlns:/', $attribute_name) &&
				! \in_array($attribute_name, $allowed_attributes, true)
			) {
				continue;
			}

			$accepted_attributes[] = $attr['whole'];
		}

		return "<$tag" . (\count($accepted_attributes) ? ' ' . \implode(' ', $accepted_attributes) : '') . "$endslash>";
	}

	/**
	 * Reworking of wp_kses_hair() for SVG
	 *
	 * @see { wp_kses_hair() }
	 *
	 * @param string $attr Attribute(s) string.
	 * @return array Attribute details.
	 */
	public static function kses_hair($attr) {
		$attributes = array();
		$mode = 0;
		$attribute = '';
		$iri_attributes = static::get_iri_attributes();

		$attr = \trim($attr);

		// Loop through the whole attribute list.
		while (\strlen($attr) !== 0) {
			$working = 0; // Was the last operation successful?

			switch ($mode) {
				case 0 : // Attribute name, href for instance.

					if (\preg_match('/^([-a-zA-Z:]+)/', $attr, $match)) {
						$attribute = $match[1];
						$working = $mode = 1;
						$attr = \preg_replace('/^[-a-zA-Z:]+/', '', $attr);
					}

					break;

				case 1 : // Equals sign or valueless ("selected").

					if (\preg_match('/^\s*=\s*/', $attr)) {
						$working = 1;
						$mode = 2;
						$attr = \preg_replace('/^\s*=\s*/', '', $attr);
						break;
					}

					if (\preg_match('/^\s+/', $attr)) {
						$working = 1;
						$mode = 0;
						if (false === \array_key_exists($attribute, $attributes)) {
							$attributes[$attribute] = array('name'=>$attribute, 'value'=>'', 'whole'=>$attribute, 'vless'=>'y');
						}
						$attr = \preg_replace('/^\s+/', '', $attr);
					}

					break;

				case 2 : // Attribute value, a URL after href= for instance.

					$attribute_value = false;

					// Double-quote.
					if (\preg_match('%^"([^"]*)"(\s+|/?$)%', $attr, $match)) {
						$attribute_value = \trim($match[1]);
						$attr = \preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
					} // Single-quote.
					elseif (\preg_match("%^'([^']*)'(\s+|/?$)%", $attr, $match)) {
						$attribute_value = \trim($match[1]);
						$attr = \preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
					} // No quote.
					elseif (\preg_match("%^([^\s\"']+)(\s+|/?$)%", $attr, $match)) {
						$attribute_value = \trim($match[1]);
						$attr = \preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
					}

					// We found a value?
					if (false !== $attribute_value) {
						$working = 1;
						$mode = 0;
						$iri = false;

						$attribute_value = static::sanitize_attribute_value($attribute_value);

						// Validate protocols.
						// IRI attributes get the full KSES treatment.
						if (
							\in_array(mb::strtolower($attribute), $iri_attributes, true) ||
							\preg_match('/^xmlns:/i', $attribute)

						) {
							$iri = true;
							$attribute_value = static::sanitize_iri_value($attribute_value);
						} // For others, we are specifically interested in removing scripty bits.
						elseif (\preg_match('/(?:\w+script):/xi', $attribute_value)) {
							$attribute_value = '';
						}

						// Save it.
						if (
							false === \array_key_exists($attribute, $attributes) &&
							(! $iri || \strlen($attribute_value))
						) {
							if (\strlen($attribute_value)) {
								$attribute_value = \esc_attr($attribute_value);
								$attributes[$attribute] = array(
									'name'=>$attribute,
									'value'=>$attribute_value,
									'whole'=>"$attribute=\"$attribute_value\"",
									'vless'=>'n',
								);
							} else {
								$attributes[$attribute] = array(
									'name'=>$attribute,
									'value'=>'',
									'whole'=>"$attribute",
									'vless'=>'y',
								);
							}
						}
					}

					break;
			} // Switch.

			if (0 === $working) {
				$attr = \wp_kses_html_error($attr);
				$mode = 0;
			}
		} // While.

		if (1 === $mode && false === \array_key_exists($attribute, $attributes)) {
			// Special case, for when the attribute list ends with a valueless
			// attribute like "selected".
			$attributes[$attribute] = array('name'=>$attribute, 'value'=>'', 'whole'=>$attribute, 'vless'=>'y');
		}

		return $attributes;
	}

	/**
	 * Import to DOMDocument object.
	 *
	 * @param string $svg SVG code or path.
	 * @return string Lightly sanitized SVG code.
	 */
	protected static function import($svg = '') {
		if (false === ($svg = static::load_svg($svg))) {
			return false;
		}

		try {
			// Run it through SimpleXML if possible.
			if (\class_exists('SimpleXMLElement')) {
				\libxml_use_internal_errors(true);
				\libxml_disable_entity_loader(true);

				$svg = new SimpleXMLElement($svg);
				$svg = $svg->asXML();
			}
		} catch (\Throwable $e) {
			return false;
		} catch (\Exception $e) {
			return false;
		}

		return $svg;
	}

	/**
	 * Get SVG Dimensions
	 *
	 * @param string $svg SVG code or path.
	 * @return array|bool Dimensions. False on failure.
	 */
	public static function get_dimensions($svg = '') {
		if (false === ($svg = static::import($svg))) {
			return false;
		}

		try {

			// Prefer SimpleXML.
			if (\class_exists('SimpleXMLElement')) {
				\libxml_use_internal_errors(true);
				\libxml_disable_entity_loader(true);

				$svg = new SimpleXMLElement($svg);

				$width = isset($svg['width']) ? (string) $svg['width'] : null;
				$height = isset($svg['height']) ? (string) $svg['height'] : null;
				$viewbox = isset($svg['viewBox']) ? (string) $svg['viewBox'] : null;
			} // Otherwise try to parse it out.
			else {
				$width = $height = $viewbox = null;

				\preg_match('/<svg\s?([^>]*)>/i', $svg, $match);
				if (\count($match) >= 2) {
					\preg_match_all('/(width|height|viewBox)\s*=\s*(([\d\.]+)|("|\')([^"\']+)("|\'))/i', $match[1], $matches);
					if (\count($matches[0])) {
						foreach ($matches[1] as $k=>$tag) {
							$tag = \strtolower($tag);
							$$tag = \strlen($matches[3][$k]) ? $matches[3][$k] : $matches[5][$k];
						}
					}
				}
			}

			// Prefer the viewbox as it is more likely to be numeric,
			// and also more likely to reflect the real dimensions.
			if (! \is_null($viewbox)) {
				$viewbox = \trim(\preg_replace('/\s+/', ' ', $viewbox));
				$viewbox = \explode(' ', $viewbox);
				if (\count($viewbox) === 4) {
					$viewbox[2] = (float) $viewbox[2];
					$viewbox[3] = (float) $viewbox[3];

					if ($viewbox[2] > 0 && $viewbox[3] > 0) {
						return array(
							'width'=>$viewbox[2],
							'height'=>$viewbox[3],
						);
					}
				}
			}

			// Otherwise maybe the width and height are good?
			if (\is_numeric($width) && \is_numeric($height)) {
				$width = (float) $width;
				$height = (float) $height;

				if ($width > 0 && $height > 0) {
					return array(
						'width'=>$width,
						'height'=>$height,
					);
				}
			}
		} catch (\Throwable $e) {
			return false;
		} catch (\Exception $e) {
			return false;
		}

		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\Variables;

use \M1\Vars\Resource\AbstractResource;

/**
 * Stores the in-file variables
 *
 * @since 1.0.0
 */
class VariableStore extends AbstractResource
{
    /**
     * The current prefix for the variable store
     *
     * @var string $current_prefix
     */
    private $current_prefix;

    /**
     * The relative prefix for the variable store
     *
     * @var string $path
     */
    private $prefix;

    /**
     * Creates a relative prefix for the store
     *
     * @param bool $relative Is the prefix relative
     */
    public function createPrefix($relative)
    {
        if ($relative) {
            $this->prefix = (empty($this->current_prefix)) ? '' : $this->current_prefix;
            return;
        }

        $this->prefix = '';
    }

    /**
     * Creates a new prefix name for the store
     *
     * @param string $prefix The prefix for the store
     * @param string $key The key for the item
     *
     * @return string The new prefix
     */
    public function createPrefixName($prefix, $key)
    {
        return $prefix.$key.'.';
    }

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

    /**
     * Sets the current prefix
     *
     * @param string $prefix The new prefix
     */
    public function setCurrentPrefix($prefix)
    {
        $this->current_prefix = $prefix;
    }
}


<?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\Resource;

use M1\Vars\Loader\DirectoryLoader;
use M1\Vars\Traits\ResourceFlagsTrait;
use M1\Vars\Vars;

/**
 * Vars Resource Provider to separate the different formats of config (array/file/dir) into one resource
 *
 * @since 0.1.0
 */
class ResourceProvider extends AbstractResource
{
    use ResourceFlagsTrait;

    /**
     * The configuration entity -- could be a file, array or dir
     *
     * @var array|string
     */
    private $entity;

    /**
     * If the import was not relative then the content will be stored in this array
     *
     * @var array
     */
    private $parent_content = array();

    /**
     * Are dirs wanting to be recursively searched
     *
     * @var bool
     */
    private $recursive;

    /**
     * Is the import relative
     *
     * @var bool
     */
    private $relative;

    /**
     * Suppress file not found exceptions
     *
     * @var bool
     */
    private $suppress_file_exceptions = false;

    /**
     * The parent Vars class
     *
     * @var \M1\Vars\Vars
     */
    public $vars;

    /**
     * The ResourceProvider constructor creates the content from the entity
     *
     * @param \M1\Vars\Vars $vars      The calling Vars class
     * @param string|array  $entity    The configuration entity
     * @param bool          $relative  Is the entity relative to the calling entity or class
     * @param bool          $recursive If entity a dir, do you want to recursively check directories
     *
     * @throws \InvalidArgumentException If the entity passed is not a string or array
     */
    public function __construct(Vars $vars, $entity, $relative = true, $recursive = false)
    {
        if (!is_string($entity) && !is_array($entity)) {
            throw new \InvalidArgumentException('You can only pass strings or arrays as Resources');
        }

        $this->vars = $vars;
        $this->entity = $entity;
        $this->relative = $relative;
        $this->recursive = $recursive;
        $type = gettype($entity);

        $resources = $this->processEntity($entity, $type);

        $vars->variables->vstore->createPrefix($relative);

        if ($resources && !empty($resources)) {
            $this->createResources($resources, $type);
        }
    }

    /**
     * Creates the FileResource|ResourceProvider from the resource
     *
     * @param array  $resources The array of resources
     * @param string $type      The type of the resource
     */
    private function createResources(array $resources, $type)
    {
        foreach ($resources as $resource) {
            if ($type === "string") {
                $this->vars->pathsLoadedCheck($resource);

                if ($this->vars->cache->checkCache()) {
                    return;
                }

                if ($this->vars->resourceImported($resource)) {
                    continue;
                }

                $pos = $this->vars->addResource($resource);
                $resource = new FileResource($this, $resource);
                $this->vars->updateResource($resource, $pos);
            } else {
                $resource = new ResourceProvider($this->vars, $resource);
            }

            $this->addContent($resource->getContent());
        }
    }

    /**
     * Creates the content from the entity
     *
     * @param string|array $entity The configuration entity
     * @param string       $type   The type of entity
     *
     * @throws \InvalidArgumentException If the entity is not array|file, is readable or exists
     *
     * @returns array The array of resources
     */
    private function processEntity($entity, $type)
    {
        $resources = $entity;

        if ($type === 'string') {
            $entity = $this->parseEntity($entity);

            if (is_file($entity)) {
                $resources = array($entity);
            } elseif (is_dir($entity)) {
                $resources = $this->getSupportedFilesInDir($entity);
            } elseif ($this->suppress_file_exceptions) {
                $resources = false;
            } else {
                throw new \InvalidArgumentException(sprintf("'%s' does not exist or is not readable", $entity));
            }
        }

        return $resources;
    }

    /**
     * Creates the content from the entity
     *
     * @param string $entity The configuration entity
     *
     * @returns string The parsed entity
     */
    private function parseEntity($entity)
    {
        $files = $this->explodeResourceIfElse($entity);

        foreach ($files as $f) {
            $this->suppress_file_exceptions = $this->checkSuppression($f);
            $this->recursive = $this->checkRecursive($f);
            $f = $this->trimFlags($f);

            if (file_exists($f) || !isset($files[1])) {
                return $f;
            }
        }

        return $f;
    }

    /**
     * Adds content to the parent contents
     *
     * @param array $content The content from the resource
     */
    private function addContent($content)
    {
        if ($this->relative) {
            $this->content = $this->mergeContents($this->content, $content);
        } else {
            $this->parent_content = $this->mergeContents($this->parent_content, $content);
        }
    }

    /**
     * Returns the supported files using the extensions from the loaders in the entity which is a directory
     *
     * @see \M1\Vars\Loader\LoaderProvider::getExtensions() \M1\Vars\Loader\LoaderProvider::getExtensions()
     * @see \M1\Vars\Loader\LoaderProvider::makeLoaders() \M1\Vars\Loader\LoaderProvider::makeLoaders()
     *
     * @param string $entity The resource entity
     *
     * @return array|bool Returns the supported files or false if no files were found
     */
    private function getSupportedFilesInDir($entity)
    {
        $dir_loader = new DirectoryLoader($entity, $this->recursive);
        $dir_loader->setSupports($this->vars->loader->getExtensions());
        $dir_loader->load();

        return $dir_loader->getContent();
    }

    /**
     * Merges various configuration contents into one array
     *
     * @return array The merged contents
     */
    private function mergeContents()
    {
        $contents = func_get_args();
        return call_user_func_array('array_replace_recursive', $contents);
    }

    /**
     * Adds content to the parent content array
     *
     * @param array $content The content to add
     */
    public function addParentContent(array $content)
    {
        $this->parent_content = array_merge_recursive($this->parent_content, $content);
    }

    /**
     * Merges the content and the parent content together
     *
     * @return \M1\Vars\Resource\ResourceProvider
     */
    public function mergeParentContent()
    {
        $this->content = $this->mergeContents($this->content, $this->parent_content);

        return $this;
    }

    /**
     * Returns the parent content
     *
     * @return mixed The parent content
     */
    public function getParentContent()
    {
        return $this->parent_content;
    }
}


<?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\Resource;

/**
 * Abstract Resource enables normal and dot notation array access on resources
 *
 * @since 0.1.0
 */
abstract class AbstractResource implements \ArrayAccess
{
    /**
     * The resource content
     *
     * @var array
     */
    protected $content = array();

    /**
     * Sets the resource contents
     *
     * @param array $content
     *
     * @return $this
     */
    public function setContent($content)
    {
        $this->content = $content;
        return $this;
    }

    /**
     * Returns the content of the resource
     *
     * @return array The content
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * Normal `$example[$key]` access for the array
     *
     * @param mixed $key The key to get the value for
     *
     * @return array|bool|null The resource key value
     */
    public function offsetGet($key)
    {
        return $this->internalGet($this->content, $key);
    }

    /**
     * Object oriented get access for the array
     *
     * @param mixed $key The key to get the value for
     *
     * @return array|bool|null The resource key value
     */
    public function get($key)
    {
        return $this->internalGet($this->content, $key);
    }

    /**
     * The internal get function for getting values by their key
     *
     * @param array $array  The array to use -- will always be $this->content
     * @param mixed $key    The key to find the value for
     * @param bool  $exists Whether to return null or false dependant on the calling function
     *
     * @return array|bool|null The resource key value
     */
    private function internalGet(array $array, $key, $exists = false)
    {
        if (isset($array[$key])) {
            return (!$exists) ? $array[$key] : true;
        }

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

        foreach ($parts as $part) {
            if (!is_array($array) || !isset($array[$part])) {
                return (!$exists) ? null : false;
            }

            $array = $array[$part];
        }

        return (!$exists) ? $array : true;
    }

    /**
     * Normal `$example[$key] = 'hello'` access for the array
     *
     * @param mixed $key The key to set the value for
     * @param mixed $value The value to set
     */
    public function offsetSet($key, $value)
    {
        $this->internalSet($this->content, $key, $value);
    }

    /**
     * Object oriented set access for the array
     *
     * @param string $key The key to set the value for
     * @param string $value The value to set
     */
    public function set($key, $value)
    {
        $this->internalSet($this->content, $key, $value);
    }

    /**
     * Object oriented set access for the array
     *
     * @param array $array  The array to use -- will always be based on $this->content but can be used recursively
     * @param mixed $key    The key to set the value for
     * @param mixed $value  The value to set
     *
     * @return array Returns the modified array
     */
    private function internalSet(array &$array, $key, $value)
    {
        if (is_null($key)) {
            return $array = $value;
        }

        $keys = explode('.', $key);

        while (count($keys) > 1) {
            $key = array_shift($keys);

            if (! isset($array[$key]) || ! is_array($array[$key])) {
                $array[$key] = [];
            }

            $array = &$array[$key];
        }

        $array[array_shift($keys)] = $value;

        return $array;
    }

    /**
     * Checks whether the key exists
     *
     * @param mixed $key The key to check
     *
     * @return bool Does the key exist
     */
    public function offsetExists($key)
    {
        return $this->internalGet($this->content, $key, true);
    }

    /**
     * Unsets the key
     *
     * @param mixed $key The key to unset
     */
    public function offsetUnset($key)
    {
        $this->internalUnset($this->content, $key);
    }

    /**
     * Internal unset for the key
     *
     * @param array $array The array to use -- will always be based on $this->content but can be used recursively
     * @param mixed $key The key to unset
     */
    protected function internalUnset(array &$array, $key)
    {
        $parts = explode(".", $key);

        while (count($parts) > 1) {
            $part = array_shift($parts);

            if (isset($array[$part]) && is_array($array[$part])) {
                $array =& $array[$part];
            }
        }

        unset($array[array_shift($parts)]);
    }

    /**
     * Port of array_key_exists to \ArrayAccess
     *
     * @param mixed $key The key to check if exists
     *
     * @return bool Does the key exist
     */
    public function arrayKeyExists($key)
    {
        if (array_key_exists($key, $this->content)) {
            return true;
        }

        $parts = explode('.', $key);
        $arr = $this->content;
        foreach ($parts as $part) {
            if (!is_array($arr) || !array_key_exists($part, $arr)) {
                return false;
            }

            $arr = $arr[$part];
        }

        return true;
    }
}


<?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;

/**
 * Vars variable file resource for getting replacement variables from files or arrays
 *
 * @since 0.1.0
 */
class VariableProvider
{
    /**
     * The replacement regex to get content between %^content%
     *
     * @var string REGEX_ENV_SYNTAX
     */
    const REGEX_ENV_SYNTAX         = '\\%\\^\\s*?([A-Za-z0-9 _ .]+)\\s*?\\%';

    /**
     * The variable regex to get content between %content%
     *
     * @var string REGEX_REPLACEMENT_SYNTAX
     */
    const REGEX_REPLACEMENT_SYNTAX = '\\%\\s*?([A-Za-z0-9 _ .]+)\\s*?\\%';

    /**
     * The variable regex to get content between %$content%
     *
     * @var string REGEX_VARIABLE_SYNTAX
     */
    const REGEX_VARIABLE_SYNTAX    = '\\%\\$\\s*?([A-Za-z0-9 _ .]+)\\s*?\\%';

    /**
     * The types of variables
     *
     * @var array $variable_types
     */
    private static $variable_types = array('replacement', 'variable', 'env');

    /**
     * The replacement store
     *
     * @var \M1\Vars\Variables\ReplacementStore $rstore
     */
    public $rstore;

    /**
     * The variable store
     *
     * @var \M1\Vars\Variables\VariableStore $vstore
     */
    public $vstore;

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

    /**
     * Parses the string for the types of variables
     *
     * @param string $value The string to parse
     *
     * @return string The parsed variable
     */
    public function parse($value)
    {
        foreach (self::$variable_types as $variable_type) {
            $value = $this->typeParse($value, $variable_type);
        }

        return $value;
    }

    /**
     * Parses the string based on the type of variable
     *
     * @param string $value The string to parse
     * @param string $type  The variable type
     *
     * @return string The parsed variable
     */
    private function typeParse($value, $type)
    {
        $const_str = sprintf('REGEX_%s_SYNTAX', strtoupper($type));
        $regex = constant('\M1\Vars\Variables\VariableProvider::'.$const_str);

        $matches = $this->fetchVariableMatches($value, $regex);

        if (is_array($matches)) {
            if (count($matches[0]) === 1 && $value === $matches[0][0]) {
                return $this->fetchVariable(trim($matches[1][0]), $type);
            }

            $value = $this->doReplacements($value, $matches, $type);
        }
        return $value;
    }

    /**
     * Fetches the variable matches in the string
     *
     * @param string $value The string to fetch matches for
     * @param string $regex The variable type regex
     *
     * @return array The matches
     */
    private function fetchVariableMatches($value, $regex)
    {
        preg_match_all('/' . $regex . '/', $value, $matches);

        if (!is_array($matches) || !isset($matches[0]) || empty($matches[0])) {
            return false;
        }

        return $matches;
    }

    /**
     * Fetches the variable from the stores
     *
     * @param string $variable_name The variable to fetch
     * @param string $type          The variable type
     *
     * @return string The fetches value for the variable
     */
    private function fetchVariable($variable_name, $type)
    {
        $this->checkVariableExists($variable_name, $type);

        if ($type === 'env') {
            return getenv($variable_name);
        } elseif ($type === 'replacement') {
            return $this->rstore->get($variable_name);
        }

        return $this->vstore->get($variable_name);
    }

    /**
     * Checks to see if the variable exists
     *
     * @param string $variable The variable to check
     * @param string $type     The variable type
     *
     * @throws \InvalidArgumentException If the variable does not exist
     *
     * @return bool Does the variable exist
     */
    private function checkVariableExists($variable, $type)
    {
        if (($type === 'env'         && !getenv($variable)) ||
            ($type === 'replacement' && !$this->rstore->arrayKeyExists($variable)) ||
            ($type === 'variable'    && !$this->vstore->arrayKeyExists($variable))
        ) {
            throw new \InvalidArgumentException(
                sprintf('Variable has not been defined as a `%s`: %s', $variable, $type)
            );
        }

        return true;
    }

    /**
     * Does the replacements in the string for the variable
     *
     * @param string $value   The string to parse
     * @param array  $matches The matches
     * @param string $type    The variable type
     *
     * @return string The parsed variable
     */
    public function doReplacements($value, $matches, $type)
    {
        $replacements = array();
        for ($i = 0; $i <= (count($matches[0]) - 1); $i++) {
            $replacement = $this->fetchVariable($matches[1][$i], $type);
            $replacements[$matches[0][$i]] = $replacement;
        }

        if (!empty($replacements)) {
            $value = strtr($value, $replacements);
        }

        return $value;
    }
}


<?php
/**
 * Lord of the Files - SVG DOMDocument
 *
 * @package blob-mimes
 * @author  Blobfolio, LLC <hello@blobfolio.com>
 */

namespace blobfolio\wp\bm\svg;

use \blobfolio\wp\bm\mb;
use DOMXPath;
use DOMDocument;

abstract class svg_dom extends svg_base {

	/**
	 * Sanitize an SVG
	 *
	 * @param string $svg SVG code or path.
	 * @param bool $header Append header.
	 * @return string|bool Sanitized SVG code. False on failure.
	 */
	public static function sanitize($svg = '', $header=true) {
		$svg = apply_filters('blobmimes_svg_pre_sanitize', $svg);

		// Importing itself will ensure the XML is at least
		// vaguely valid. It will also adjust tag casing,
		// quote styles, etc.
		if (false === ($dom = static::import($svg))) {
			return false;
		}

		// Whitelist.
		$allowed_tags = static::get_allowed_tags();
		$allowed_attributes = static::get_allowed_attributes();
		$iri_attributes = static::get_iri_attributes();
		$xpath = new DOMXPath($dom);

		// Validate tags.
		$tags = $dom->getElementsByTagName('*');

		for ($x = $tags->length - 1; $x >= 0; $x--) {
			$tag = $tags->item($x);

			// @codingStandardsIgnoreStart
			$tag_name = mb::strtolower($tag->tagName);
			// @codingStandardsIgnoreEnd

			// The tag might be namespaced (ns:tag). We'll allow it if
			// the tag is allowed.
			if (
				false !== mb::strpos($tag_name, ':') &&
				!in_array($tag_name, $allowed_tags, true)
			) {
				$tag_name = explode(':', $tag_name);
				$tag_name = $tag_name[1];
			}

			// Bad tag: not whitelisted.
			if (!in_array($tag_name, $allowed_tags, true)) {
				static::remove_node($tag);
				continue;
			}

			// If this is a <style> tag, we want to decode entities.
			// Thanks XML!
			if ('style' === $tag_name) {
				// @codingStandardsIgnoreStart
				$style = strip_tags(static::sanitize_attribute_value($tag->textContent));
				$tag->textContent = $style;
				// @codingStandardsIgnoreEnd
			}

			// Use XPath for attributes, as $tag->attributes will skip
			// anything namespaced. Note: We aren't focusing on
			// actual Namespaces here, that comes later.
			$attributes = $xpath->query('.//@*', $tag);
			for ($y = $attributes->length - 1; $y >= 0; $y--) {
				$attribute = $attributes->item($y);

				// @codingStandardsIgnoreStart
				$attribute_name = mb::strtolower($attribute->nodeName);
				// @codingStandardsIgnoreEnd

				// Could be namespaced.
				if (
					!in_array($attribute_name, $allowed_attributes, true) &&
					false !== ($start = mb::strpos($attribute_name, ':'))
				) {
					$attribute_name = mb::substr($attribute_name, $start + 1);
				}

				// Bad attribute: not whitelisted.
				// data-* is implicitly whitelisted.
				if (
					!preg_match('/^data\-/', $attribute_name) &&
					!in_array($attribute_name, $allowed_attributes, true)
				) {
					// @codingStandardsIgnoreStart
					$tag->removeAttribute($attribute->nodeName);
					// @codingStandardsIgnoreEnd
					continue;
				}

				// Validate values.
				$attribute_value = static::sanitize_attribute_value($attribute->value);

				// Validate protocols.
				// IRI attributes get the full KSES treatment.
				$iri = false;
				if (in_array($attribute_name, $iri_attributes, true)) {
					$iri = true;
					$attribute_value = static::sanitize_iri_value($attribute_value);
				} // For others, we are specifically interested in removing scripty bits.
				elseif (preg_match('/(?:\w+script):/xi', $attribute_value)) {
					$attribute_value = '';
				}

				// Update it.
				if ($attribute_value !== $attribute->value) {
					// @codingStandardsIgnoreStart
					if ($iri) {
						$tag->removeAttribute($attribute->nodeName);
					} else {
						$tag->setAttribute($attribute->nodeName, $attribute_value);
					}
					// @codingStandardsIgnoreEnd
				}
			}
		} // Each tag.

		// Once more through the tags to find namespaces.
		$tags = $dom->getElementsByTagName('*');
		for ($x = 0; $x < $tags->length; $x++) {
			$tag = $tags->item($x);
			$nodes = $xpath->query('namespace::*', $tag);
			for ($y = 0; $y < $nodes->length; $y++) {
				$node = $nodes->item($y);

				// @codingStandardsIgnoreStart
				$node_name = mb::strtolower($node->nodeName);
				// @codingStandardsIgnoreEnd

				// Not xmlns?
				if (!preg_match('/^xmlns:/', $node_name)) {
					// @codingStandardsIgnoreStart
					static::remove_namespace($dom, $node->localName);
					// @codingStandardsIgnoreEnd
					continue;
				}

				// Validate values.
				// @codingStandardsIgnoreStart
				$node_value = static::sanitize_attribute_value($node->nodeValue);
				// @codingStandardsIgnoreEnd
				$node_value = static::sanitize_iri_value($node_value);

				// Remove invalid.
				if (!strlen($node_value)) {
					// @codingStandardsIgnoreStart
					static::remove_namespace($dom, $node->localName);
					// @codingStandardsIgnoreEnd
				}
			}
		}

		// Back to a string!
		if (false === ($svg = static::export($dom))) {
			return false;
		}

		// Sanitize CSS values (e.g. foo="url(...)").
		$svg = preg_replace_callback('/url\s*\((.*)\s*\)/Ui', array(get_called_class(), 'callback_sanitize_css_iri'), $svg);

		// Make sure if xmlns="" exists, it is correct. Can't alter
		// that with DOMDocument, and there is only one proper value.
		$svg = preg_replace('/xmlns\s*=\s*"[^"]*"/', 'xmlns="' . static::XMLNS_NAMESPACE . '"', $svg);

		// Let's crunch some whitespace!
		$svg = preg_replace('/\s+/u', ' ', $svg);
		$svg = str_replace('> <', '><', $svg);

		// Add our headers, and we're done!
		if ($header) {
			$svg = static::XMLTAG . "\n" . static::get_doctype() . "\n$svg";
		}

		// Done!
		return apply_filters('blobmimes_svg_post_sanitize', $svg);
	}

	/**
	 * Import to DOMDocument object.
	 *
	 * @param string $svg SVG code or path.
	 * @return DOMDocument|bool XML object. False on failure.
	 */
	protected static function import($svg = '') {
		// Early bail.
		if (
			false === ($svg = static::load_svg($svg)) ||
			!class_exists('DOMDocument')
		) {
			return false;
		}

		try {
			// @codingStandardsIgnoreStart
			libxml_use_internal_errors(true);
			libxml_disable_entity_loader(true);
			$dom = new DOMDocument('1.0', 'UTF-8');
			$dom->formatOutput = false;
			$dom->preserveWhiteSpace = false;
			$dom->loadXML(static::get_doctype() . "\n{$svg}");

			// Make sure there are still SVG tags.
			$svgs = $dom->getElementsByTagName('svg');
			if (!$svgs->length) {
				return false;
			}

			return $dom;
			// @codingStandardsIgnoreEnd
		} catch (\Throwable $e) {
			error_log($e->getMessage());
			return false;
		} catch (\Exception $e) {
			return false;
		}

		return false;
	}

	/**
	 * Export from a DOMDocument object.
	 *
	 * @param DOMDocument $dom XML object.
	 * @return string|bool Sanitized SVG code. False on failure.
	 */
	protected static function export($dom = null) {
		if (!is_a($dom, 'DOMDocument')) {
			return false;
		}

		try {
			$svgs = $dom->getElementsByTagName('svg');
			if (!$svgs->length) {
				return false;
			}
			$svg = $svgs->item(0)->ownerDocument->saveXML($svgs->item(0), LIBXML_NOBLANKS);

			// Remove comments.
			$svg = static::strip_comments($svg);

			return $svg;
		} catch (\Throwable $e) {
			error_log($e->getMessage());
			return false;
		} catch (\Exception $e) {
			return false;
		}

		return false;
	}

	/**
	 * Get SVG Dimensions
	 *
	 * @param string $svg SVG code or path.
	 * @return array|bool Dimensions. False on failure.
	 */
	public static function get_dimensions($svg = '') {
		if (false === ($dom = static::import($svg))) {
			return false;
		}

		try {

			$svgs = $dom->getElementsByTagName('svg');
			if (!$svgs->length) {
				return false;
			}

			$svg = $svgs->item(0);

			$width = $svg->hasAttribute('width') ? $svg->getAttribute('width') : null;
			$height = $svg->hasAttribute('height') ? $svg->getAttribute('height') : null;
			$viewbox = $svg->hasAttribute('viewBox') ? $svg->getAttribute('viewBox') : null;

			// Prefer the viewbox as it is more likely to be numeric,
			// and also more likely to reflect the real dimensions.
			if (!is_null($viewbox)) {
				$viewbox = trim(preg_replace('/\s+/', ' ', $viewbox));
				$viewbox = explode(' ', $viewbox);
				if (count($viewbox) === 4) {
					$viewbox[2] = (float) $viewbox[2];
					$viewbox[3] = (float) $viewbox[3];

					if ($viewbox[2] > 0 && $viewbox[3] > 0) {
						return array(
							'width'=>$viewbox[2],
							'height'=>$viewbox[3],
						);
					}
				}
			}

			// Otherwise maybe the width and height are good?
			if (is_numeric($width) && is_numeric($height)) {
				$width = (float) $width;
				$height = (float) $height;

				if ($width > 0 && $height > 0) {
					return array(
						'width'=>$width,
						'height'=>$height,
					);
				}
			}
		} catch (\Throwable $e) {
			error_log($e->getMessage());
			return false;
		} catch (\Exception $e) {
			return false;
		}

		return false;
	}

	/**
	 * Remove nodes from a DOMNodeList
	 *
	 * @param DOMNodeList $nodes Nodes.
	 * @return bool True/False.
	 */
	protected static function remove_nodes($nodes) {
		if (!is_a($nodes, 'DOMNodeList')) {
			return false;
		}

		try {
			while ($nodes->length) {
				static::remove_node($nodes->item(0));
			}
			return true;
		} catch (\Throwable $e) {
			error_log($e->getMessage());
			return false;
		} catch (\Exception $e) {
			return false;
		}

		return false;
	}

	/**
	 * Remove node from a DOMDocument
	 *
	 * @param DOMElement $node Node.
	 * @return bool True/False.
	 */
	protected static function remove_node($node) {
		if (
			!is_a($node, 'DOMElement') &&
			!is_a($node, 'DOMNode')
		) {
			return false;
		}

		try {
			// @codingStandardsIgnoreStart
			$node->parentNode->removeChild($node);
			// @codingStandardsIgnoreEnd
			return true;
		} catch (\Throwable $e) {
			error_log($e->getMessage());
			return false;
		} catch (\Exception $e) {
			return false;
		}

		return false;
	}

	/**
	 * Remove namespace (and attached nodes) from a DOMDocument
	 *
	 * @param DOMDocument $dom Object.
	 * @param string      $namespace Namespace.
	 * @return bool True/False.
	 */
	protected static function remove_namespace($dom, $namespace) {
		if (
			!is_a($dom, 'DOMDocument') ||
			!is_string($namespace) ||
			!strlen($namespace)
		) {
			return false;
		}

		try {
			$xpath = new DOMXPath($dom);
			$nodes = $xpath->query("//*[namespace::{$namespace} and not(../namespace::{$namespace})]");
			for ($x = 0; $x < $nodes->length; $x++) {
				$node = $nodes->item($x);
				$node->removeAttributeNS($node->lookupNamespaceURI($namespace), $namespace);
			}

			return true;
		} catch (\Throwable $e) {
			error_log($e->getMessage());
			return false;
		} catch (\Exception $e) {
			return false;
		}

		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\Loader;

use Symfony\Component\Yaml\Yaml;

/**
 * The Yaml file loader
 *
 * @since 0.1.0
 */
class YamlLoader extends AbstractLoader
{
    /**
     * {@inheritdoc}
     */
    public static $supported = array('yaml', 'yml');

    /**
     * {@inheritdoc}
     *
     * @throws \RuntimeException If `symfony/yaml` library is not installed or the file can not be parsed
     */
    public function load()
    {
        try {
            $this->content = Yaml::parse(file_get_contents($this->entity));
        } catch (\Exception $e) {
            throw new \RuntimeException(sprintf(
                "%s threw an exception: %s",
                $this->entity,
                $e
            ));
        }

        return $this;
    }
}