Commit realizado el 12:13:52 08-04-2024
This commit is contained in:
0
wp-content/plugins/web-stories/third-party/maintenance.php
vendored
Normal file
0
wp-content/plugins/web-stories/third-party/maintenance.php
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"ampRuntimeVersion":"012307052224000","ampCssUrl":"https://cdn.ampproject.org/rtv/012307052224000/v0.css","canaryPercentage":"0.005","diversions":["002307150128000","022307052224000","032307150128000","042307212240000","052307052224000","112307150128000"],"ltsRuntimeVersion":"012306202201000","ltsCssUrl":"https://cdn.ampproject.org/rtv/012306202201000/v0.css"}
|
File diff suppressed because one or more lines are too long
272
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Amp.php
vendored
Normal file
272
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Amp.php
vendored
Normal file
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Element;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Attribute;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Tag;
|
||||
use DOMNode;
|
||||
/**
|
||||
* Central helper functionality for all Amp-related PHP code.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class Amp
|
||||
{
|
||||
/**
|
||||
* Attribute prefix for AMP-bind data attributes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BIND_DATA_ATTR_PREFIX = 'data-amp-bind-';
|
||||
/**
|
||||
* List of AMP attribute tags that can be appended to the <html> element.
|
||||
*
|
||||
* The *_ALT version represent a Unicode variation of the lightning emoji.
|
||||
* @see https://github.com/ampproject/amphtml/issues/25990
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const TAGS = [Attribute::AMP, Attribute::AMP_EMOJI, Attribute::AMP_EMOJI_ALT, Attribute::AMP4ADS, Attribute::AMP4ADS_EMOJI, Attribute::AMP4ADS_EMOJI_ALT, Attribute::AMP4EMAIL, Attribute::AMP4EMAIL_EMOJI, Attribute::AMP4EMAIL_EMOJI_ALT];
|
||||
/**
|
||||
* Host and scheme of the AMP cache.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CACHE_HOST = 'https://cdn.ampproject.org';
|
||||
/**
|
||||
* URL of the AMP cache.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CACHE_ROOT_URL = self::CACHE_HOST . '/';
|
||||
/**
|
||||
* List of valid AMP HTML formats.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const FORMATS = [Format::AMP, Format::AMP4ADS, Format::AMP4EMAIL];
|
||||
/**
|
||||
* List of dynamic components.
|
||||
*
|
||||
* This list should be kept in sync with the list of dynamic components at:
|
||||
*
|
||||
* @see https://github.com/ampproject/amphtml/blob/292dc66b8c0bb078bbe3a1bca960e8f494f7fc8f/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem
|
||||
*
|
||||
* @var array[]
|
||||
*/
|
||||
const DYNAMIC_COMPONENTS = [Attribute::CUSTOM_ELEMENT => [Extension::GEO], Attribute::CUSTOM_TEMPLATE => []];
|
||||
/**
|
||||
* Array of custom element names that delay rendering.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const RENDER_DELAYING_EXTENSIONS = [Extension::DYNAMIC_CSS_CLASSES, Extension::EXPERIMENT, Extension::STORY];
|
||||
/**
|
||||
* Standard boilerplate CSS stylesheet.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BOILERPLATE_CSS = 'body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}';
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
/**
|
||||
* Boilerplate CSS stylesheet for the <noscript> tag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BOILERPLATE_NOSCRIPT_CSS = 'body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}';
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
/**
|
||||
* Boilerplate CSS stylesheet for Amp4Ads & Amp4Email.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP4ADS_AND_AMP4EMAIL_BOILERPLATE_CSS = 'body{visibility:hidden}';
|
||||
/**
|
||||
* AMP runtime tag name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const RUNTIME = 'amp-runtime';
|
||||
// AMP classes reserved for internal use.
|
||||
const LAYOUT_ATTRIBUTE = 'i-amphtml-layout';
|
||||
const NO_BOILERPLATE_ATTRIBUTE = 'i-amphtml-no-boilerplate';
|
||||
const LAYOUT_CLASS_PREFIX = 'i-amphtml-layout-';
|
||||
const LAYOUT_SIZE_DEFINED_CLASS = 'i-amphtml-layout-size-defined';
|
||||
const SIZER_ELEMENT = 'i-amphtml-sizer';
|
||||
const INTRINSIC_SIZER_ELEMENT = 'i-amphtml-intrinsic-sizer';
|
||||
const LAYOUT_AWAITING_SIZE_CLASS = 'i-amphtml-layout-awaiting-size';
|
||||
/**
|
||||
* Slot used by AMP for all service elements, like "i-amphtml-sizer" elements and similar.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SERVICE_SLOT = 'i-amphtml-svc';
|
||||
/**
|
||||
* Check if a given node is the AMP runtime script.
|
||||
*
|
||||
* The AMP runtime script node is of the form '<script async src="https://cdn.ampproject.org...v0.js"></script>'.
|
||||
*
|
||||
* @param DOMNode $node Node to check.
|
||||
* @return bool Whether the given node is the AMP runtime script.
|
||||
*/
|
||||
public static function isRuntimeScript(DOMNode $node)
|
||||
{
|
||||
if (!$node instanceof Element || !self::isAsyncScript($node) || self::isExtension($node)) {
|
||||
return \false;
|
||||
}
|
||||
$src = $node->getAttribute(Attribute::SRC);
|
||||
if (\strpos($src, self::CACHE_ROOT_URL) !== 0) {
|
||||
return \false;
|
||||
}
|
||||
if (\substr($src, -6) !== '/v0.js' && \substr($src, -7) !== '/v0.mjs' && \substr($src, -14) !== '/amp4ads-v0.js' && \substr($src, -15) !== '/amp4ads-v0.mjs') {
|
||||
return \false;
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Check if a given node is the AMP viewer script.
|
||||
*
|
||||
* The AMP viewer script node is of the form '<script async
|
||||
* src="https://cdn.ampproject.org/v0/amp-viewer-integration-...js>"</script>'.
|
||||
*
|
||||
* @param DOMNode $node Node to check.
|
||||
* @return bool Whether the given node is the AMP runtime script.
|
||||
*/
|
||||
public static function isViewerScript(DOMNode $node)
|
||||
{
|
||||
if (!$node instanceof Element || !self::isAsyncScript($node) || self::isExtension($node)) {
|
||||
return \false;
|
||||
}
|
||||
$src = $node->getAttribute(Attribute::SRC);
|
||||
if (\strpos($src, self::CACHE_HOST . '/v0/amp-viewer-integration-') !== 0) {
|
||||
return \false;
|
||||
}
|
||||
if (\substr($src, -3) !== '.js') {
|
||||
return \false;
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Check if a given node is an AMP extension.
|
||||
*
|
||||
* @param DOMNode $node Node to check.
|
||||
* @return bool Whether the given node is the AMP runtime script.
|
||||
*/
|
||||
public static function isExtension(DOMNode $node)
|
||||
{
|
||||
return !empty(self::getExtensionName($node));
|
||||
}
|
||||
/**
|
||||
* Get the name of the extension.
|
||||
*
|
||||
* Returns an empty string if the name of the extension could not be retrieved.
|
||||
*
|
||||
* @param DOMNode $node Node to get the name of.
|
||||
* @return string Name of the custom node or template. Empty string if none found.
|
||||
*/
|
||||
public static function getExtensionName(DOMNode $node)
|
||||
{
|
||||
if (!$node instanceof Element || $node->tagName !== Tag::SCRIPT) {
|
||||
return '';
|
||||
}
|
||||
if ($node->hasAttribute(Attribute::CUSTOM_ELEMENT)) {
|
||||
return $node->getAttribute(Attribute::CUSTOM_ELEMENT);
|
||||
}
|
||||
if ($node->hasAttribute(Attribute::CUSTOM_TEMPLATE)) {
|
||||
return $node->getAttribute(Attribute::CUSTOM_TEMPLATE);
|
||||
}
|
||||
if ($node->hasAttribute(Attribute::HOST_SERVICE)) {
|
||||
return $node->getAttribute(Attribute::HOST_SERVICE);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
/**
|
||||
* Check whether a given node is a script for a render-delaying extension.
|
||||
*
|
||||
* @param DOMNode $node Node to check.
|
||||
* @return bool Whether the node is a script for a render-delaying extension.
|
||||
*/
|
||||
public static function isRenderDelayingExtension(DOMNode $node)
|
||||
{
|
||||
$extensionName = self::getExtensionName($node);
|
||||
if (empty($extensionName)) {
|
||||
return \false;
|
||||
}
|
||||
return \in_array($extensionName, self::RENDER_DELAYING_EXTENSIONS, \true);
|
||||
}
|
||||
/**
|
||||
* Check whether a given DOM node is an AMP custom element.
|
||||
*
|
||||
* @param DOMNode $node DOM node to check.
|
||||
* @return bool Whether the checked DOM node is an AMP custom element.
|
||||
*/
|
||||
public static function isCustomElement(DOMNode $node)
|
||||
{
|
||||
return $node instanceof Element && \strpos($node->tagName, Extension::PREFIX) === 0;
|
||||
}
|
||||
/**
|
||||
* Check whether the given document is an AMP story.
|
||||
*
|
||||
* @param Document $document Document of the page to check within.
|
||||
* @return bool Whether the provided document is an AMP story.
|
||||
*/
|
||||
public static function isAmpStory(Document $document)
|
||||
{
|
||||
foreach ($document->head->childNodes as $node) {
|
||||
if ($node instanceof Element && $node->tagName === Tag::SCRIPT && $node->getAttribute(Attribute::CUSTOM_ELEMENT) === Extension::STORY) {
|
||||
return \true;
|
||||
}
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
/**
|
||||
* Check whether a given node is an AMP template.
|
||||
*
|
||||
* @param DOMNode $node Node to check.
|
||||
* @return bool Whether the node is an AMP template.
|
||||
*/
|
||||
public static function isTemplate(DOMNode $node)
|
||||
{
|
||||
if (!$node instanceof Element) {
|
||||
return \false;
|
||||
}
|
||||
if ($node->tagName === Tag::TEMPLATE) {
|
||||
return \true;
|
||||
}
|
||||
if ($node->tagName === Tag::SCRIPT && $node->hasAttribute(Attribute::TEMPLATE) && $node->getAttribute(Attribute::TEMPLATE) === Extension::MUSTACHE) {
|
||||
return \true;
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
/**
|
||||
* Check whether a given node is an async <script> element.
|
||||
*
|
||||
* @param DOMNode $node Node to check.
|
||||
* @return bool Whether the given node is an async <script> element.
|
||||
*/
|
||||
private static function isAsyncScript(DOMNode $node)
|
||||
{
|
||||
if (!$node instanceof Element || $node->tagName !== Tag::SCRIPT) {
|
||||
return \false;
|
||||
}
|
||||
if (!$node->hasAttribute(Attribute::SRC) || !$node->hasAttribute(Attribute::ASYNC)) {
|
||||
return \false;
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Check whether a given node is an AMP iframe.
|
||||
*
|
||||
* @param DOMNode $node Node to check.
|
||||
* @return bool Whether the node is an AMP iframe.
|
||||
*/
|
||||
public static function isAmpIframe(DOMNode $node)
|
||||
{
|
||||
if (!$node instanceof Element) {
|
||||
return \false;
|
||||
}
|
||||
return $node->tagName === Extension::IFRAME || $node->tagName === Extension::VIDEO_IFRAME;
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\Cli\InvalidCommand;
|
||||
/**
|
||||
* Executable that assembles all of the commands.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class AmpExecutable extends Executable
|
||||
{
|
||||
/**
|
||||
* Array of command classes to register.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const COMMAND_CLASSES = [Command\Optimize::class, Command\Validate::class];
|
||||
/**
|
||||
* Array of command object instances.
|
||||
*
|
||||
* @var Command[]
|
||||
*/
|
||||
private $commandInstances = [];
|
||||
/**
|
||||
* Register options and arguments on the given $options object.
|
||||
*
|
||||
* @param Options $options Options instance to register the commands with.
|
||||
* @return void
|
||||
*/
|
||||
protected function setup(Options $options)
|
||||
{
|
||||
foreach (self::COMMAND_CLASSES as $commandClass) {
|
||||
/** @var Command $command */
|
||||
$command = new $commandClass($this);
|
||||
$command->register($options);
|
||||
$this->commandInstances[$command->getName()] = $command;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Your main program.
|
||||
*
|
||||
* Arguments and options have been parsed when this is run.
|
||||
*
|
||||
* @param Options $options Options instance to register the commands with.
|
||||
* @return void
|
||||
*/
|
||||
protected function main(Options $options)
|
||||
{
|
||||
$commandName = $options->getCommand();
|
||||
if (empty($commandName)) {
|
||||
echo $this->options->help();
|
||||
exit(1);
|
||||
}
|
||||
if (!\array_key_exists($commandName, $this->commandInstances)) {
|
||||
throw InvalidCommand::forUnregisteredCommand($commandName);
|
||||
}
|
||||
$command = $this->commandInstances[$commandName];
|
||||
$command->process($options);
|
||||
}
|
||||
}
|
143
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Cli/Colors.php
vendored
Normal file
143
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Cli/Colors.php
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\Cli\InvalidColor;
|
||||
/**
|
||||
* This file is adapted from the splitbrain\php-cli library, which is authored by Andreas Gohr <andi@splitbrain.org> and
|
||||
* licensed under the MIT license.
|
||||
*
|
||||
* Source: https://github.com/splitbrain/php-cli/blob/8c2c001b1b55d194402cf18aad2757049ac6d575/src/Colors.php
|
||||
*/
|
||||
/**
|
||||
* Handles color output on (Unix) terminals.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
class Colors
|
||||
{
|
||||
const C_BLACK = 'black';
|
||||
const C_BLUE = 'blue';
|
||||
const C_BROWN = 'brown';
|
||||
const C_CYAN = 'cyan';
|
||||
const C_DARKGRAY = 'darkgray';
|
||||
const C_GREEN = 'green';
|
||||
const C_LIGHTBLUE = 'lightblue';
|
||||
const C_LIGHTCYAN = 'lightcyan';
|
||||
const C_LIGHTGRAY = 'lightgray';
|
||||
const C_LIGHTGREEN = 'lightgreen';
|
||||
const C_LIGHTPURPLE = 'lightpurple';
|
||||
const C_LIGHTRED = 'lightred';
|
||||
const C_PURPLE = 'purple';
|
||||
const C_RED = 'red';
|
||||
const C_RESET = 'reset';
|
||||
const C_WHITE = 'white';
|
||||
const C_YELLOW = 'yellow';
|
||||
/**
|
||||
* Associative array of known color names.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
const KNOWN_COLORS = [self::C_RESET => "\x1b[0m", self::C_BLACK => "\x1b[0;30m", self::C_DARKGRAY => "\x1b[1;30m", self::C_BLUE => "\x1b[0;34m", self::C_LIGHTBLUE => "\x1b[1;34m", self::C_GREEN => "\x1b[0;32m", self::C_LIGHTGREEN => "\x1b[1;32m", self::C_CYAN => "\x1b[0;36m", self::C_LIGHTCYAN => "\x1b[1;36m", self::C_RED => "\x1b[0;31m", self::C_LIGHTRED => "\x1b[1;31m", self::C_PURPLE => "\x1b[0;35m", self::C_LIGHTPURPLE => "\x1b[1;35m", self::C_BROWN => "\x1b[0;33m", self::C_YELLOW => "\x1b[1;33m", self::C_LIGHTGRAY => "\x1b[0;37m", self::C_WHITE => "\x1b[1;37m"];
|
||||
/**
|
||||
* Whether colors should be used.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $enabled = \true;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Tries to disable colors for non-terminals.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->enabled = \getenv('TERM') || \function_exists('posix_isatty') && \posix_isatty(\STDOUT);
|
||||
}
|
||||
/**
|
||||
* Enable color output.
|
||||
*/
|
||||
public function enable()
|
||||
{
|
||||
$this->enabled = \true;
|
||||
}
|
||||
/**
|
||||
* Disable color output.
|
||||
*/
|
||||
public function disable()
|
||||
{
|
||||
$this->enabled = \false;
|
||||
}
|
||||
/**
|
||||
* Check whether color support is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
/**
|
||||
* Convenience function to print a line in a given color.
|
||||
*
|
||||
* @param string $line The line to print. A new line is added automatically.
|
||||
* @param string $color One of the available color names.
|
||||
* @param resource $channel Optional. File descriptor to write to. Defaults to STDOUT.
|
||||
* @throws InvalidColor If the requested color code is not known.
|
||||
*/
|
||||
public function line($line, $color, $channel = \STDOUT)
|
||||
{
|
||||
$this->set($color);
|
||||
\fwrite($channel, \rtrim($line) . "\n");
|
||||
$this->reset();
|
||||
}
|
||||
/**
|
||||
* Returns the given text wrapped in the appropriate color and reset code
|
||||
*
|
||||
* @param string $text String to wrap.
|
||||
* @param string $color One of the available color names.
|
||||
* @return string The wrapped string.
|
||||
* @throws InvalidColor If the requested color code is not known.
|
||||
*/
|
||||
public function wrap($text, $color)
|
||||
{
|
||||
return $this->getColorCode($color) . $text . $this->getColorCode(self::C_RESET);
|
||||
}
|
||||
/**
|
||||
* Gets the appropriate terminal code for the given color.
|
||||
*
|
||||
* @param string $color One of the available color names.
|
||||
* @return string Color code.
|
||||
* @throws InvalidColor If the requested color code is not known.
|
||||
*/
|
||||
public function getColorCode($color)
|
||||
{
|
||||
if (!\array_key_exists($color, self::KNOWN_COLORS)) {
|
||||
throw InvalidColor::forUnknownColor($color);
|
||||
}
|
||||
if (!$this->enabled) {
|
||||
return '';
|
||||
}
|
||||
return self::KNOWN_COLORS[$color];
|
||||
}
|
||||
/**
|
||||
* Set the given color for consecutive output.
|
||||
*
|
||||
* @param string $color One of the supported color names.
|
||||
* @param resource $channel Optional. File descriptor to write to. Defaults to STDOUT.
|
||||
* @throws InvalidColor If the requested color code is not known.
|
||||
*/
|
||||
public function set($color, $channel = \STDOUT)
|
||||
{
|
||||
\fwrite($channel, $this->getColorCode($color));
|
||||
}
|
||||
/**
|
||||
* Reset the terminal color.
|
||||
*
|
||||
* @param resource $channel Optional. File descriptor to write to. Defaults to STDOUT.
|
||||
*/
|
||||
public function reset($channel = \STDOUT)
|
||||
{
|
||||
$this->set(self::C_RESET, $channel);
|
||||
}
|
||||
}
|
58
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Cli/Command.php
vendored
Normal file
58
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Cli/Command.php
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Cli;
|
||||
|
||||
/**
|
||||
* A command that is registered with the amp executable.
|
||||
*
|
||||
* @package AmpProject\Cli
|
||||
*/
|
||||
abstract class Command
|
||||
{
|
||||
/**
|
||||
* Name of the command.
|
||||
*
|
||||
* This needs to be overridden in extending commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NAME = '<unknown>';
|
||||
/**
|
||||
* Instance of the CLI executable that the command belongs to.
|
||||
*
|
||||
* @var Executable
|
||||
*/
|
||||
protected $cli;
|
||||
/**
|
||||
* Instantiate the command.
|
||||
*
|
||||
* @param Executable $cli Instance of the CLI executable that the command belongs to.
|
||||
*/
|
||||
public function __construct(Executable $cli)
|
||||
{
|
||||
$this->cli = $cli;
|
||||
}
|
||||
/**
|
||||
* Get the name of the command.
|
||||
*
|
||||
* @return string Name of the command.
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return static::NAME;
|
||||
}
|
||||
/**
|
||||
* Register the command.
|
||||
*
|
||||
* @param Options $options Options instance to register the command with.
|
||||
*/
|
||||
public abstract function register(Options $options);
|
||||
/**
|
||||
* Process the command.
|
||||
*
|
||||
* Arguments and options have been parsed when this is run.
|
||||
*
|
||||
* @param Options $options Options instance to process the command with.
|
||||
*/
|
||||
public abstract function process(Options $options);
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Cli\Command;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Cli\Command;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Cli\Options;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\Cli\InvalidArgument;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\ErrorCollection;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\TransformationEngine;
|
||||
/**
|
||||
* Optimize AMP HTML markup and return optimized markup.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class Optimize extends Command
|
||||
{
|
||||
/**
|
||||
* Name of the command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NAME = 'optimize';
|
||||
/**
|
||||
* Help text of the command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HELP_TEXT = 'Optimize AMP HTML markup and return optimized markup.';
|
||||
/**
|
||||
* Register the command.
|
||||
*
|
||||
* @param Options $options Options instance to register the command with.
|
||||
*/
|
||||
public function register(Options $options)
|
||||
{
|
||||
$options->registerCommand(self::NAME, self::HELP_TEXT);
|
||||
$options->registerArgument('file', "File with unoptimized AMP markup. Use '-' for STDIN.", \true, self::NAME);
|
||||
}
|
||||
/**
|
||||
* Process the command.
|
||||
*
|
||||
* Arguments and options have been parsed when this is run.
|
||||
*
|
||||
* @param Options $options Options instance to process the command with.
|
||||
*
|
||||
* @throws InvalidArgument If the provided file is not readable.
|
||||
*/
|
||||
public function process(Options $options)
|
||||
{
|
||||
list($file) = $options->getArguments();
|
||||
if ($file !== '-' && (!\is_file($file) || !\is_readable($file))) {
|
||||
throw InvalidArgument::forUnreadableFile($file);
|
||||
}
|
||||
if ($file === '-') {
|
||||
$file = 'php://stdin';
|
||||
}
|
||||
$html = \file_get_contents($file);
|
||||
$optimizer = new TransformationEngine();
|
||||
$errors = new ErrorCollection();
|
||||
$optimizedHtml = $optimizer->optimizeHtml($html, $errors);
|
||||
echo $optimizedHtml . \PHP_EOL;
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Cli\Command;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Cli\Colors;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Cli\Command;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Cli\Options;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Cli\TableFormatter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\Cli\InvalidArgument;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Validator\ValidationEngine;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Validator\ValidationStatus;
|
||||
/**
|
||||
* Validate AMP HTML markup and return validation errors.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class Validate extends Command
|
||||
{
|
||||
/**
|
||||
* Name of the command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NAME = 'validate';
|
||||
/**
|
||||
* Help text of the command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HELP_TEXT = 'Validate AMP HTML markup and return validation errors.';
|
||||
/**
|
||||
* Register the command.
|
||||
*
|
||||
* @param Options $options Options instance to register the command with.
|
||||
*/
|
||||
public function register(Options $options)
|
||||
{
|
||||
$options->registerCommand(self::NAME, self::HELP_TEXT);
|
||||
$options->registerArgument('file', "File with AMP markup to validate. Use '-' for STDIN.", \true, self::NAME);
|
||||
}
|
||||
/**
|
||||
* Process the command.
|
||||
*
|
||||
* Arguments and options have been parsed when this is run.
|
||||
*
|
||||
* @param Options $options Options instance to process the command with.
|
||||
*
|
||||
* @throws InvalidArgument If the provided file is not readable.
|
||||
*/
|
||||
public function process(Options $options)
|
||||
{
|
||||
list($file) = $options->getArguments();
|
||||
if ($file !== '-' && (!\is_file($file) || !\is_readable($file))) {
|
||||
throw InvalidArgument::forUnreadableFile($file);
|
||||
}
|
||||
if ($file === '-') {
|
||||
$file = 'php://stdin';
|
||||
}
|
||||
$html = \file_get_contents($file);
|
||||
$validator = new ValidationEngine();
|
||||
$result = $validator->validateHtml($html);
|
||||
foreach ($result->getErrors() as $error) {
|
||||
echo \sprintf("%d:%d [%s] %s (%s)\n", $error->getLine(), $error->getColumn(), $error->getSeverity(), $error->getCode(), \implode(', ', $error->getParams()));
|
||||
}
|
||||
switch ($result->getStatus()->asInt()) {
|
||||
case ValidationStatus::PASS:
|
||||
$this->cli->success('Validation SUCCEEDED.');
|
||||
exit(0);
|
||||
case ValidationStatus::FAIL:
|
||||
$this->cli->error('Validation FAILED!');
|
||||
exit(1);
|
||||
case ValidationStatus::UNKNOWN:
|
||||
$this->cli->critical('Validation produced an UNKNOWN state!');
|
||||
exit(128);
|
||||
}
|
||||
}
|
||||
}
|
336
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Cli/Executable.php
vendored
Normal file
336
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Cli/Executable.php
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\AmpCliException;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\Cli\InvalidSapi;
|
||||
use Exception;
|
||||
/**
|
||||
* This file is adapted from the splitbrain\php-cli library, which is authored by Andreas Gohr <andi@splitbrain.org> and
|
||||
* licensed under the MIT license.
|
||||
*
|
||||
* Source: https://github.com/splitbrain/php-cli/blob/fb4f888866d090b10e3e68292d197ca274cea626/src/CLI.php
|
||||
*/
|
||||
/**
|
||||
* Your commandline script should inherit from this class and implement the abstract methods.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
abstract class Executable
|
||||
{
|
||||
/**
|
||||
* Instance of the Colors helper object.
|
||||
*
|
||||
* @var Colors
|
||||
*/
|
||||
public $colors;
|
||||
/**
|
||||
* The executable script itself.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bin;
|
||||
/**
|
||||
* Instance of the options parser to use.
|
||||
*
|
||||
* @var Options
|
||||
*/
|
||||
protected $options;
|
||||
/**
|
||||
* PSR-3 compatible log levels and their prefix, color, output channel.
|
||||
*
|
||||
* @var array<array>
|
||||
*/
|
||||
protected $loglevels = [LogLevel::DEBUG => ['', Colors::C_RESET, \STDOUT], LogLevel::INFO => ['ℹ ', Colors::C_CYAN, \STDOUT], LogLevel::NOTICE => ['☛ ', Colors::C_CYAN, \STDOUT], LogLevel::SUCCESS => ['✓ ', Colors::C_GREEN, \STDOUT], LogLevel::WARNING => ['⚠ ', Colors::C_BROWN, \STDERR], LogLevel::ERROR => ['✗ ', Colors::C_RED, \STDERR], LogLevel::CRITICAL => ['☠ ', Colors::C_LIGHTRED, \STDERR], LogLevel::ALERT => ['✖ ', Colors::C_LIGHTRED, \STDERR], LogLevel::EMERGENCY => ['✘ ', Colors::C_LIGHTRED, \STDERR]];
|
||||
/**
|
||||
* Default log level.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $loglevel = 'info';
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Initialize the arguments, set up helper classes and set up the CLI environment.
|
||||
*
|
||||
* @param bool $autocatch Optional. Whether exceptions should be caught and handled automatically. Defaults
|
||||
* to true.
|
||||
* @param Options|null $options Optional. Instance of the Options object to use. Defaults to null to instantiate a
|
||||
* new one.
|
||||
* @param Colors|null $colors Optional. Instance of the Colors object to use. Defaults to null to instantiate a
|
||||
* new one.
|
||||
*/
|
||||
public function __construct($autocatch = \true, Options $options = null, Colors $colors = null)
|
||||
{
|
||||
if ($autocatch) {
|
||||
\set_exception_handler([$this, 'fatal']);
|
||||
}
|
||||
$this->colors = $colors instanceof Colors ? $colors : new Colors();
|
||||
$this->options = $options instanceof Options ? $options : new Options($this->colors);
|
||||
}
|
||||
/**
|
||||
* Execute the CLI program.
|
||||
*
|
||||
* Executes the setup() routine, adds default options, initiate the options parsing and argument checking
|
||||
* and finally executes main() - Each part is split into their own protected function below, so behaviour
|
||||
* can easily be overwritten.
|
||||
*
|
||||
* @param bool $exitOnCompletion Optional. Whether to exit on completion. Defaults to true.
|
||||
* @throws InvalidSapi If a SAPI other than 'cli' is detected.
|
||||
*/
|
||||
public function run($exitOnCompletion = \true)
|
||||
{
|
||||
$sapi = \php_sapi_name();
|
||||
if ('cli' !== $sapi) {
|
||||
throw InvalidSapi::forSapi($sapi);
|
||||
}
|
||||
$this->setup($this->options);
|
||||
$this->registerDefaultOptions();
|
||||
$this->parseOptions();
|
||||
$this->handleDefaultOptions();
|
||||
$this->setupLogging();
|
||||
$this->checkArguments();
|
||||
$this->execute();
|
||||
if ($exitOnCompletion) {
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Exits the program on a fatal error.
|
||||
*
|
||||
* @param Exception|string $error Either an exception or an error message.
|
||||
* @param array $context Optional. Associative array of contextual information. Defaults to an empty
|
||||
* array.
|
||||
*/
|
||||
public function fatal($error, array $context = [])
|
||||
{
|
||||
$code = 0;
|
||||
if ($error instanceof Exception) {
|
||||
$this->debug(\get_class($error) . ' caught in ' . $error->getFile() . ':' . $error->getLine());
|
||||
$this->debug($error->getTraceAsString());
|
||||
$code = $error->getCode();
|
||||
$error = $error->getMessage();
|
||||
}
|
||||
if (!$code) {
|
||||
$code = AmpCliException::E_ANY;
|
||||
}
|
||||
$this->critical($error, $context);
|
||||
exit($code);
|
||||
}
|
||||
/**
|
||||
* System is unusable.
|
||||
*
|
||||
* @param string $message Log message.
|
||||
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
||||
* @return void
|
||||
*/
|
||||
public function emergency($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::EMERGENCY, $message, $context);
|
||||
}
|
||||
/**
|
||||
* Action must be taken immediately.
|
||||
*
|
||||
* Example: Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up.
|
||||
*
|
||||
* @param string $message Log message.
|
||||
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
||||
* @return void
|
||||
*/
|
||||
public function alert($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::ALERT, $message, $context);
|
||||
}
|
||||
/**
|
||||
* Critical conditions.
|
||||
*
|
||||
* Example: Application component unavailable, unexpected exception.
|
||||
*
|
||||
* @param string $message Log message.
|
||||
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
||||
* @return void
|
||||
*/
|
||||
public function critical($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::CRITICAL, $message, $context);
|
||||
}
|
||||
/**
|
||||
* Runtime errors that do not require immediate action but should typically be logged and monitored.
|
||||
*
|
||||
* @param string $message Log message.
|
||||
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
||||
* @return void
|
||||
*/
|
||||
public function error($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::ERROR, $message, $context);
|
||||
}
|
||||
/**
|
||||
* Exceptional occurrences that are not errors.
|
||||
*
|
||||
* Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
|
||||
*
|
||||
* @param string $message Log message.
|
||||
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
||||
* @return void
|
||||
*/
|
||||
public function warning($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::WARNING, $message, $context);
|
||||
}
|
||||
/**
|
||||
* Normal, positive outcome.
|
||||
*
|
||||
* @param string $string Log message.
|
||||
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
||||
* @return void
|
||||
*/
|
||||
public function success($string, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::SUCCESS, $string, $context);
|
||||
}
|
||||
/**
|
||||
* Normal but significant events.
|
||||
*
|
||||
* @param string $message Log message.
|
||||
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
||||
* @return void
|
||||
*/
|
||||
public function notice($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::NOTICE, $message, $context);
|
||||
}
|
||||
/**
|
||||
* Interesting events.
|
||||
*
|
||||
* Example: User logs in, SQL logs.
|
||||
*
|
||||
* @param string $message Log message.
|
||||
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
||||
* @return void
|
||||
*/
|
||||
public function info($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::INFO, $message, $context);
|
||||
}
|
||||
/**
|
||||
* Detailed debug information.
|
||||
*
|
||||
* @param string $message Log message.
|
||||
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
||||
* @return void
|
||||
*/
|
||||
public function debug($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::DEBUG, $message, $context);
|
||||
}
|
||||
/**
|
||||
* Log a message of a given log level to the logs.
|
||||
*
|
||||
* @param string $level Log level to use.
|
||||
* @param string $message Log message.
|
||||
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
||||
* @return void
|
||||
*/
|
||||
public function log($level, $message, array $context = [])
|
||||
{
|
||||
if (!LogLevel::matches($level, $this->options->getOption('loglevel', $this->loglevel))) {
|
||||
return;
|
||||
}
|
||||
list($prefix, $color, $channel) = $this->loglevels[$level];
|
||||
if (!$this->colors->isEnabled()) {
|
||||
$prefix = '';
|
||||
}
|
||||
$message = $this->interpolate($message, $context);
|
||||
$this->colors->line($prefix . $message, $color, $channel);
|
||||
}
|
||||
/**
|
||||
* Interpolates context values into the message placeholders.
|
||||
*
|
||||
* @param string $message Message to interpolate.
|
||||
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
||||
* @return string Interpolated string.
|
||||
*/
|
||||
protected function interpolate($message, array $context = [])
|
||||
{
|
||||
// Build a replacement array with braces around the context keys.
|
||||
$replace = [];
|
||||
foreach ($context as $key => $val) {
|
||||
// Check that the value can be cast to string.
|
||||
if (!\is_array($val) && (!\is_object($val) || \method_exists($val, '__toString'))) {
|
||||
$replace['{' . $key . '}'] = $val;
|
||||
}
|
||||
}
|
||||
// Interpolate replacement values into the message and return.
|
||||
return \strtr($message, $replace);
|
||||
}
|
||||
/**
|
||||
* Add the default help, color and log options.
|
||||
*/
|
||||
protected function registerDefaultOptions()
|
||||
{
|
||||
$this->options->registerOption('help', 'Display this help screen and exit immediately.', 'h');
|
||||
$this->options->registerOption('no-colors', 'Do not use any colors in output. Useful when piping output to other tools or files.');
|
||||
$this->options->registerOption('loglevel', "Minimum level of messages to display. Default is {$this->colors->wrap($this->loglevel, Colors::C_CYAN)}." . ' Valid levels are: debug, info, notice, success, warning, error, critical, alert, emergency.', null, 'level');
|
||||
}
|
||||
/**
|
||||
* Handle the default options.
|
||||
*/
|
||||
protected function handleDefaultOptions()
|
||||
{
|
||||
if ($this->options->getOption('no-colors')) {
|
||||
$this->colors->disable();
|
||||
}
|
||||
if ($this->options->getOption('help')) {
|
||||
echo $this->options->help();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle the logging options.
|
||||
*/
|
||||
protected function setupLogging()
|
||||
{
|
||||
$this->loglevel = $this->options->getOption('loglevel', $this->loglevel);
|
||||
if (!\in_array($this->loglevel, LogLevel::ORDER)) {
|
||||
$this->fatal('Unknown log level');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Wrapper around the option parsing.
|
||||
*/
|
||||
protected function parseOptions()
|
||||
{
|
||||
$this->options->parseOptions();
|
||||
}
|
||||
/**
|
||||
* Wrapper around the argument checking.
|
||||
*/
|
||||
protected function checkArguments()
|
||||
{
|
||||
$this->options->checkArguments();
|
||||
}
|
||||
/**
|
||||
* Wrapper around main.
|
||||
*/
|
||||
protected function execute()
|
||||
{
|
||||
$this->main($this->options);
|
||||
}
|
||||
/**
|
||||
* Register options and arguments on the given $options object.
|
||||
*
|
||||
* @param Options $options Options instance to register the commands with.
|
||||
* @return void
|
||||
*/
|
||||
protected abstract function setup(Options $options);
|
||||
/**
|
||||
* Main program routine.
|
||||
*
|
||||
* Arguments and options have been parsed when this is run.
|
||||
*
|
||||
* @param Options $options Options instance to register the commands with.
|
||||
* @return void
|
||||
*/
|
||||
protected abstract function main(Options $options);
|
||||
}
|
94
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Cli/LogLevel.php
vendored
Normal file
94
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Cli/LogLevel.php
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\AmpCliException;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\Cli\InvalidSapi;
|
||||
use Exception;
|
||||
/**
|
||||
* Abstract class with the individual log levels.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
abstract class LogLevel
|
||||
{
|
||||
/**
|
||||
* Detailed debug information.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DEBUG = 'debug';
|
||||
/**
|
||||
* Interesting events.
|
||||
*
|
||||
* Example: User logs in, SQL logs.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const INFO = 'info';
|
||||
/**
|
||||
* Normal but significant events.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NOTICE = 'notice';
|
||||
/**
|
||||
* Normal, positive outcome.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SUCCESS = 'success';
|
||||
/**
|
||||
* Exceptional occurrences that are not errors.
|
||||
*
|
||||
* Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const WARNING = 'warning';
|
||||
/**
|
||||
* Runtime errors that do not require immediate action but should typically be logged and monitored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ERROR = 'error';
|
||||
/**
|
||||
* Critical conditions.
|
||||
*
|
||||
* Example: Application component unavailable, unexpected exception.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CRITICAL = 'critical';
|
||||
/**
|
||||
* Action must be taken immediately.
|
||||
*
|
||||
* Example: Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ALERT = 'alert';
|
||||
/**
|
||||
* System is unusable.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const EMERGENCY = 'emergency';
|
||||
/**
|
||||
* Ordering to use for log levels.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const ORDER = [self::DEBUG, self::INFO, self::NOTICE, self::SUCCESS, self::WARNING, self::ERROR, self::CRITICAL, self::ALERT, self::EMERGENCY];
|
||||
/**
|
||||
* Test whether a given log level matches the currently set threshold.
|
||||
*
|
||||
* @param string $logLevel Log level to check.
|
||||
* @param string $threshold Log level threshold to check against.
|
||||
* @return bool Whether the provided log level matches the threshold.
|
||||
*/
|
||||
public static function matches($logLevel, $threshold)
|
||||
{
|
||||
return \array_search($logLevel, self::ORDER, \true) >= \array_search($threshold, self::ORDER, \true);
|
||||
}
|
||||
}
|
455
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Cli/Options.php
vendored
Normal file
455
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Cli/Options.php
vendored
Normal file
@@ -0,0 +1,455 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\Cli\InvalidArgument;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\Cli\InvalidCommand;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\Cli\InvalidOption;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\Cli\MissingArgument;
|
||||
/**
|
||||
* This file is adapted from the splitbrain\php-cli library, which is authored by Andreas Gohr <andi@splitbrain.org> and
|
||||
* licensed under the MIT license.
|
||||
*
|
||||
* Source: https://github.com/splitbrain/php-cli/blob/8c2c001b1b55d194402cf18aad2757049ac6d575/src/Options.php
|
||||
*/
|
||||
/**
|
||||
* Parses command line options passed to the CLI script. Allows CLI scripts to easily register all accepted options and
|
||||
* commands and even generates a help text from this setup.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
class Options
|
||||
{
|
||||
/**
|
||||
* List of options to parse.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $setup;
|
||||
/**
|
||||
* Storage for parsed options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [];
|
||||
/**
|
||||
* Currently parsed command if any.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $command = '';
|
||||
/**
|
||||
* Passed non-option arguments.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
/**
|
||||
* Name of the executed script.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bin;
|
||||
/**
|
||||
* Instance of the Colors helper object.
|
||||
*
|
||||
* @var Colors
|
||||
*/
|
||||
protected $colors;
|
||||
/**
|
||||
* Newline used for spacing help texts.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $newline = "\n";
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Colors $colors Optional. Configured color object.
|
||||
* @throws InvalidArgument When arguments can't be read.
|
||||
*/
|
||||
public function __construct(Colors $colors = null)
|
||||
{
|
||||
$this->colors = $colors instanceof Colors ? $colors : new Colors();
|
||||
$this->setup = ['' => ['options' => [], 'arguments' => [], 'help' => '', 'commandHelp' => 'This tool accepts a command as first parameter as outlined below:']];
|
||||
// Default command.
|
||||
$this->arguments = $this->readPHPArgv();
|
||||
$this->bin = \basename(\array_shift($this->arguments));
|
||||
$this->options = [];
|
||||
}
|
||||
/**
|
||||
* Gets the name of the binary that was executed.
|
||||
*
|
||||
* @return string Name of the binary that was executed.
|
||||
*/
|
||||
public function getBin()
|
||||
{
|
||||
return $this->bin;
|
||||
}
|
||||
/**
|
||||
* Sets the help text for the tool itself.
|
||||
*
|
||||
* @param string $help Help text to set.
|
||||
*/
|
||||
public function setHelp($help)
|
||||
{
|
||||
$this->setup['']['help'] = $help;
|
||||
}
|
||||
/**
|
||||
* Sets the help text for the tools commands itself.
|
||||
*
|
||||
* @param string $help Help text to set.
|
||||
*/
|
||||
public function setCommandHelp($help)
|
||||
{
|
||||
$this->setup['']['commandHelp'] = $help;
|
||||
}
|
||||
/**
|
||||
* Use a more compact help screen with less new lines.
|
||||
*
|
||||
* @param bool $set Optional. Whether to set compact help or not. Defaults to true.
|
||||
*/
|
||||
public function useCompactHelp($set = \true)
|
||||
{
|
||||
$this->newline = $set ? '' : "\n";
|
||||
}
|
||||
/**
|
||||
* Register the names of arguments for help generation and number checking.
|
||||
*
|
||||
* This has to be called in the order arguments are expected.
|
||||
*
|
||||
* @param string $name Name of the argument.
|
||||
* @param string $help Help text.
|
||||
* @param bool $required Optional. Whether this argument is required. Defaults to true.
|
||||
* @param string $command Optional. Command this argument applies to. Empty string (default) for global arguments.
|
||||
* @throws InvalidCommand If the referenced command is not registered.
|
||||
*/
|
||||
public function registerArgument($name, $help, $required = \true, $command = '')
|
||||
{
|
||||
if (!isset($this->setup[$command])) {
|
||||
throw InvalidCommand::forUnregisteredCommand($command);
|
||||
}
|
||||
$this->setup[$command]['arguments'][] = ['name' => $name, 'help' => $help, 'required' => $required];
|
||||
}
|
||||
/**
|
||||
* Register a sub command.
|
||||
*
|
||||
* Sub commands have their own options and use their own function (not main()).
|
||||
*
|
||||
* @param string $name Name of the command to register.
|
||||
* @param string $help Help text of the command.
|
||||
* @throws InvalidCommand If the referenced command is already registered.
|
||||
*/
|
||||
public function registerCommand($name, $help)
|
||||
{
|
||||
if (isset($this->setup[$name])) {
|
||||
throw InvalidCommand::forAlreadyRegisteredCommand($name);
|
||||
}
|
||||
$this->setup[$name] = ['options' => [], 'arguments' => [], 'help' => $help];
|
||||
}
|
||||
/**
|
||||
* Register an option for option parsing and help generation.
|
||||
*
|
||||
* @param string $long Multi character option (specified with --).
|
||||
* @param string $help Help text for this option.
|
||||
* @param string|null $short Optional. One character option (specified with -). Disable with null (default).
|
||||
* @param bool|string $needsArgument Optional. Whether this option requires an argument. Use a boolean value, or
|
||||
* provide a string to require a specific argument by name. Defaults to false.
|
||||
* @param string $command Optional. Name of the command this option applies to. Use an empty string for
|
||||
* none (default).
|
||||
* @throws InvalidCommand If the referenced command is not registered.
|
||||
* @throws InvalidArgument If the short option is too long.
|
||||
*/
|
||||
public function registerOption($long, $help, $short = null, $needsArgument = \false, $command = '')
|
||||
{
|
||||
if (!isset($this->setup[$command])) {
|
||||
throw InvalidCommand::forUnregisteredCommand($command);
|
||||
}
|
||||
$this->setup[$command]['options'][$long] = ['needsArgument' => $needsArgument, 'help' => $help, 'short' => $short];
|
||||
if ($short) {
|
||||
if (\strlen($short) > 1) {
|
||||
throw InvalidArgument::forMultiCharacterShortOption();
|
||||
}
|
||||
$this->setup[$command]['short'][$short] = $long;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks the actual number of arguments against the required number.
|
||||
*
|
||||
* This is run from CLI automatically and usually does not need to be called directly.
|
||||
*
|
||||
* @throws MissingArgument If not enough arguments were provided.
|
||||
*/
|
||||
public function checkArguments()
|
||||
{
|
||||
$argumentCount = \count($this->arguments);
|
||||
$required = 0;
|
||||
foreach ($this->setup[$this->command]['arguments'] as $argument) {
|
||||
if (!$argument['required']) {
|
||||
break;
|
||||
}
|
||||
// Last required arguments seen.
|
||||
$required++;
|
||||
}
|
||||
if ($required > $argumentCount) {
|
||||
throw MissingArgument::forNotEnoughArguments();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parses the given arguments for known options and command.
|
||||
*
|
||||
* The given $arguments array should NOT contain the executed file as first item anymore! The $arguments
|
||||
* array is stripped from any options and possible command. All found options can be accessed via the
|
||||
* getOptions() function.
|
||||
*
|
||||
* Note that command options will overwrite any global options with the same name.
|
||||
*
|
||||
* This is run from CLI automatically and usually does not need to be called directly.
|
||||
*
|
||||
* @throws InvalidOption If an unknown option was provided.
|
||||
* @throws MissingArgument If an argument is missing.
|
||||
*/
|
||||
public function parseOptions()
|
||||
{
|
||||
$nonOptions = [];
|
||||
$argumentCount = \count($this->arguments);
|
||||
for ($index = 0; $index < $argumentCount; $index++) {
|
||||
$argument = $this->arguments[$index];
|
||||
// The special element '--' means explicit end of options. Treat the rest of the arguments as non-options
|
||||
// and end the loop.
|
||||
if ($argument == '--') {
|
||||
$nonOptions = \array_merge($nonOptions, \array_slice($this->arguments, $index + 1));
|
||||
break;
|
||||
}
|
||||
// '-' is stdin - a normal argument.
|
||||
if ($argument == '-') {
|
||||
$nonOptions = \array_merge($nonOptions, \array_slice($this->arguments, $index));
|
||||
break;
|
||||
}
|
||||
// First non-option.
|
||||
if ($argument[0] != '-') {
|
||||
$nonOptions = \array_merge($nonOptions, \array_slice($this->arguments, $index));
|
||||
break;
|
||||
}
|
||||
// Long option.
|
||||
if (\strlen($argument) > 1 && $argument[1] === '-') {
|
||||
$argument = \explode('=', \substr($argument, 2), 2);
|
||||
$option = \array_shift($argument);
|
||||
$value = \array_shift($argument);
|
||||
if (!isset($this->setup[$this->command]['options'][$option])) {
|
||||
throw InvalidOption::forUnknownOption($option);
|
||||
}
|
||||
// Argument required?
|
||||
if ($this->setup[$this->command]['options'][$option]['needsArgument']) {
|
||||
if (\is_null($value) && $index + 1 < $argumentCount && !\preg_match('/^--?[\\w]/', $this->arguments[$index + 1])) {
|
||||
$value = $this->arguments[++$index];
|
||||
}
|
||||
if (\is_null($value)) {
|
||||
throw MissingArgument::forNoArgument($option);
|
||||
}
|
||||
$this->options[$option] = $value;
|
||||
} else {
|
||||
$this->options[$option] = \true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Short option.
|
||||
$option = \substr($argument, 1);
|
||||
if (!isset($this->setup[$this->command]['short'][$option])) {
|
||||
throw InvalidOption::forUnknownOption($option);
|
||||
} else {
|
||||
$option = $this->setup[$this->command]['short'][$option];
|
||||
// Store it under long name.
|
||||
}
|
||||
// Argument required?
|
||||
if ($this->setup[$this->command]['options'][$option]['needsArgument']) {
|
||||
$value = null;
|
||||
if ($index + 1 < $argumentCount && !\preg_match('/^--?[\\w]/', $this->arguments[$index + 1])) {
|
||||
$value = $this->arguments[++$index];
|
||||
}
|
||||
if (\is_null($value)) {
|
||||
throw MissingArgument::forNoArgument($option);
|
||||
}
|
||||
$this->options[$option] = $value;
|
||||
} else {
|
||||
$this->options[$option] = \true;
|
||||
}
|
||||
}
|
||||
// Parsing is now done, update arguments array.
|
||||
$this->arguments = $nonOptions;
|
||||
// If not done yet, check if first argument is a command and re-execute argument parsing if it is.
|
||||
if (!$this->command && $this->arguments && isset($this->setup[$this->arguments[0]])) {
|
||||
// It is a command!
|
||||
$this->command = \array_shift($this->arguments);
|
||||
$this->parseOptions();
|
||||
// Second pass.
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the value of the given option.
|
||||
*
|
||||
* Please note that all options are accessed by their long option names regardless of how they were
|
||||
* specified on commandline.
|
||||
*
|
||||
* Can only be used after parseOptions() has been run.
|
||||
*
|
||||
* @param string $option Option to get.
|
||||
* @param bool|string $default Optional. Default value to return if the option is not set. Defaults to false.
|
||||
* @return bool|string Value of the option.
|
||||
*/
|
||||
public function getOption($option, $default = \false)
|
||||
{
|
||||
if (isset($this->options[$option])) {
|
||||
return $this->options[$option];
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
/**
|
||||
* Get all options.
|
||||
*
|
||||
* Please note that all options are accessed by their long option names regardless of how they were
|
||||
* specified on commandline.
|
||||
*
|
||||
* Can only be used after parseOptions() has been run.
|
||||
*
|
||||
* @return string[] Associative array of all options.
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
/**
|
||||
* Return the found command, if any.
|
||||
*
|
||||
* @return string Name of the command that was found.
|
||||
*/
|
||||
public function getCommand()
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
/**
|
||||
* Get all the arguments passed to the script.
|
||||
*
|
||||
* This will not contain any recognized options or the script name itself.
|
||||
*
|
||||
* @return array Associative array of arguments.
|
||||
*/
|
||||
public function getArguments()
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
/**
|
||||
* Builds a help screen from the available options.
|
||||
*
|
||||
* You may want to call it from -h or on error.
|
||||
*
|
||||
* @return string Help screen text.
|
||||
*/
|
||||
public function help()
|
||||
{
|
||||
$tableFormatter = new TableFormatter($this->colors);
|
||||
$text = '';
|
||||
$hasCommands = \count($this->setup) > 1;
|
||||
$commandHelp = $this->setup['']['commandHelp'];
|
||||
foreach ($this->setup as $command => $config) {
|
||||
$hasOptions = (bool) $this->setup[$command]['options'];
|
||||
$hasArguments = (bool) $this->setup[$command]['arguments'];
|
||||
// Usage or command syntax line.
|
||||
if (!$command) {
|
||||
$text .= $this->colors->wrap('USAGE:', Colors::C_BROWN);
|
||||
$text .= "\n";
|
||||
$text .= ' ' . $this->bin;
|
||||
$indentation = 2;
|
||||
} else {
|
||||
$text .= $this->newline;
|
||||
$text .= $this->colors->wrap(' ' . $command, Colors::C_PURPLE);
|
||||
$indentation = 4;
|
||||
}
|
||||
if ($hasOptions) {
|
||||
$text .= ' ' . $this->colors->wrap('<OPTIONS>', Colors::C_GREEN);
|
||||
}
|
||||
if (!$command && $hasCommands) {
|
||||
$text .= ' ' . $this->colors->wrap('<COMMAND> ...', Colors::C_PURPLE);
|
||||
}
|
||||
foreach ($this->setup[$command]['arguments'] as $argument) {
|
||||
$output = $this->colors->wrap('<' . $argument['name'] . '>', Colors::C_CYAN);
|
||||
if (!$argument['required']) {
|
||||
$output = '[' . $output . ']';
|
||||
}
|
||||
$text .= ' ' . $output;
|
||||
}
|
||||
$text .= $this->newline;
|
||||
// Usage or command intro.
|
||||
if ($this->setup[$command]['help']) {
|
||||
$text .= "\n";
|
||||
$text .= $tableFormatter->format([$indentation, '*'], ['', $this->setup[$command]['help'] . $this->newline]);
|
||||
}
|
||||
// Option description.
|
||||
if ($hasOptions) {
|
||||
if (!$command) {
|
||||
$text .= "\n";
|
||||
$text .= $this->colors->wrap('OPTIONS:', Colors::C_BROWN);
|
||||
}
|
||||
$text .= "\n";
|
||||
foreach ($this->setup[$command]['options'] as $long => $option) {
|
||||
$name = '';
|
||||
if ($option['short']) {
|
||||
$name .= '-' . $option['short'];
|
||||
if ($option['needsArgument']) {
|
||||
$name .= ' <' . $option['needsArgument'] . '>';
|
||||
}
|
||||
$name .= ', ';
|
||||
}
|
||||
$name .= "--{$long}";
|
||||
if ($option['needsArgument']) {
|
||||
$name .= ' <' . $option['needsArgument'] . '>';
|
||||
}
|
||||
$text .= $tableFormatter->format([$indentation, '30%', '*'], ['', $name, $option['help']], ['', 'green', '']);
|
||||
$text .= $this->newline;
|
||||
}
|
||||
}
|
||||
// Argument description.
|
||||
if ($hasArguments) {
|
||||
if (!$command) {
|
||||
$text .= "\n";
|
||||
$text .= $this->colors->wrap('ARGUMENTS:', Colors::C_BROWN);
|
||||
}
|
||||
$text .= $this->newline;
|
||||
foreach ($this->setup[$command]['arguments'] as $argument) {
|
||||
$name = '<' . $argument['name'] . '>';
|
||||
$text .= $tableFormatter->format([$indentation, '30%', '*'], ['', $name, $argument['help']], ['', 'cyan', '']);
|
||||
}
|
||||
}
|
||||
// Headline and intro for following command documentation.
|
||||
if (!$command && $hasCommands) {
|
||||
$text .= "\n";
|
||||
$text .= $this->colors->wrap('COMMANDS:', Colors::C_BROWN);
|
||||
$text .= "\n";
|
||||
$text .= $tableFormatter->format([$indentation, '*'], ['', $commandHelp]);
|
||||
$text .= $this->newline;
|
||||
}
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
/**
|
||||
* Safely read the $argv PHP array across different PHP configurations.
|
||||
* Will take care of register_globals and register_argc_argv ini directives.
|
||||
*
|
||||
* @return array The $argv PHP array.
|
||||
* @throws InvalidArgument If the $argv array could not be read.
|
||||
*/
|
||||
private function readPHPArgv()
|
||||
{
|
||||
global $argv;
|
||||
if (\is_array($argv)) {
|
||||
return $argv;
|
||||
}
|
||||
if (\is_array($_SERVER) && \array_key_exists('argv', $_SERVER) && \is_array($_SERVER['argv'])) {
|
||||
return $_SERVER['argv'];
|
||||
}
|
||||
if (\array_key_exists('HTTP_SERVER_VARS', $GLOBALS) && \is_array($GLOBALS['HTTP_SERVER_VARS']) && \array_key_exists('argv', $GLOBALS['HTTP_SERVER_VARS']) && \is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
|
||||
return $GLOBALS['HTTP_SERVER_VARS']['argv'];
|
||||
}
|
||||
throw InvalidArgument::forUnreadableArguments();
|
||||
}
|
||||
}
|
497
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Cli/TableFormatter.php
vendored
Normal file
497
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Cli/TableFormatter.php
vendored
Normal file
@@ -0,0 +1,497 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\Cli\InvalidColumnFormat;
|
||||
/**
|
||||
* This file is adapted from the splitbrain\php-cli library, which is authored by Andreas Gohr <andi@splitbrain.org> and
|
||||
* licensed under the MIT license.
|
||||
*
|
||||
* Source: https://github.com/splitbrain/php-cli/blob/8c2c001b1b55d194402cf18aad2757049ac6d575/src/TableFormatter.php
|
||||
*/
|
||||
/**
|
||||
* Output text in multiple columns.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
class TableFormatter
|
||||
{
|
||||
/**
|
||||
* Border between columns.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $border = ' ';
|
||||
/**
|
||||
* Padding around the border.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $padding = 0;
|
||||
/**
|
||||
* The terminal width in characters.
|
||||
*
|
||||
* Falls back to 74 characters if it cannot be detected.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxWidth = 74;
|
||||
/**
|
||||
* Instance of the Colors helper object.
|
||||
*
|
||||
* @var Colors
|
||||
*/
|
||||
protected $colors;
|
||||
/**
|
||||
* Width of each column size based on the content length.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tableColumnWidths = [];
|
||||
/**
|
||||
* Maximum length of the table content.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxColumnWidth = 0;
|
||||
/**
|
||||
* Whether to wrap the table with borders.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isBorderedTable = \false;
|
||||
/**
|
||||
* TableFormatter constructor.
|
||||
*
|
||||
* @param Colors|null $colors Optional. Instance of the Colors helper object.
|
||||
*/
|
||||
public function __construct(Colors $colors = null)
|
||||
{
|
||||
// Try to get terminal width.
|
||||
$width = $this->getTerminalWidth();
|
||||
if ($width) {
|
||||
$this->maxWidth = $width - 1;
|
||||
}
|
||||
$this->colors = $colors instanceof Colors ? $colors : new Colors();
|
||||
}
|
||||
/**
|
||||
* The currently set border.
|
||||
*
|
||||
* Defaults to ' '.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBorder()
|
||||
{
|
||||
return $this->border;
|
||||
}
|
||||
/**
|
||||
* Set the border.
|
||||
*
|
||||
* The border is set between each column. Its width is added to the column widths.
|
||||
*
|
||||
* @param string $border Border to set.
|
||||
*/
|
||||
public function setBorder($border)
|
||||
{
|
||||
$this->border = $border;
|
||||
}
|
||||
/**
|
||||
* Set the padding.
|
||||
*
|
||||
* The padding around the border is added to the column widths.
|
||||
*
|
||||
* @param int $padding Padding to set.
|
||||
*/
|
||||
public function setPadding($padding)
|
||||
{
|
||||
$this->padding = $padding;
|
||||
}
|
||||
/**
|
||||
* Width of the terminal in characters.
|
||||
*
|
||||
* Initially auto-detected, with a fallback of 74 characters.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxWidth()
|
||||
{
|
||||
return $this->maxWidth;
|
||||
}
|
||||
/**
|
||||
* Set the width of the terminal to assume (in characters).
|
||||
*
|
||||
* @param int $maxWidth Terminal width in characters.
|
||||
*/
|
||||
public function setMaxWidth($maxWidth)
|
||||
{
|
||||
$this->maxWidth = $maxWidth;
|
||||
}
|
||||
/**
|
||||
* Displays text in multiple word wrapped columns.
|
||||
*
|
||||
* @param array<int|string> $columns List of column widths (in characters, percent or '*').
|
||||
* @param array<string> $texts List of texts for each column.
|
||||
* @param array<string> $colors Optional. A list of color names to use for each column. Use empty string within
|
||||
* the array for default. Defaults to an empty array.
|
||||
* @return string Adapted text.
|
||||
*/
|
||||
public function format($columns, $texts, $colors = [])
|
||||
{
|
||||
$columns = $this->calculateColumnWidths($columns);
|
||||
$wrapped = [];
|
||||
$maxLength = 0;
|
||||
foreach ($columns as $column => $width) {
|
||||
$wrapped[$column] = \explode("\n", $this->wordwrap($texts[$column], $width, "\n", \true));
|
||||
$length = \count($wrapped[$column]);
|
||||
if ($length > $maxLength) {
|
||||
$maxLength = $length;
|
||||
}
|
||||
}
|
||||
$last = \count($columns) - 1;
|
||||
$output = '';
|
||||
for ($index = 0; $index < $maxLength; $index++) {
|
||||
foreach ($columns as $column => $width) {
|
||||
if ($this->isBorderedTable && $column === 0) {
|
||||
$output .= $this->border . \str_repeat(' ', $this->padding);
|
||||
$width = $width - \strlen($this->border) - $this->padding;
|
||||
}
|
||||
if (isset($wrapped[$column][$index])) {
|
||||
$value = $wrapped[$column][$index];
|
||||
} else {
|
||||
$value = '';
|
||||
}
|
||||
if ($this->isBorderedTable && $column === $last && $width > $this->tableColumnWidths[$last]) {
|
||||
$width = $this->tableColumnWidths[$last];
|
||||
}
|
||||
$chunk = $this->pad($value, $width);
|
||||
if (isset($colors[$column]) && $colors[$column]) {
|
||||
$chunk = $this->colors->wrap($chunk, $colors[$column]);
|
||||
}
|
||||
$output .= $chunk;
|
||||
// Add border in-between columns.
|
||||
if ($column != $last) {
|
||||
$output .= \str_repeat(' ', $this->padding) . $this->border . \str_repeat(' ', $this->padding);
|
||||
}
|
||||
if ($this->isBorderedTable && $column === $last) {
|
||||
$output .= \str_repeat(' ', $this->padding) . $this->border;
|
||||
}
|
||||
}
|
||||
$output .= "\n";
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
/**
|
||||
* Tries to figure out the width of the terminal.
|
||||
*
|
||||
* @return int Terminal width, 0 if unknown.
|
||||
*/
|
||||
protected function getTerminalWidth()
|
||||
{
|
||||
// From environment.
|
||||
if (isset($_SERVER['COLUMNS'])) {
|
||||
return (int) $_SERVER['COLUMNS'];
|
||||
}
|
||||
// Via tput.
|
||||
$process = \proc_open('tput cols', [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
|
||||
$width = (int) \stream_get_contents($pipes[1]);
|
||||
\proc_close($process);
|
||||
return $width;
|
||||
}
|
||||
/**
|
||||
* Takes an array with dynamic column width and calculates the correct widths.
|
||||
*
|
||||
* Column width can be given as fixed char widths, percentages and a single * width can be given
|
||||
* for taking the remaining available space. When mixing percentages and fixed widths, percentages
|
||||
* refer to the remaining space after allocating the fixed width.
|
||||
*
|
||||
* @param array $columns Columns to calculate the widths for.
|
||||
* @return int[] Array of calculated column widths.
|
||||
* @throws InvalidColumnFormat If the column format is not valid.
|
||||
*/
|
||||
protected function calculateColumnWidths($columns)
|
||||
{
|
||||
$index = 0;
|
||||
$border = $this->strlen($this->border);
|
||||
$fixed = (\count($columns) - 1) * $border;
|
||||
// Borders are used already.
|
||||
$fluid = -1;
|
||||
// First pass for format check and fixed columns.
|
||||
foreach ($columns as $index => $column) {
|
||||
// Handle fixed columns.
|
||||
if ((string) \intval($column) === (string) $column) {
|
||||
$fixed += $column;
|
||||
continue;
|
||||
}
|
||||
// Check if other columns are using proper units.
|
||||
if (\substr($column, -1) === '%') {
|
||||
continue;
|
||||
}
|
||||
if ($column === '*') {
|
||||
// Only one fluid.
|
||||
if ($fluid < 0) {
|
||||
$fluid = $index;
|
||||
continue;
|
||||
} else {
|
||||
throw InvalidColumnFormat::forMultipleFluidColumns();
|
||||
}
|
||||
}
|
||||
throw InvalidColumnFormat::forUnknownColumnFormat($column);
|
||||
}
|
||||
$allocated = $fixed;
|
||||
$remain = $this->maxWidth - $allocated;
|
||||
// Second pass to handle percentages.
|
||||
foreach ($columns as $index => $column) {
|
||||
if (\substr($column, -1) !== '%') {
|
||||
continue;
|
||||
}
|
||||
$percent = \floatval($column);
|
||||
$real = (int) \floor($percent * $remain / 100);
|
||||
$columns[$index] = $real;
|
||||
$allocated += $real;
|
||||
}
|
||||
$remain = $this->maxWidth - $allocated;
|
||||
if ($remain < 0) {
|
||||
throw InvalidColumnFormat::forExceededMaxWidth();
|
||||
}
|
||||
// Assign remaining space.
|
||||
if ($fluid < 0) {
|
||||
$columns[$index] += $remain;
|
||||
// Add to last column.
|
||||
} else {
|
||||
$columns[$fluid] = $remain;
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
/**
|
||||
* Pad the given string to the correct length.
|
||||
*
|
||||
* @param string $string String to pad.
|
||||
* @param int $length Length to pad the string to.
|
||||
* @return string Padded string.
|
||||
*/
|
||||
protected function pad($string, $length)
|
||||
{
|
||||
$strlen = $this->strlen($string);
|
||||
if ($strlen > $length) {
|
||||
return $string;
|
||||
}
|
||||
$pad = $length - $strlen;
|
||||
return $string . \str_pad('', $pad, ' ');
|
||||
}
|
||||
/**
|
||||
* Measures character length in UTF-8 when possible.
|
||||
*
|
||||
* @param string $string String to measure the character length of.
|
||||
* @return int Count of characters.
|
||||
*/
|
||||
protected function strlen($string)
|
||||
{
|
||||
// Don't count color codes.
|
||||
$string = \preg_replace("/\x1b\\[\\d+(;\\d+)?m/", '', $string);
|
||||
if (\function_exists('mb_strlen')) {
|
||||
return \mb_strlen($string, 'utf-8');
|
||||
}
|
||||
return \strlen($string);
|
||||
}
|
||||
/**
|
||||
* Extract a substring in UTF-8 if possible.
|
||||
* @param string $string String to extract a substring out of.
|
||||
* @param int $start Optional. Starting index to extract from. Defaults to 0.
|
||||
* @param int|null $length Optional. Length to extract. Set to null to use the remainder of the string (default).
|
||||
* @return string Extracted substring.
|
||||
*/
|
||||
protected function substr($string, $start = 0, $length = null)
|
||||
{
|
||||
if (\function_exists('mb_substr')) {
|
||||
return \mb_substr($string, $start, $length);
|
||||
}
|
||||
// mb_substr() treats $length differently than substr().
|
||||
if ($length) {
|
||||
return \substr($string, $start, $length);
|
||||
}
|
||||
return \substr($string, $start);
|
||||
}
|
||||
/**
|
||||
* Wrap words of a string into a requested width.
|
||||
*
|
||||
* @param string $string String to wrap.
|
||||
* @param int $width Optional. Width to warp the string into. Defaults to 75.
|
||||
* @param string $break Optional. Character to use for wrapping. Defaults to a newline character. Defaults to the
|
||||
* newline character.
|
||||
* @param bool $cut Optional. Whether to cut longer words to enforce the width. Defaults to false.
|
||||
* @return string Word-wrapped string.
|
||||
* @link http://stackoverflow.com/a/4988494
|
||||
*/
|
||||
protected function wordwrap($string, $width = 75, $break = "\n", $cut = \false)
|
||||
{
|
||||
if (!\is_int($width) || $width < 0) {
|
||||
$width = 75;
|
||||
}
|
||||
if (!\is_string($break) || empty($break)) {
|
||||
$break = "\n";
|
||||
}
|
||||
$lines = \explode($break, $string);
|
||||
foreach ($lines as &$line) {
|
||||
$line = \rtrim($line);
|
||||
if ($this->strlen($line) <= $width) {
|
||||
continue;
|
||||
}
|
||||
$words = \explode(' ', $line);
|
||||
$line = '';
|
||||
$actual = '';
|
||||
foreach ($words as $word) {
|
||||
if ($this->strlen($actual . $word) <= $width) {
|
||||
$actual .= $word . ' ';
|
||||
} else {
|
||||
if ($actual != '') {
|
||||
$line .= \rtrim($actual) . $break;
|
||||
}
|
||||
$actual = $word;
|
||||
if ($cut) {
|
||||
while ($this->strlen($actual) > $width) {
|
||||
$line .= $this->substr($actual, 0, $width) . $break;
|
||||
$actual = $this->substr($actual, $width);
|
||||
}
|
||||
}
|
||||
$actual .= ' ';
|
||||
}
|
||||
}
|
||||
$line .= \trim($actual);
|
||||
}
|
||||
return \implode($break, $lines);
|
||||
}
|
||||
/**
|
||||
* Format the rows in a bordered table.
|
||||
*
|
||||
* @param array<array<string>> $rows List of texts for each column.
|
||||
* @param array<string> $headers Optional. List of texts used in the table header.
|
||||
*
|
||||
* @return string A borered table containing the given rows.
|
||||
*/
|
||||
public function formatTable($rows, $headers = [])
|
||||
{
|
||||
$this->setBorder('|');
|
||||
$this->setPadding(1);
|
||||
$this->setIsBorderedTable(\true);
|
||||
if (!empty($headers)) {
|
||||
$this->calculateTableColumnWidths($headers);
|
||||
}
|
||||
foreach ($rows as $row) {
|
||||
$this->calculateTableColumnWidths($row);
|
||||
}
|
||||
$numberOfColumns = \count($this->tableColumnWidths);
|
||||
$columns = \array_map(function ($width, $index) {
|
||||
// Add extra padding to the first and last columns.
|
||||
if ($index === 0 || $index === \count($this->tableColumnWidths) - 1) {
|
||||
$width = $width + \strlen($this->border) + $this->padding;
|
||||
}
|
||||
return $width;
|
||||
}, $this->tableColumnWidths, \array_keys($this->tableColumnWidths));
|
||||
// For a three column table, we'll have have "| " at start and " |" at the end,
|
||||
// and in-between two " | ". So in total "| " + " | " + " | " + " |" = 10 chars.
|
||||
$borderCharWidth = \strlen($this->border);
|
||||
$totalBorderWidth = 2 * ($borderCharWidth + $this->padding) + ($numberOfColumns - 1) * ($borderCharWidth + $this->padding * 2);
|
||||
$estimatedColumnWidth = $numberOfColumns * $this->maxColumnWidth;
|
||||
$estimatedTotalWidth = $totalBorderWidth + $estimatedColumnWidth;
|
||||
if ($estimatedTotalWidth > $this->maxWidth) {
|
||||
$maxWidthWithoutBorders = $this->maxWidth - $totalBorderWidth;
|
||||
$avrg = \floor($maxWidthWithoutBorders / $numberOfColumns);
|
||||
$resizedWidths = [];
|
||||
$extraWidth = 0;
|
||||
foreach ($this->tableColumnWidths as $width) {
|
||||
if ($width > $avrg) {
|
||||
$resizedWidths[] = $width;
|
||||
} else {
|
||||
$extraWidth = $extraWidth + ($avrg - $width);
|
||||
}
|
||||
}
|
||||
if (!empty($resizedWidths) && $extraWidth) {
|
||||
$avrgExtraWidth = \floor($extraWidth / \count($resizedWidths));
|
||||
foreach ($this->tableColumnWidths as $i => &$width) {
|
||||
if (\in_array($width, $resizedWidths, \true)) {
|
||||
$width = $avrg + $avrgExtraWidth;
|
||||
\array_shift($resizedWidths);
|
||||
if (empty($resizedWidths)) {
|
||||
$width = 0;
|
||||
// Zero it so not in sum.
|
||||
$width = $maxWidthWithoutBorders - \array_sum($this->tableColumnWidths);
|
||||
}
|
||||
}
|
||||
if ($i === 0 || $i === $numberOfColumns - 1) {
|
||||
$width = $width + \strlen($this->border) + $this->padding;
|
||||
}
|
||||
$columns[$i] = \intval($width);
|
||||
}
|
||||
}
|
||||
}
|
||||
$horizontalBorder = $this->getTableHorizontalBorder($rows[0], $columns);
|
||||
$table = $horizontalBorder . "\n";
|
||||
if (!empty($headers)) {
|
||||
$table .= $this->getTableRow($headers, $columns);
|
||||
$table .= $horizontalBorder . "\n";
|
||||
}
|
||||
foreach ($rows as $row) {
|
||||
$table .= $this->getTableRow($row, $columns);
|
||||
}
|
||||
$table .= $horizontalBorder;
|
||||
return $table;
|
||||
}
|
||||
/**
|
||||
* Whether the table is wrapped with borders or not.
|
||||
*
|
||||
* @param bool $isBorderedTable Whether the table is wrapped with borders or not.
|
||||
*/
|
||||
public function setIsBorderedTable($isBorderedTable)
|
||||
{
|
||||
$this->isBorderedTable = $isBorderedTable;
|
||||
}
|
||||
/**
|
||||
* Calculate table column widths based on the column content length.
|
||||
*
|
||||
* @param array<string> $row List of texts for each column.
|
||||
*/
|
||||
protected function calculateTableColumnWidths($row)
|
||||
{
|
||||
foreach ($row as $i => $rowContent) {
|
||||
$width = \strlen($rowContent);
|
||||
if ($width > $this->maxColumnWidth) {
|
||||
$this->maxColumnWidth = $width;
|
||||
}
|
||||
if (!isset($this->tableColumnWidths[$i]) || $width > $this->tableColumnWidths[$i]) {
|
||||
$this->tableColumnWidths[$i] = $width;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the table row.
|
||||
*
|
||||
* @param array<string> $row List of texts for each column.
|
||||
* @param array<int> $columns List of maximum column widths.
|
||||
*
|
||||
* @return string Table row.
|
||||
*/
|
||||
protected function getTableRow($row, $columns)
|
||||
{
|
||||
return \trim($this->format($columns, $row)) . "\n";
|
||||
}
|
||||
/**
|
||||
* Get the table horizontal border.
|
||||
*
|
||||
* @param array<string> $row List of texts for each column.
|
||||
* @param array<int> $columns List of maximum column widths.
|
||||
*
|
||||
* @return string Table border.
|
||||
*/
|
||||
protected function getTableHorizontalBorder($row, $columns)
|
||||
{
|
||||
$tableRow = $this->getTableRow($row, $columns);
|
||||
$tableRow = \explode("\n", $tableRow);
|
||||
$firstRow = \array_shift($tableRow);
|
||||
$firstRow = \trim($firstRow);
|
||||
$borderChar = \preg_quote($this->border, '/');
|
||||
$border = \preg_replace("/[^{$borderChar}]/", '-', $firstRow);
|
||||
$border = \preg_replace("/[{$borderChar}]/", '+', $border);
|
||||
return $border;
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject;
|
||||
|
||||
/**
|
||||
* Compatibility fix that can be registered.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface CompatibilityFix
|
||||
{
|
||||
/**
|
||||
* Register the compatibility fix.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register();
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\CompatibilityFix;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\CompatibilityFix;
|
||||
/**
|
||||
* Backwards compatibility fix for classes that were moved.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class MovedClasses implements CompatibilityFix
|
||||
{
|
||||
/**
|
||||
* Mapping of aliases to be registered.
|
||||
*
|
||||
* @var array<string, string> Associative array of class alias mappings.
|
||||
*/
|
||||
const ALIASES = [
|
||||
// v0.9.0 - moved HTML-based utility into a separate `Html` sub-namespace.
|
||||
'Google\\Web_Stories_Dependencies\\AmpProject\\AtRule' => 'Google\\Web_Stories_Dependencies\\AmpProject\\Html\\AtRule',
|
||||
'Google\\Web_Stories_Dependencies\\AmpProject\\Attribute' => 'Google\\Web_Stories_Dependencies\\AmpProject\\Html\\Attribute',
|
||||
'Google\\Web_Stories_Dependencies\\AmpProject\\LengthUnit' => 'Google\\Web_Stories_Dependencies\\AmpProject\\Html\\LengthUnit',
|
||||
'Google\\Web_Stories_Dependencies\\AmpProject\\RequestDestination' => 'Google\\Web_Stories_Dependencies\\AmpProject\\Html\\RequestDestination',
|
||||
'Google\\Web_Stories_Dependencies\\AmpProject\\Role' => 'Google\\Web_Stories_Dependencies\\AmpProject\\Html\\Role',
|
||||
'Google\\Web_Stories_Dependencies\\AmpProject\\Tag' => 'Google\\Web_Stories_Dependencies\\AmpProject\\Html\\Tag',
|
||||
// v0.9.0 - extracted `Encoding` out of `Dom\Document`, as it is turned into AMP value object.
|
||||
'Google\\Web_Stories_Dependencies\\AmpProject\\Dom\\Document\\Encoding' => 'Google\\Web_Stories_Dependencies\\AmpProject\\Encoding',
|
||||
];
|
||||
/**
|
||||
* Register the compatibility fix.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
\spl_autoload_register(__CLASS__ . '::autoloader');
|
||||
}
|
||||
/**
|
||||
* Autoloader to register.
|
||||
*
|
||||
* @param string $oldClassName Old class name that was requested to be autoloaded.
|
||||
* @return void
|
||||
*/
|
||||
public static function autoloader($oldClassName)
|
||||
{
|
||||
if (!\array_key_exists($oldClassName, self::ALIASES)) {
|
||||
return;
|
||||
}
|
||||
\class_alias(self::ALIASES[$oldClassName], $oldClassName, \true);
|
||||
}
|
||||
}
|
159
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/CssLength.php
vendored
Normal file
159
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/CssLength.php
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject;
|
||||
|
||||
/**
|
||||
* Flexible unit of measure for CSS dimensions.
|
||||
*
|
||||
* Adapted from the `amp.validator.CssLength` class found in `validator.js` from the `ampproject/amphtml` project on
|
||||
* GitHub.
|
||||
*
|
||||
* @version 1911070201440
|
||||
* @link https://github.com/ampproject/amphtml/blob/1911070201440/validator/engine/validator.js#L3351
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class CssLength
|
||||
{
|
||||
// Special attribute values.
|
||||
const AUTO = 'auto';
|
||||
const FLUID = 'fluid';
|
||||
/**
|
||||
* Whether the value or unit is invalid. Note that passing an empty value as `$attr_value` is considered valid.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isValid = \false;
|
||||
/**
|
||||
* Whether the attribute value is set.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isDefined = \false;
|
||||
/**
|
||||
* Whether the attribute value is 'auto'. This is a special value that indicates that the value gets derived from
|
||||
* the context. In practice that's only ever the case for a width.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isAuto = \false;
|
||||
/**
|
||||
* Whether the attribute value is 'fluid'.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isFluid = \false;
|
||||
/**
|
||||
* The numeric value.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
protected $numeral = 0;
|
||||
/**
|
||||
* The unit, 'px' being the default in case it's absent.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $unit = 'px';
|
||||
/**
|
||||
* Value of attribute.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $attrValue;
|
||||
/**
|
||||
* Instantiate a CssLength object.
|
||||
*
|
||||
* @param string|null $attrValue Attribute value to be parsed.
|
||||
*/
|
||||
public function __construct($attrValue)
|
||||
{
|
||||
if (null === $attrValue) {
|
||||
$this->isValid = \true;
|
||||
return;
|
||||
}
|
||||
$this->attrValue = $attrValue;
|
||||
$this->isDefined = \true;
|
||||
}
|
||||
/**
|
||||
* Validate the attribute value.
|
||||
*
|
||||
* @param bool $allowAuto Whether or not to allow the 'auto' value as a value.
|
||||
* @param bool $allowFluid Whether or not to allow the 'fluid' value as a value.
|
||||
*/
|
||||
public function validate($allowAuto, $allowFluid)
|
||||
{
|
||||
if ($this->isValid()) {
|
||||
return;
|
||||
}
|
||||
if (self::AUTO === $this->attrValue) {
|
||||
$this->isAuto = \true;
|
||||
$this->isValid = $allowAuto;
|
||||
return;
|
||||
}
|
||||
if (self::FLUID === $this->attrValue) {
|
||||
$this->isFluid = \true;
|
||||
$this->isValid = $allowFluid;
|
||||
}
|
||||
$pattern = '/^(?<numeral>\\d+(?:\\.\\d+)?)(?<unit>px|em|rem|vh|vw|vmin|vmax)?$/';
|
||||
if (\preg_match($pattern, $this->attrValue, $match)) {
|
||||
$this->isValid = \true;
|
||||
$this->numeral = isset($match['numeral']) ? (float) $match['numeral'] : $this->numeral;
|
||||
$this->unit = isset($match['unit']) ? $match['unit'] : $this->unit;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Whether or not the attribute value is valid.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
return $this->isValid;
|
||||
}
|
||||
/**
|
||||
* Whether the attribute value is set.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDefined()
|
||||
{
|
||||
return $this->isDefined;
|
||||
}
|
||||
/**
|
||||
* Whether the attribute value is 'fluid'.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFluid()
|
||||
{
|
||||
return $this->isFluid;
|
||||
}
|
||||
/**
|
||||
* Whether the attribute value is 'auto'.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAuto()
|
||||
{
|
||||
return $this->isAuto;
|
||||
}
|
||||
/**
|
||||
* The unit of the attribute.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUnit()
|
||||
{
|
||||
return $this->unit;
|
||||
}
|
||||
/**
|
||||
* The numeral of the attribute.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getNumeral()
|
||||
{
|
||||
return $this->numeral;
|
||||
}
|
||||
}
|
75
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/DevMode.php
vendored
Normal file
75
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/DevMode.php
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Element;
|
||||
use DOMNode;
|
||||
/**
|
||||
* Helper functionality to deal with AMP dev-mode.
|
||||
*
|
||||
* @link https://github.com/ampproject/amphtml/issues/20974
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class DevMode
|
||||
{
|
||||
/**
|
||||
* Attribute name for AMP dev mode.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DEV_MODE_ATTRIBUTE = 'data-ampdevmode';
|
||||
/**
|
||||
* Check whether the provided document is in dev mode.
|
||||
*
|
||||
* @param Document $document Document for which to check whether dev mode is active.
|
||||
* @return bool Whether the document is in dev mode.
|
||||
*/
|
||||
public static function isActiveForDocument(Document $document)
|
||||
{
|
||||
return $document->documentElement->hasAttribute(self::DEV_MODE_ATTRIBUTE);
|
||||
}
|
||||
/**
|
||||
* Check whether a node is exempt from validation during dev mode.
|
||||
*
|
||||
* @param DOMNode $node Node to check.
|
||||
* @return bool Whether the node should be exempt during dev mode.
|
||||
*/
|
||||
public static function hasExemptionForNode(DOMNode $node)
|
||||
{
|
||||
if (!$node instanceof Element) {
|
||||
return \false;
|
||||
}
|
||||
$document = self::getDocument($node);
|
||||
if ($node === $document->documentElement) {
|
||||
return $document->hasInitialAmpDevMode();
|
||||
}
|
||||
return $node->hasAttribute(self::DEV_MODE_ATTRIBUTE);
|
||||
}
|
||||
/**
|
||||
* Check whether a certain node should be exempt from validation.
|
||||
*
|
||||
* @param DOMNode $node Node to check.
|
||||
* @return bool Whether the node should be exempt from validation.
|
||||
*/
|
||||
public static function isExemptFromValidation(DOMNode $node)
|
||||
{
|
||||
$document = self::getDocument($node);
|
||||
return self::isActiveForDocument($document) && self::hasExemptionForNode($node);
|
||||
}
|
||||
/**
|
||||
* Get the document from the specified node.
|
||||
*
|
||||
* @param DOMNode $node The Node from which the document should be retrieved.
|
||||
* @return Document
|
||||
*/
|
||||
private static function getDocument(DOMNode $node)
|
||||
{
|
||||
$document = $node->ownerDocument;
|
||||
if (!$document instanceof Document) {
|
||||
$document = Document::fromNode($node);
|
||||
}
|
||||
return $document;
|
||||
}
|
||||
}
|
966
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Dom/Document.php
vendored
Normal file
966
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Dom/Document.php
vendored
Normal file
@@ -0,0 +1,966 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\DevMode;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterSaveFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeSaveFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Option;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Encoding;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\FailedToRetrieveRequiredDomElement;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\InvalidDocumentFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\MaxCssByteCountExceeded;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Attribute;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Tag;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\CssRule;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Validator\Spec\CssRuleset\AmpNoTransformed;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Validator\Spec\SpecRule;
|
||||
use DOMComment;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
use DOMNodeList;
|
||||
use DOMText;
|
||||
use DOMXPath;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionNamedType;
|
||||
/**
|
||||
* Abstract away some of the difficulties of working with PHP's DOMDocument.
|
||||
*
|
||||
* @property DOMXPath $xpath XPath query object for this document.
|
||||
* @property Element $html The document's <html> element.
|
||||
* @property Element $head The document's <head> element.
|
||||
* @property Element $body The document's <body> element.
|
||||
* @property Element|null $charset The document's charset meta element.
|
||||
* @property Element|null $viewport The document's viewport meta element.
|
||||
* @property DOMNodeList $ampElements The document's <amp-*> elements.
|
||||
* @property Element $ampCustomStyle The document's <style amp-custom> element.
|
||||
* @property int $ampCustomStyleByteCount Count of bytes of CSS in the <style amp-custom> tag.
|
||||
* @property int $inlineStyleByteCount Count of bytes of CSS in all of the inline style attributes.
|
||||
* @property LinkManager $links Link manager to manage <link> tags in the <head>.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class Document extends DOMDocument
|
||||
{
|
||||
/**
|
||||
* Default document type to use.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DEFAULT_DOCTYPE = '<!DOCTYPE html>';
|
||||
/**
|
||||
* Regular expression to match the HTML doctype.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_DOCTYPE_REGEX_PATTERN = '#<!doctype\\s+html[^>]+?>#si';
|
||||
/*
|
||||
* Regular expressions to fetch the individual structural tags.
|
||||
* These patterns were optimized to avoid extreme backtracking on large documents.
|
||||
*/
|
||||
const HTML_STRUCTURE_DOCTYPE_PATTERN = '/^(?<doctype>[^<]*(?>\\s*<!--.*?-->\\s*)*<!doctype(?>\\s+[^>]+)?>)/is';
|
||||
const HTML_STRUCTURE_HTML_START_TAG = '/^(?<html_start>[^<]*(?>\\s*<!--.*?-->\\s*)*<html(?>\\s+[^>]*)?>)/is';
|
||||
const HTML_STRUCTURE_HTML_END_TAG = '/(?<html_end><\\/html(?>\\s+[^>]*)?>.*)$/is';
|
||||
const HTML_STRUCTURE_HEAD_START_TAG = '/^[^<]*(?><!--.*?-->\\s*)*(?><head(?>\\s+[^>]*)?>)/is';
|
||||
const HTML_STRUCTURE_BODY_START_TAG = '/^[^<]*(?><!--.*-->\\s*)*(?><body(?>\\s+[^>]*)?>)/is';
|
||||
const HTML_STRUCTURE_BODY_END_TAG = '/(?><\\/body(?>\\s+[^>]*)?>.*)$/is';
|
||||
const HTML_STRUCTURE_HEAD_TAG = '/^(?>[^<]*(?><head(?>\\s+[^>]*)?>).*?<\\/head(?>\\s+[^>]*)?>)/is';
|
||||
// Regex pattern used for removing Internet Explorer conditional comments.
|
||||
const HTML_IE_CONDITIONAL_COMMENTS_PATTERN = '/<!--(?>\\[if\\s|<!\\[endif)(?>[^>]+(?<!--)>)*(?>[^>]+(?<=--)>)/i';
|
||||
/**
|
||||
* Error message to use when the __get() is triggered for an unknown property.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PROPERTY_GETTER_ERROR_MESSAGE = 'Undefined property: AmpProject\\Dom\\Document::';
|
||||
// Attribute to use as a placeholder to move the emoji AMP symbol (⚡) over to DOM.
|
||||
const EMOJI_AMP_ATTRIBUTE_PLACEHOLDER = 'emoji-amp';
|
||||
/**
|
||||
* XPath query to retrieve all <amp-*> tags, relative to the <body> node.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const XPATH_AMP_ELEMENTS_QUERY = ".//*[starts-with(name(), 'amp-')]";
|
||||
/**
|
||||
* XPath query to retrieve the <style amp-custom> tag, relative to the <head> node.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const XPATH_AMP_CUSTOM_STYLE_QUERY = './/style[@amp-custom]';
|
||||
/**
|
||||
* XPath query to fetch the inline style attributes, relative to the <body> node.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const XPATH_INLINE_STYLE_ATTRIBUTES_QUERY = './/@style';
|
||||
/**
|
||||
* Associative array for lazily-created, cached properties for the document.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $properties = [];
|
||||
/**
|
||||
* Associative array of options to configure the behavior of the DOM document abstraction.
|
||||
*
|
||||
* @see Option::DEFAULTS For a list of available options.
|
||||
*
|
||||
* @var Options
|
||||
*/
|
||||
private $options;
|
||||
/**
|
||||
* Whether `data-ampdevmode` was initially set on the the document element.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $hasInitialAmpDevMode = \false;
|
||||
/**
|
||||
* The original encoding of how the Dom\Document was created.
|
||||
*
|
||||
* This is stored to do an automatic conversion to UTF-8, which is a requirement for AMP.
|
||||
*
|
||||
* @var Encoding
|
||||
*/
|
||||
private $originalEncoding;
|
||||
/**
|
||||
* The maximum number of bytes of CSS that is enforced.
|
||||
*
|
||||
* A negative number will disable the byte count limit.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $cssMaxByteCountEnforced = -1;
|
||||
/**
|
||||
* List of document filter class names.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $filterClasses = [];
|
||||
/**
|
||||
* List of document filter class instances.
|
||||
*
|
||||
* @var Filter[]
|
||||
*/
|
||||
private $filters = [];
|
||||
/**
|
||||
* Unique ID manager for the Document instance.
|
||||
*
|
||||
* @var UniqueIdManager
|
||||
*/
|
||||
private $uniqueIdManager;
|
||||
/**
|
||||
* Creates a new AmpProject\Dom\Document object
|
||||
*
|
||||
* @link https://php.net/manual/domdocument.construct.php
|
||||
*
|
||||
* @param string $version Optional. The version number of the document as part of the XML declaration.
|
||||
* @param string $encoding Optional. The encoding of the document as part of the XML declaration.
|
||||
*/
|
||||
public function __construct($version = '', $encoding = null)
|
||||
{
|
||||
$this->originalEncoding = new Encoding($encoding);
|
||||
parent::__construct($version ?: '1.0', Encoding::AMP);
|
||||
$this->registerNodeClass(DOMElement::class, Element::class);
|
||||
$this->options = new Options(Option::DEFAULTS);
|
||||
$this->uniqueIdManager = new UniqueIdManager();
|
||||
$this->registerFilters([Filter\DetectInvalidByteSequence::class, Filter\SvgSourceAttributeEncoding::class, Filter\AmpEmojiAttribute::class, Filter\AmpBindAttributes::class, Filter\SelfClosingTags::class, Filter\SelfClosingSVGElements::class, Filter\NoscriptElements::class, Filter\DeduplicateTag::class, Filter\ConvertHeadProfileToLink::class, Filter\MustacheScriptTemplates::class, Filter\DoctypeNode::class, Filter\NormalizeHtmlAttributes::class, Filter\DocumentEncoding::class, Filter\HttpEquivCharset::class, Filter\LibxmlCompatibility::class, Filter\ProtectEsiTags::class, Filter\NormalizeHtmlEntities::class]);
|
||||
}
|
||||
/**
|
||||
* Named constructor to provide convenient way of transforming HTML into DOM.
|
||||
*
|
||||
* Due to slow automatic encoding detection, it is recommended to provide an explicit
|
||||
* charset either via a <meta charset> tag or via $options.
|
||||
*
|
||||
* @param string $html HTML to turn into a DOM.
|
||||
* @param array|string $options Optional. Array of options to configure the document. Used as encoding if a string
|
||||
* is passed. Defaults to an empty array.
|
||||
* @return Document|false DOM generated from provided HTML, or false if the transformation failed.
|
||||
*/
|
||||
public static function fromHtml($html, $options = [])
|
||||
{
|
||||
// Assume options are the encoding if a string is passed, for BC reasons.
|
||||
if (\is_string($options)) {
|
||||
$options = [Option::ENCODING => $options];
|
||||
}
|
||||
$encoding = isset($options[Option::ENCODING]) ? $options[Option::ENCODING] : null;
|
||||
$dom = new self('', $encoding);
|
||||
if (!$dom->loadHTML($html, $options)) {
|
||||
return \false;
|
||||
}
|
||||
return $dom;
|
||||
}
|
||||
/**
|
||||
* Named constructor to provide convenient way of transforming a HTML fragment into DOM.
|
||||
*
|
||||
* The difference to Document::fromHtml() is that fragments are not normalized as to their structure.
|
||||
*
|
||||
* Due to slow automatic encoding detection, it is recommended to pass in an explicit
|
||||
* charset via $options.
|
||||
*
|
||||
* @param string $html HTML to turn into a DOM.
|
||||
* @param array|string $options Optional. Array of options to configure the document. Used as encoding if a string
|
||||
* is passed. Defaults to an empty array.
|
||||
* @return Document|false DOM generated from provided HTML, or false if the transformation failed.
|
||||
*/
|
||||
public static function fromHtmlFragment($html, $options = [])
|
||||
{
|
||||
// Assume options are the encoding if a string is passed, for BC reasons.
|
||||
if (\is_string($options)) {
|
||||
$options = [Option::ENCODING => $options];
|
||||
}
|
||||
$encoding = isset($options[Option::ENCODING]) ? $options[Option::ENCODING] : null;
|
||||
$dom = new self('', $encoding);
|
||||
if (!$dom->loadHTMLFragment($html, $options)) {
|
||||
return \false;
|
||||
}
|
||||
return $dom;
|
||||
}
|
||||
/**
|
||||
* Named constructor to provide convenient way of retrieving the DOM from a node.
|
||||
*
|
||||
* @param DOMNode $node Node to retrieve the DOM from. This is being modified by reference (!).
|
||||
* @return Document DOM generated from provided HTML, or false if the transformation failed.
|
||||
*/
|
||||
public static function fromNode(DOMNode &$node)
|
||||
{
|
||||
/**
|
||||
* Document of the node.
|
||||
*
|
||||
* If the node->ownerDocument returns null, the node is the document.
|
||||
*
|
||||
* @var DOMDocument
|
||||
*/
|
||||
$root = $node->ownerDocument === null ? $node : $node->ownerDocument;
|
||||
if ($root instanceof self) {
|
||||
return $root;
|
||||
}
|
||||
$dom = new self();
|
||||
// We replace the $node by reference, to make sure the next lines of code will
|
||||
// work as expected with the new document.
|
||||
// Otherwise $dom and $node would refer to two different DOMDocuments.
|
||||
$node = $dom->importNode($node, \true);
|
||||
$dom->appendChild($node);
|
||||
$dom->hasInitialAmpDevMode = $dom->documentElement->hasAttribute(DevMode::DEV_MODE_ATTRIBUTE);
|
||||
return $dom;
|
||||
}
|
||||
/**
|
||||
* Reset the internal optimizations of the Document object.
|
||||
*
|
||||
* This might be needed if you are doing an operation that causes the cached
|
||||
* nodes and XPath objects to point to the wrong document.
|
||||
*
|
||||
* @return self Reset version of the Document object.
|
||||
*/
|
||||
private function reset()
|
||||
{
|
||||
// Drop references to old DOM document.
|
||||
unset($this->properties['xpath'], $this->properties['head'], $this->properties['body']);
|
||||
// Reference of the document itself doesn't change here, but might need to change in the future.
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Load HTML from a string.
|
||||
*
|
||||
* @link https://php.net/manual/domdocument.loadhtml.php
|
||||
*
|
||||
* @param string $source The HTML string.
|
||||
* @param array|int|string $options Optional. Array of options to configure the document. Used as additional Libxml
|
||||
* parameters if an int or string is passed. Defaults to an empty array.
|
||||
* @return bool true on success or false on failure.
|
||||
*/
|
||||
public function loadHTML($source, $options = [])
|
||||
{
|
||||
$source = $this->normalizeDocumentStructure($source);
|
||||
$success = $this->loadHTMLFragment($source, $options);
|
||||
if ($success) {
|
||||
$this->insertMissingCharset();
|
||||
// Do some further clean-up.
|
||||
$this->moveInvalidHeadNodesToBody();
|
||||
$this->movePostBodyNodesToBody();
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
/**
|
||||
* Load a HTML fragment from a string.
|
||||
*
|
||||
* @param string $source The HTML fragment string.
|
||||
* @param array|int|string $options Optional. Array of options to configure the document. Used as additional Libxml
|
||||
* parameters if an int or string is passed. Defaults to an empty array.
|
||||
* @return bool true on success or false on failure.
|
||||
*/
|
||||
public function loadHTMLFragment($source, $options = [])
|
||||
{
|
||||
// Assume options are the additional libxml flags if a string or int is passed, for BC reasons.
|
||||
if (\is_string($options)) {
|
||||
$options = (int) $options;
|
||||
}
|
||||
if (\is_int($options)) {
|
||||
$options = [Option::LIBXML_FLAGS => $options];
|
||||
}
|
||||
$this->options = $this->options->merge($options);
|
||||
$this->reset();
|
||||
foreach ($this->filterClasses as $filterClass) {
|
||||
$filter = null;
|
||||
try {
|
||||
$filter = $this->instantiateFilter($filterClass);
|
||||
$this->filters[] = $filter;
|
||||
} catch (ReflectionException $exception) {
|
||||
// A filter cannot properly be instantiated. Let's just skip loading it for now.
|
||||
continue;
|
||||
}
|
||||
if (!$filter instanceof Filter) {
|
||||
throw InvalidDocumentFilter::forFilter($filter);
|
||||
}
|
||||
if ($filter instanceof BeforeLoadFilter) {
|
||||
$source = $filter->beforeLoad($source);
|
||||
}
|
||||
}
|
||||
$success = parent::loadHTML($source, $this->options[Option::LIBXML_FLAGS]);
|
||||
if ($success) {
|
||||
foreach ($this->filters as $filter) {
|
||||
if ($filter instanceof AfterLoadFilter) {
|
||||
$filter->afterLoad($this);
|
||||
}
|
||||
}
|
||||
$this->hasInitialAmpDevMode = $this->documentElement->hasAttribute(DevMode::DEV_MODE_ATTRIBUTE);
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
/**
|
||||
* Dumps the internal document into a string using HTML formatting.
|
||||
*
|
||||
* @link https://php.net/manual/domdocument.savehtml.php
|
||||
*
|
||||
* @param DOMNode|null $node Optional. Parameter to output a subset of the document.
|
||||
* @return string The HTML, or false if an error occurred.
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function saveHTML(DOMNode $node = null)
|
||||
{
|
||||
return $this->saveHTMLFragment($node);
|
||||
}
|
||||
/**
|
||||
* Dumps the internal document fragment into a string using HTML formatting.
|
||||
*
|
||||
* @param DOMNode|null $node Optional. Parameter to output a subset of the document.
|
||||
* @return string The HTML fragment, or false if an error occurred.
|
||||
*/
|
||||
public function saveHTMLFragment(DOMNode $node = null)
|
||||
{
|
||||
$filtersInReverse = \array_reverse($this->filters);
|
||||
foreach ($filtersInReverse as $filter) {
|
||||
if ($filter instanceof BeforeSaveFilter) {
|
||||
$filter->beforeSave($this);
|
||||
}
|
||||
}
|
||||
if (null === $node || \PHP_VERSION_ID >= 70300) {
|
||||
$html = parent::saveHTML($node);
|
||||
} else {
|
||||
$html = $this->extractNodeViaFragmentBoundaries($node);
|
||||
}
|
||||
foreach ($filtersInReverse as $filter) {
|
||||
if ($filter instanceof AfterSaveFilter) {
|
||||
$html = $filter->afterSave($html);
|
||||
}
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
/**
|
||||
* Get the current options of the Document instance.
|
||||
*
|
||||
* @return Options
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
/**
|
||||
* Add the required utf-8 meta charset tag if it is still missing.
|
||||
*/
|
||||
private function insertMissingCharset()
|
||||
{
|
||||
// Bail if a charset tag is already present.
|
||||
if ($this->xpath->query('.//meta[ @charset ]')->item(0)) {
|
||||
return;
|
||||
}
|
||||
$charset = $this->createElement(Tag::META);
|
||||
$charset->setAttribute(Attribute::CHARSET, Encoding::AMP);
|
||||
$this->head->insertBefore($charset, $this->head->firstChild);
|
||||
}
|
||||
/**
|
||||
* Extract a node's HTML via fragment boundaries.
|
||||
*
|
||||
* Temporarily adds fragment boundary comments in order to locate the desired node to extract from
|
||||
* the given HTML document. This is required because libxml seems to only preserve whitespace when
|
||||
* serializing when calling DOMDocument::saveHTML() on the entire document. If you pass the element
|
||||
* to DOMDocument::saveHTML() then formatting whitespace gets added unexpectedly. This is seen to
|
||||
* be fixed in PHP 7.3, but for older versions of PHP the following workaround is needed.
|
||||
*
|
||||
* @param DOMNode $node Node to extract the HTML for.
|
||||
* @return string Extracted HTML string.
|
||||
*/
|
||||
private function extractNodeViaFragmentBoundaries(DOMNode $node)
|
||||
{
|
||||
$boundary = $this->uniqueIdManager->getUniqueId('fragment_boundary');
|
||||
$startBoundary = $boundary . ':start';
|
||||
$endBoundary = $boundary . ':end';
|
||||
$commentStart = $this->createComment($startBoundary);
|
||||
$commentEnd = $this->createComment($endBoundary);
|
||||
$node->parentNode->insertBefore($commentStart, $node);
|
||||
$node->parentNode->insertBefore($commentEnd, $node->nextSibling);
|
||||
$pattern = '/^.*?' . \preg_quote("<!--{$startBoundary}-->", '/') . '(.*)' . \preg_quote("<!--{$endBoundary}-->", '/') . '.*?\\s*$/s';
|
||||
$html = \preg_replace($pattern, '$1', parent::saveHTML());
|
||||
$node->parentNode->removeChild($commentStart);
|
||||
$node->parentNode->removeChild($commentEnd);
|
||||
return $html;
|
||||
}
|
||||
/**
|
||||
* Normalize the document structure.
|
||||
*
|
||||
* This makes sure the document adheres to the general structure that AMP requires:
|
||||
* ```
|
||||
* <!DOCTYPE html>
|
||||
* <html>
|
||||
* <head>
|
||||
* <meta charset="utf-8">
|
||||
* </head>
|
||||
* <body>
|
||||
* </body>
|
||||
* </html>
|
||||
* ```
|
||||
*
|
||||
* @param string $content Content to normalize the structure of.
|
||||
* @return string Normalized content.
|
||||
*/
|
||||
private function normalizeDocumentStructure($content)
|
||||
{
|
||||
$matches = [];
|
||||
$doctype = self::DEFAULT_DOCTYPE;
|
||||
$htmlStart = '<html>';
|
||||
$htmlEnd = '</html>';
|
||||
// Strip IE conditional comments, which are supported by IE 5-9 only (which AMP doesn't support).
|
||||
$content = \preg_replace(self::HTML_IE_CONDITIONAL_COMMENTS_PATTERN, '', $content);
|
||||
// Detect and strip <!doctype> tags.
|
||||
if (\preg_match(self::HTML_STRUCTURE_DOCTYPE_PATTERN, $content, $matches)) {
|
||||
$doctype = $matches['doctype'];
|
||||
$content = \preg_replace(self::HTML_STRUCTURE_DOCTYPE_PATTERN, '', $content, 1);
|
||||
}
|
||||
// Detect and strip <html> tags.
|
||||
if (\preg_match(self::HTML_STRUCTURE_HTML_START_TAG, $content, $matches)) {
|
||||
$htmlStart = $matches['html_start'];
|
||||
$content = \preg_replace(self::HTML_STRUCTURE_HTML_START_TAG, '', $content, 1);
|
||||
\preg_match(self::HTML_STRUCTURE_HTML_END_TAG, $content, $matches);
|
||||
$htmlEnd = isset($matches['html_end']) ? $matches['html_end'] : $htmlEnd;
|
||||
$content = \preg_replace(self::HTML_STRUCTURE_HTML_END_TAG, '', $content, 1);
|
||||
}
|
||||
// Detect <head> and <body> tags and add as needed.
|
||||
if (!\preg_match(self::HTML_STRUCTURE_HEAD_START_TAG, $content, $matches)) {
|
||||
if (!\preg_match(self::HTML_STRUCTURE_BODY_START_TAG, $content, $matches)) {
|
||||
// Both <head> and <body> missing.
|
||||
$content = "<head></head><body>{$content}</body>";
|
||||
} else {
|
||||
// Only <head> missing.
|
||||
$content = "<head></head>{$content}";
|
||||
}
|
||||
} elseif (!\preg_match(self::HTML_STRUCTURE_BODY_END_TAG, $content, $matches)) {
|
||||
// Only <body> missing.
|
||||
// @todo This is an expensive regex operation, look into further optimization.
|
||||
$content = \preg_replace(self::HTML_STRUCTURE_HEAD_TAG, '$0<body>', $content, 1, $count);
|
||||
// Closing </head> tag is missing.
|
||||
if (!$count) {
|
||||
$content = $content . '</head><body>';
|
||||
}
|
||||
$content .= '</body>';
|
||||
}
|
||||
$content = "{$htmlStart}{$content}{$htmlEnd}";
|
||||
// Reinsert a standard doctype (while preserving any potentially leading comments).
|
||||
$doctype = \preg_replace(self::HTML_DOCTYPE_REGEX_PATTERN, self::DEFAULT_DOCTYPE, $doctype);
|
||||
$content = "{$doctype}{$content}";
|
||||
return $content;
|
||||
}
|
||||
/**
|
||||
* Normalize the structure of the document if it was already provided as a DOM.
|
||||
*
|
||||
* Warning: This method may not use any magic getters for html, head, or body.
|
||||
*/
|
||||
public function normalizeDomStructure()
|
||||
{
|
||||
if (!$this->documentElement) {
|
||||
$this->appendChild($this->createElement(Tag::HTML));
|
||||
}
|
||||
if (Tag::HTML !== $this->documentElement->nodeName) {
|
||||
$nextSibling = $this->documentElement->nextSibling;
|
||||
/**
|
||||
* The old document element that we need to remove and replace as we cannot just move it around.
|
||||
*
|
||||
* @var Element
|
||||
*/
|
||||
$oldDocumentElement = $this->removeChild($this->documentElement);
|
||||
$html = $this->createElement(Tag::HTML);
|
||||
$this->insertBefore($html, $nextSibling);
|
||||
if ($oldDocumentElement->nodeName === Tag::HEAD) {
|
||||
$head = $oldDocumentElement;
|
||||
} else {
|
||||
$head = $this->getElementsByTagName(Tag::HEAD)->item(0);
|
||||
if (!$head) {
|
||||
$head = $this->createElement(Tag::HEAD);
|
||||
}
|
||||
}
|
||||
if (!$head instanceof Element) {
|
||||
throw FailedToRetrieveRequiredDomElement::forHeadElement($head);
|
||||
}
|
||||
$this->properties['head'] = $head;
|
||||
$html->appendChild($head);
|
||||
if ($oldDocumentElement->nodeName === Tag::BODY) {
|
||||
$body = $oldDocumentElement;
|
||||
} else {
|
||||
$body = $this->getElementsByTagName(Tag::BODY)->item(0);
|
||||
if (!$body) {
|
||||
$body = $this->createElement(Tag::BODY);
|
||||
}
|
||||
}
|
||||
if (!$body instanceof Element) {
|
||||
throw FailedToRetrieveRequiredDomElement::forBodyElement($body);
|
||||
}
|
||||
$this->properties['body'] = $body;
|
||||
$html->appendChild($body);
|
||||
if ($oldDocumentElement !== $body && $oldDocumentElement !== $this->head) {
|
||||
$body->appendChild($oldDocumentElement);
|
||||
}
|
||||
} else {
|
||||
$head = $this->getElementsByTagName(Tag::HEAD)->item(0);
|
||||
if (!$head) {
|
||||
$this->properties['head'] = $this->createElement(Tag::HEAD);
|
||||
$this->documentElement->insertBefore($this->properties['head'], $this->documentElement->firstChild);
|
||||
}
|
||||
$body = $this->getElementsByTagName(Tag::BODY)->item(0);
|
||||
if (!$body) {
|
||||
$this->properties['body'] = $this->createElement(Tag::BODY);
|
||||
$this->documentElement->appendChild($this->properties['body']);
|
||||
}
|
||||
}
|
||||
$this->moveInvalidHeadNodesToBody();
|
||||
$this->movePostBodyNodesToBody();
|
||||
}
|
||||
/**
|
||||
* Move invalid head nodes back to the body.
|
||||
*
|
||||
* Warning: This method may not use any magic getters for html, head, or body.
|
||||
*/
|
||||
private function moveInvalidHeadNodesToBody()
|
||||
{
|
||||
// Walking backwards makes it easier to move elements in the expected order.
|
||||
$node = $this->properties['head']->lastChild;
|
||||
while ($node) {
|
||||
$nextSibling = $node->previousSibling;
|
||||
if (!$this->isValidHeadNode($node)) {
|
||||
$this->properties['body']->insertBefore($this->properties['head']->removeChild($node), $this->properties['body']->firstChild);
|
||||
}
|
||||
$node = $nextSibling;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Move any nodes appearing after </body> or </html> to be appended to the <body>.
|
||||
*
|
||||
* This accounts for markup that is output at shutdown, such markup from Query Monitor. Not only is elements after
|
||||
* the </body> not valid in AMP, but trailing elements after </html> will get wrapped in additional <html> elements.
|
||||
* While comment nodes would be allowed in AMP, everything is moved regardless so that source stack comments will
|
||||
* retain their relative position with the element nodes they annotate.
|
||||
*
|
||||
* Warning: This method may not use any magic getters for html, head, or body.
|
||||
*/
|
||||
private function movePostBodyNodesToBody()
|
||||
{
|
||||
// Move nodes (likely comments) from after the </body>.
|
||||
while ($this->properties['body']->nextSibling) {
|
||||
$this->properties['body']->appendChild($this->properties['body']->nextSibling);
|
||||
}
|
||||
// Move nodes from after the </html>.
|
||||
while ($this->documentElement->nextSibling) {
|
||||
$nextSibling = $this->documentElement->nextSibling;
|
||||
if ($nextSibling instanceof Element && Tag::HTML === $nextSibling->nodeName) {
|
||||
// Handle trailing elements getting wrapped in implicit duplicate <html>.
|
||||
while ($nextSibling->firstChild) {
|
||||
$this->properties['body']->appendChild($nextSibling->firstChild);
|
||||
}
|
||||
$nextSibling->parentNode->removeChild($nextSibling);
|
||||
// Discard now-empty implicit <html>.
|
||||
} else {
|
||||
$this->properties['body']->appendChild($this->documentElement->nextSibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Determine whether a node can be in the head.
|
||||
*
|
||||
* Warning: This method may not use any magic getters for html, head, or body.
|
||||
*
|
||||
* @link https://github.com/ampproject/amphtml/blob/445d6e3be8a5063e2738c6f90fdcd57f2b6208be/validator/engine/htmlparser.js#L83-L100
|
||||
* @link https://www.w3.org/TR/html5/document-metadata.html
|
||||
*
|
||||
* @param DOMNode $node Node.
|
||||
* @return bool Whether valid head node.
|
||||
*/
|
||||
public function isValidHeadNode(DOMNode $node)
|
||||
{
|
||||
return $node instanceof Element && \in_array($node->nodeName, Tag::ELEMENTS_ALLOWED_IN_HEAD, \true) || $node instanceof DOMText && \preg_match('/^\\s*$/', $node->nodeValue) || $node instanceof DOMComment;
|
||||
}
|
||||
/**
|
||||
* Get the ID for an element.
|
||||
*
|
||||
* If the element does not have an ID, create one first.
|
||||
*
|
||||
* @param Element $element Element to get the ID for.
|
||||
* @param string $prefix Optional. The prefix to use (should not have a trailing dash). Defaults to 'i-amp-id'.
|
||||
* @return string ID to use.
|
||||
*/
|
||||
public function getElementId(Element $element, $prefix = 'i-amp')
|
||||
{
|
||||
if ($element->hasAttribute(Attribute::ID)) {
|
||||
return $element->getAttribute(Attribute::ID);
|
||||
}
|
||||
$id = $this->uniqueIdManager->getUniqueId($prefix);
|
||||
while ($this->getElementById($id) instanceof Element) {
|
||||
$id = $this->uniqueIdManager->getUniqueId($prefix);
|
||||
}
|
||||
$element->setAttribute(Attribute::ID, $id);
|
||||
return $id;
|
||||
}
|
||||
/**
|
||||
* Determine whether `data-ampdevmode` was initially set on the document element.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasInitialAmpDevMode()
|
||||
{
|
||||
return $this->hasInitialAmpDevMode;
|
||||
}
|
||||
/**
|
||||
* Add style(s) to the <style amp-custom> tag.
|
||||
*
|
||||
* @param string $style Style to add.
|
||||
* @throws MaxCssByteCountExceeded If the allowed max byte count is exceeded.
|
||||
*/
|
||||
public function addAmpCustomStyle($style)
|
||||
{
|
||||
$style = \trim($style, CssRule::CSS_TRIM_CHARACTERS);
|
||||
$existingStyle = (string) $this->ampCustomStyle->textContent;
|
||||
// Inject new styles before any potential source map annotation comment like: /*# sourceURL=amp-custom.css */.
|
||||
// If not present, then just put it at the end of the stylesheet. This isn't strictly required, but putting the
|
||||
// source map comments at the end is the convention.
|
||||
$newStyle = \preg_replace(':(?=\\s+/\\*#[^*]+?\\*/\\s*$|$):s', $style, $existingStyle, 1);
|
||||
$newByteCount = \strlen($newStyle);
|
||||
if ($this->getRemainingCustomCssSpace() < $newByteCount - $this->ampCustomStyleByteCount) {
|
||||
throw MaxCssByteCountExceeded::forAmpCustom($newStyle);
|
||||
}
|
||||
$this->ampCustomStyle->textContent = $newStyle;
|
||||
$this->properties['ampCustomStyleByteCount'] = $newByteCount;
|
||||
}
|
||||
/**
|
||||
* Add the given number of bytes ot the total inline style byte count.
|
||||
*
|
||||
* @param int $byteCount Bytes to add.
|
||||
*/
|
||||
public function addInlineStyleByteCount($byteCount)
|
||||
{
|
||||
$this->inlineStyleByteCount += $byteCount;
|
||||
}
|
||||
/**
|
||||
* Get the remaining number bytes allowed for custom CSS.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRemainingCustomCssSpace()
|
||||
{
|
||||
if ($this->cssMaxByteCountEnforced < 0) {
|
||||
// No CSS byte count limit is being enforced, so return the next best thing to +∞.
|
||||
return \PHP_INT_MAX;
|
||||
}
|
||||
return \max(0, $this->cssMaxByteCountEnforced - (int) $this->ampCustomStyleByteCount - (int) $this->inlineStyleByteCount);
|
||||
}
|
||||
/**
|
||||
* Get the array of allowed keys of lazily-created, cached properties.
|
||||
* The array index is the key and the array value is the key's default value.
|
||||
*
|
||||
* @return array Array of allowed keys.
|
||||
*/
|
||||
protected function getAllowedKeys()
|
||||
{
|
||||
return ['xpath', Tag::HTML, Tag::HEAD, Tag::BODY, Attribute::CHARSET, Attribute::VIEWPORT, 'ampElements', 'ampCustomStyle', 'ampCustomStyleByteCount', 'inlineStyleByteCount', 'links'];
|
||||
}
|
||||
/**
|
||||
* Magic getter to implement lazily-created, cached properties for the document.
|
||||
*
|
||||
* @param string $name Name of the property to get.
|
||||
* @return mixed Value of the property, or null if unknown property was requested.
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'xpath':
|
||||
$this->properties['xpath'] = new DOMXPath($this);
|
||||
return $this->properties['xpath'];
|
||||
case Tag::HTML:
|
||||
$html = $this->getElementsByTagName(Tag::HTML)->item(0);
|
||||
if ($html === null) {
|
||||
// Document was assembled manually and bypassed normalisation.
|
||||
$this->normalizeDomStructure();
|
||||
$html = $this->getElementsByTagName(Tag::HTML)->item(0);
|
||||
}
|
||||
if (!$html instanceof Element) {
|
||||
throw FailedToRetrieveRequiredDomElement::forHtmlElement($html);
|
||||
}
|
||||
$this->properties['html'] = $html;
|
||||
return $this->properties['html'];
|
||||
case Tag::HEAD:
|
||||
$head = $this->getElementsByTagName(Tag::HEAD)->item(0);
|
||||
if ($head === null) {
|
||||
// Document was assembled manually and bypassed normalisation.
|
||||
$this->normalizeDomStructure();
|
||||
$head = $this->getElementsByTagName(Tag::HEAD)->item(0);
|
||||
}
|
||||
if (!$head instanceof Element) {
|
||||
throw FailedToRetrieveRequiredDomElement::forHeadElement($head);
|
||||
}
|
||||
$this->properties['head'] = $head;
|
||||
return $this->properties['head'];
|
||||
case Tag::BODY:
|
||||
$body = $this->getElementsByTagName(Tag::BODY)->item(0);
|
||||
if ($body === null) {
|
||||
// Document was assembled manually and bypassed normalisation.
|
||||
$this->normalizeDomStructure();
|
||||
$body = $this->getElementsByTagName(Tag::BODY)->item(0);
|
||||
}
|
||||
if (!$body instanceof Element) {
|
||||
throw FailedToRetrieveRequiredDomElement::forBodyElement($body);
|
||||
}
|
||||
$this->properties['body'] = $body;
|
||||
return $this->properties['body'];
|
||||
case Attribute::CHARSET:
|
||||
// This is not cached as it could potentially be requested too early, before the viewport was added, and
|
||||
// the cache would then store null without rechecking later on after the viewport has been added.
|
||||
for ($node = $this->head->firstChild; $node !== null; $node = $node->nextSibling) {
|
||||
if ($node instanceof Element && $node->tagName === Tag::META && $node->getAttribute(Attribute::NAME) === Attribute::CHARSET) {
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case Attribute::VIEWPORT:
|
||||
// This is not cached as it could potentially be requested too early, before the viewport was added, and
|
||||
// the cache would then store null without rechecking later on after the viewport has been added.
|
||||
for ($node = $this->head->firstChild; $node !== null; $node = $node->nextSibling) {
|
||||
if ($node instanceof Element && $node->tagName === Tag::META && $node->getAttribute(Attribute::NAME) === Attribute::VIEWPORT) {
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case 'ampElements':
|
||||
// This is not cached as we clone some elements during SSR transformations to avoid ending up with
|
||||
// partially transformed, broken elements.
|
||||
return $this->xpath->query(self::XPATH_AMP_ELEMENTS_QUERY, $this->body) ?: new DOMNodeList();
|
||||
case 'ampCustomStyle':
|
||||
$ampCustomStyle = $this->xpath->query(self::XPATH_AMP_CUSTOM_STYLE_QUERY, $this->head)->item(0);
|
||||
if (!$ampCustomStyle instanceof Element) {
|
||||
$ampCustomStyle = $this->createElement(Tag::STYLE);
|
||||
$ampCustomStyle->appendChild($this->createAttribute(Attribute::AMP_CUSTOM));
|
||||
$this->head->appendChild($ampCustomStyle);
|
||||
}
|
||||
$this->properties['ampCustomStyle'] = $ampCustomStyle;
|
||||
return $this->properties['ampCustomStyle'];
|
||||
case 'ampCustomStyleByteCount':
|
||||
if (!isset($this->properties['ampCustomStyle'])) {
|
||||
$ampCustomStyle = $this->xpath->query(self::XPATH_AMP_CUSTOM_STYLE_QUERY, $this->head)->item(0);
|
||||
if (!$ampCustomStyle instanceof Element) {
|
||||
return 0;
|
||||
}
|
||||
$this->properties['ampCustomStyle'] = $ampCustomStyle;
|
||||
}
|
||||
if (!isset($this->properties['ampCustomStyleByteCount'])) {
|
||||
$this->properties['ampCustomStyleByteCount'] = \strlen($this->properties['ampCustomStyle']->textContent);
|
||||
}
|
||||
return $this->properties['ampCustomStyleByteCount'];
|
||||
case 'inlineStyleByteCount':
|
||||
if (!isset($this->properties['inlineStyleByteCount'])) {
|
||||
$this->properties['inlineStyleByteCount'] = 0;
|
||||
$attributes = $this->xpath->query(self::XPATH_INLINE_STYLE_ATTRIBUTES_QUERY, $this->documentElement);
|
||||
foreach ($attributes as $attribute) {
|
||||
$this->properties['inlineStyleByteCount'] += \strlen($attribute->textContent);
|
||||
}
|
||||
}
|
||||
return $this->properties['inlineStyleByteCount'];
|
||||
case 'links':
|
||||
if (!isset($this->properties['links'])) {
|
||||
$this->properties['links'] = new LinkManager($this);
|
||||
}
|
||||
return $this->properties['links'];
|
||||
}
|
||||
// Mimic regular PHP behavior for missing notices.
|
||||
\trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, \E_USER_NOTICE);
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Magic setter to implement lazily-created, cached properties for the document.
|
||||
*
|
||||
* @param string $name Name of the property to set.
|
||||
* @param mixed $value Value of the property.
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
if (!\in_array($name, $this->getAllowedKeys(), \true)) {
|
||||
// Mimic regular PHP behavior for missing notices.
|
||||
\trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, \E_USER_NOTICE);
|
||||
return;
|
||||
}
|
||||
$this->properties[$name] = $value;
|
||||
}
|
||||
/**
|
||||
* Magic callback for lazily-created, cached properties for the document.
|
||||
*
|
||||
* @param string $name Name of the property to set.
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
if (!\in_array($name, $this->getAllowedKeys(), \true)) {
|
||||
// Mimic regular PHP behavior for missing notices.
|
||||
\trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, \E_USER_NOTICE);
|
||||
return \false;
|
||||
}
|
||||
return isset($this->properties[$name]);
|
||||
}
|
||||
/**
|
||||
* Make sure we properly reinitialize on clone.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->reset();
|
||||
}
|
||||
/**
|
||||
* Create new element node.
|
||||
*
|
||||
* @link https://php.net/manual/domdocument.createelement.php
|
||||
*
|
||||
* This override only serves to provide the correct object type-hint for our extended Dom/Element class.
|
||||
*
|
||||
* @param string $name The tag name of the element.
|
||||
* @param string $value Optional. The value of the element. By default, an empty element will be created.
|
||||
* You can also set the value later with Element->nodeValue.
|
||||
* @return Element|false A new instance of class Element or false if an error occurred.
|
||||
*/
|
||||
public function createElement($name, $value = '')
|
||||
{
|
||||
$element = parent::createElement($name, $value);
|
||||
if (!$element instanceof Element) {
|
||||
return \false;
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
/**
|
||||
* Create new element node.
|
||||
*
|
||||
* @link https://php.net/manual/domdocument.createelement.php
|
||||
*
|
||||
* This override only serves to provide the correct object type-hint for our extended Dom/Element class.
|
||||
*
|
||||
* @param string $name The tag name of the element.
|
||||
* @param array $attributes Attributes to add to the newly created element.
|
||||
* @param string $value Optional. The value of the element. By default, an empty element will be created.
|
||||
* You can also set the value later with Element->nodeValue.
|
||||
* @return Element|false A new instance of class Element or false if an error occurred.
|
||||
*/
|
||||
public function createElementWithAttributes($name, $attributes, $value = '')
|
||||
{
|
||||
$element = parent::createElement($name, $value);
|
||||
if (!$element instanceof Element) {
|
||||
return \false;
|
||||
}
|
||||
$element->setAttributes($attributes);
|
||||
return $element;
|
||||
}
|
||||
/**
|
||||
* Check whether the CSS maximum byte count is enforced.
|
||||
*
|
||||
* @return bool Whether the CSS maximum byte count is enforced.
|
||||
*/
|
||||
public function isCssMaxByteCountEnforced()
|
||||
{
|
||||
return $this->cssMaxByteCountEnforced >= 0;
|
||||
}
|
||||
/**
|
||||
* Enforce a maximum number of bytes for the CSS.
|
||||
*
|
||||
* @param int|null $maxByteCount Maximum number of bytes to limit the CSS to. A negative number disables the limit.
|
||||
* If null then the max bytes from AmpNoTransformed is used.
|
||||
*/
|
||||
public function enforceCssMaxByteCount($maxByteCount = null)
|
||||
{
|
||||
if ($maxByteCount === null) {
|
||||
// No need to instantiate the spec here, we can just directly reference the needed constant.
|
||||
$maxByteCount = AmpNoTransformed::SPEC[SpecRule::MAX_BYTES];
|
||||
}
|
||||
$this->cssMaxByteCountEnforced = $maxByteCount;
|
||||
}
|
||||
/**
|
||||
* Register filters to pre- or post-process the document content.
|
||||
*
|
||||
* @param string[] $filterClasses Array of FQCNs of document filter classes.
|
||||
*/
|
||||
public function registerFilters($filterClasses)
|
||||
{
|
||||
foreach ($filterClasses as $filterClass) {
|
||||
$this->filterClasses[] = $filterClass;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Instantiate a filter from its class while providing the needed dependencies.
|
||||
*
|
||||
* @param string $filterClass Class of the filter to instantiate.
|
||||
* @return Filter Filter object instance.
|
||||
* @throws ReflectionException If the constructor could not be reflected upon.
|
||||
*/
|
||||
private function instantiateFilter($filterClass)
|
||||
{
|
||||
$constructor = (new ReflectionClass($filterClass))->getConstructor();
|
||||
$parameters = $constructor === null ? [] : $constructor->getParameters();
|
||||
$dependencies = [];
|
||||
foreach ($parameters as $parameter) {
|
||||
$dependencyType = null;
|
||||
// The use of `ReflectionParameter::getClass()` is deprecated in PHP 8, and is superseded
|
||||
// by `ReflectionParameter::getType()`. See https://github.com/php/php-src/pull/5209.
|
||||
if (\PHP_VERSION_ID >= 70100) {
|
||||
if ($parameter->getType()) {
|
||||
/** @var ReflectionNamedType $returnType */
|
||||
$returnType = $parameter->getType();
|
||||
$dependencyType = new ReflectionClass($returnType->getName());
|
||||
}
|
||||
} else {
|
||||
$dependencyType = $parameter->getClass();
|
||||
}
|
||||
if ($dependencyType === null) {
|
||||
// No type provided, so we pass `null` in the hopes that the argument is optional.
|
||||
$dependencies[] = null;
|
||||
continue;
|
||||
}
|
||||
if (\is_a($dependencyType->name, Encoding::class, \true)) {
|
||||
$dependencies[] = $this->originalEncoding;
|
||||
continue;
|
||||
}
|
||||
if (\is_a($dependencyType->name, Options::class, \true)) {
|
||||
$dependencies[] = $this->options;
|
||||
continue;
|
||||
}
|
||||
if (\is_a($dependencyType->name, UniqueIdManager::class, \true)) {
|
||||
$dependencies[] = $this->uniqueIdManager;
|
||||
continue;
|
||||
}
|
||||
// Unknown dependency type, so we pass `null` in the hopes that the argument is optional.
|
||||
$dependencies[] = null;
|
||||
}
|
||||
return new $filterClass(...$dependencies);
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
/**
|
||||
* Filter the Dom\Document after it was loaded.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface AfterLoadFilter extends Filter
|
||||
{
|
||||
/**
|
||||
* Process the Document after the html loaded into the Dom\Document.
|
||||
*
|
||||
* @param Document $document Document to be processed.
|
||||
*/
|
||||
public function afterLoad(Document $document);
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
|
||||
/**
|
||||
* Filter the HTML after it is saved from the Dom\Document.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface AfterSaveFilter extends Filter
|
||||
{
|
||||
/**
|
||||
* Process the Dom\Document after being saved from Dom\Document.
|
||||
*
|
||||
* @param string $html String of HTML markup to be preprocessed.
|
||||
* @return string Preprocessed string of HTML markup.
|
||||
*/
|
||||
public function afterSave($html);
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
|
||||
/**
|
||||
* Filter the HTML before it is loaded into the Dom\Document.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface BeforeLoadFilter extends Filter
|
||||
{
|
||||
/**
|
||||
* Preprocess the HTML to be loaded into the Dom\Document.
|
||||
*
|
||||
* @param string $html String of HTML markup to be preprocessed.
|
||||
* @return string Preprocessed string of HTML markup.
|
||||
*/
|
||||
public function beforeLoad($html);
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
/**
|
||||
* Filter the Dom\Document before it is saved.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface BeforeSaveFilter extends Filter
|
||||
{
|
||||
/**
|
||||
* Preprocess the DOM to be saved into HTML.
|
||||
*
|
||||
* @param Document $document Document to be preprocessed before saving it into HTML.
|
||||
*/
|
||||
public function beforeSave(Document $document);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
|
||||
/**
|
||||
* Filter to process the document.
|
||||
*
|
||||
* This is only a marker interface and needs to be extended by the specific filter types that defines methods.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface Filter
|
||||
{
|
||||
}
|
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Amp;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterSaveFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Option;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Options;
|
||||
/**
|
||||
* Amp bind attributes filter.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class AmpBindAttributes implements BeforeLoadFilter, AfterSaveFilter
|
||||
{
|
||||
/**
|
||||
* Pattern for HTML attribute accounting for binding attr name in data attribute syntax, boolean attribute,
|
||||
* single/double-quoted attribute value, and unquoted attribute values.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP_BIND_DATA_ATTRIBUTE_ATTR_PATTERN = '#^\\s+(?P<name>(?:' . Amp::BIND_DATA_ATTR_PREFIX . ')?[a-zA-Z0-9_\\-]+)' . '(?P<value>=(?>"[^"]*+"|\'[^\']*+\'|[^\'"\\s]+))?#';
|
||||
/**
|
||||
* Match all start tags that contain a binding attribute in data attribute syntax.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP_BIND_DATA_START_PATTERN = '#<' . '(?P<name>[a-zA-Z0-9_\\-]+)' . '(?P<attrs>\\s+' . '(?>' . '(?>' . '(?![a-zA-Z0-9_\\-\\s]*' . Amp::BIND_DATA_ATTR_PREFIX . '[a-zA-Z0-9_\\-]+="[^"]*+"|\'[^\']*+\')' . '[^>"\']+|"[^"]*+"|\'[^\']*+\'' . ')*+' . '(?>[a-zA-Z0-9_\\-\\s]*' . Amp::BIND_DATA_ATTR_PREFIX . '[a-zA-Z0-9_\\-]+' . ')' . ')+' . '(?>[^>"\']+|"[^"]*+"|\'[^\']*+\')*+' . ')>#is';
|
||||
/**
|
||||
* Pattern for HTML attribute accounting for binding attr name in square brackets syntax, boolean attribute,
|
||||
* single/double-quoted attribute value, and unquoted attribute values.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP_BIND_SQUARE_BRACKETS_ATTR_PATTERN = '#^\\s+(?P<name>\\[?[a-zA-Z0-9_\\-]+\\]?)' . '(?P<value>=(?>"[^"]*+"|\'[^\']*+\'|[^\'"\\s]+))?#';
|
||||
/**
|
||||
* Match all start tags that contain a binding attribute in square brackets syntax.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP_BIND_SQUARE_START_PATTERN = '#<' . '(?P<name>[a-zA-Z0-9_\\-]+)' . '(?P<attrs>\\s+' . '(?>[^>"\'\\[\\]]+|"[^"]*+"|\'[^\']*+\')*+' . '\\[[a-zA-Z0-9_\\-]+\\]' . '(?>[^>"\']+|"[^"]*+"|\'[^\']*+\')*+' . ')>#s';
|
||||
/**
|
||||
* Options instance to use.
|
||||
*
|
||||
* @var Options
|
||||
*/
|
||||
private $options;
|
||||
/**
|
||||
* Store the names of the amp-bind attributes that were converted so that we can restore them later on.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
private $convertedAmpBindAttributes = [];
|
||||
/**
|
||||
* AmpBindAttributes constructor.
|
||||
*
|
||||
* @param Options $options Options instance to use.
|
||||
*/
|
||||
public function __construct(Options $options)
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
/**
|
||||
* Replace AMP binding attributes with something that libxml can parse (as HTML5 data-* attributes).
|
||||
*
|
||||
* This is necessary because attributes in square brackets are not understood in PHP and
|
||||
* get dropped with an error raised:
|
||||
* > Warning: DOMDocument::loadHTML(): error parsing attribute name
|
||||
*
|
||||
* @link https://www.ampproject.org/docs/reference/components/amp-bind
|
||||
*
|
||||
* @param string $html HTML containing amp-bind attributes.
|
||||
* @return string HTML with AMP binding attributes replaced with HTML5 data-* attributes.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
/**
|
||||
* Replace callback.
|
||||
*
|
||||
* @param array $tagMatches Tag matches.
|
||||
* @return string Replacement.
|
||||
*/
|
||||
$replaceCallback = function ($tagMatches) {
|
||||
$oldAttrs = $this->maybeStripSelfClosingSlash($tagMatches['attrs']);
|
||||
$newAttrs = '';
|
||||
$offset = 0;
|
||||
while (\preg_match(self::AMP_BIND_SQUARE_BRACKETS_ATTR_PATTERN, \substr($oldAttrs, $offset), $attrMatches)) {
|
||||
$offset += \strlen($attrMatches[0]);
|
||||
if ('[' === $attrMatches['name'][0]) {
|
||||
$attrName = \trim($attrMatches['name'], '[]');
|
||||
$newAttrs .= ' ' . Amp::BIND_DATA_ATTR_PREFIX . $attrName;
|
||||
if (isset($attrMatches['value'])) {
|
||||
$newAttrs .= $attrMatches['value'];
|
||||
}
|
||||
$this->convertedAmpBindAttributes[] = $attrName;
|
||||
} else {
|
||||
$newAttrs .= $attrMatches[0];
|
||||
}
|
||||
}
|
||||
// Bail on parse error which occurs when the regex isn't able to consume the entire $newAttrs string.
|
||||
if (\strlen($oldAttrs) !== $offset) {
|
||||
return $tagMatches[0];
|
||||
}
|
||||
return '<' . $tagMatches['name'] . $newAttrs . '>';
|
||||
};
|
||||
$result = \preg_replace_callback(self::AMP_BIND_SQUARE_START_PATTERN, $replaceCallback, $html);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* Convert AMP bind-attributes back to their original syntax.
|
||||
*
|
||||
* This is not guaranteed to produce the exact same result as the initial markup, as it is more of a best guess.
|
||||
* It can end up replacing the wrong attributes if the initial markup had inconsistent styling, mixing both syntaxes
|
||||
* for the same attribute. In either case, it will always produce working markup, so this is not that big of a deal.
|
||||
*
|
||||
* @see convertAmpBindAttributes() Reciprocal function.
|
||||
* @link https://www.ampproject.org/docs/reference/components/amp-bind
|
||||
*
|
||||
* @param string $html HTML with amp-bind attributes converted.
|
||||
* @return string HTML with amp-bind attributes restored.
|
||||
*/
|
||||
public function afterSave($html)
|
||||
{
|
||||
if ($this->options[Option::AMP_BIND_SYNTAX] === Option::AMP_BIND_SYNTAX_DATA_ATTRIBUTE) {
|
||||
// All amp-bind attributes should remain in their converted data attribute form.
|
||||
return $html;
|
||||
}
|
||||
if ($this->options[Option::AMP_BIND_SYNTAX] === Option::AMP_BIND_SYNTAX_AUTO && empty($this->convertedAmpBindAttributes)) {
|
||||
// Only previously converted amp-bind attributes should be restored, but none were converted.
|
||||
return $html;
|
||||
}
|
||||
/**
|
||||
* Replace callback.
|
||||
*
|
||||
* @param array $tagMatches Tag matches.
|
||||
* @return string Replacement.
|
||||
*/
|
||||
$replaceCallback = function ($tagMatches) {
|
||||
$oldAttrs = $this->maybeStripSelfClosingSlash($tagMatches['attrs']);
|
||||
$newAttrs = '';
|
||||
$offset = 0;
|
||||
while (\preg_match(self::AMP_BIND_DATA_ATTRIBUTE_ATTR_PATTERN, \substr($oldAttrs, $offset), $attrMatches)) {
|
||||
$offset += \strlen($attrMatches[0]);
|
||||
$attrName = \substr($attrMatches['name'], \strlen(Amp::BIND_DATA_ATTR_PREFIX));
|
||||
if ($this->options[Option::AMP_BIND_SYNTAX] === Option::AMP_BIND_SYNTAX_SQUARE_BRACKETS || \in_array($attrName, $this->convertedAmpBindAttributes, \true)) {
|
||||
$attrValue = isset($attrMatches['value']) ? $attrMatches['value'] : '=""';
|
||||
$newAttrs .= " [{$attrName}]{$attrValue}";
|
||||
} else {
|
||||
$newAttrs .= $attrMatches[0];
|
||||
}
|
||||
}
|
||||
// Bail on parse error which occurs when the regex isn't able to consume the entire $newAttrs string.
|
||||
if (\strlen($oldAttrs) !== $offset) {
|
||||
return $tagMatches[0];
|
||||
}
|
||||
return '<' . $tagMatches['name'] . $newAttrs . '>';
|
||||
};
|
||||
$result = \preg_replace_callback(self::AMP_BIND_DATA_START_PATTERN, $replaceCallback, $html);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* Strip the self-closing slash as long as it is not an attribute value, like for the href attribute.
|
||||
*
|
||||
* @param string $attributes Attributes to strip the self-closing slash of.
|
||||
* @return string Adapted attributes.
|
||||
*/
|
||||
private function maybeStripSelfClosingSlash($attributes)
|
||||
{
|
||||
$result = \preg_replace('#(?<!=)/$#', '', $attributes);
|
||||
if (!\is_string($result)) {
|
||||
return \rtrim($attributes);
|
||||
}
|
||||
return \rtrim($result);
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Attribute;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterSaveFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
/**
|
||||
* Filter for the emoji AMP symbol (⚡).
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class AmpEmojiAttribute implements BeforeLoadFilter, AfterSaveFilter
|
||||
{
|
||||
/**
|
||||
* Pattern to match an AMP emoji together with its variant (amp4ads, amp4email, ...).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP_EMOJI_ATTRIBUTE_PATTERN = '/<html\\s([^>]*?(?:' . Attribute::AMP_EMOJI_ALT . '|' . Attribute::AMP_EMOJI . ')(4(?:ads|email))?[^>]*?)>/i';
|
||||
/**
|
||||
* Store the emoji that was used to represent the AMP attribute.
|
||||
*
|
||||
* There are a few variations, so we want to keep track of this.
|
||||
*
|
||||
* @see https://github.com/ampproject/amphtml/issues/25990
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $usedAmpEmoji;
|
||||
/**
|
||||
* Covert the emoji AMP symbol (⚡) into pure text.
|
||||
*
|
||||
* The emoji symbol gets stripped by DOMDocument::loadHTML().
|
||||
*
|
||||
* @param string $html Source HTML string to convert the emoji AMP symbol in.
|
||||
* @return string Adapted source HTML string.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
$this->usedAmpEmoji = '';
|
||||
$result = \preg_replace_callback(self::AMP_EMOJI_ATTRIBUTE_PATTERN, function ($matches) {
|
||||
// Split into individual attributes.
|
||||
$attributes = \array_map('trim', \array_filter(\preg_split('#(\\s+[^"\'\\s=]+(?:=(?:"[^"]+"|\'[^\']+\'|[^"\'\\s]+))?)#', $matches[1], -1, \PREG_SPLIT_DELIM_CAPTURE)));
|
||||
foreach ($attributes as $index => $attribute) {
|
||||
$attributeMatches = [];
|
||||
if (\preg_match('/^(' . Attribute::AMP_EMOJI_ALT . '|' . Attribute::AMP_EMOJI . ')(4(?:ads|email))?$/i', $attribute, $attributeMatches)) {
|
||||
$this->usedAmpEmoji = $attributeMatches[1];
|
||||
$variant = !empty($attributeMatches[2]) ? $attributeMatches[2] : '';
|
||||
$attributes[$index] = Document::EMOJI_AMP_ATTRIBUTE_PLACEHOLDER . "=\"{$variant}\"";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return '<html ' . \implode(' ', $attributes) . '>';
|
||||
}, $html, 1);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* Restore the emoji AMP symbol (⚡) from its pure text placeholder.
|
||||
*
|
||||
* @param string $html HTML string to restore the AMP emoji symbol in.
|
||||
* @return string Adapted HTML string.
|
||||
*/
|
||||
public function afterSave($html)
|
||||
{
|
||||
if (empty($this->usedAmpEmoji)) {
|
||||
return $html;
|
||||
}
|
||||
$result = \preg_replace('/(<html\\s[^>]*?)' . \preg_quote(Document::EMOJI_AMP_ATTRIBUTE_PLACEHOLDER, '/') . '="([^"]*)"/i', '\\1' . $this->usedAmpEmoji . '\\2', $html, 1);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Attribute;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Tag;
|
||||
/**
|
||||
* Filter to convert a possible head[profile] attribute to link[rel=profile].
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class ConvertHeadProfileToLink implements AfterLoadFilter
|
||||
{
|
||||
/**
|
||||
* Converts a possible head[profile] attribute to link[rel=profile].
|
||||
*
|
||||
* The head[profile] attribute is only valid in HTML4, not HTML5.
|
||||
* So if it exists and isn't empty, add it to the <head> as a link[rel=profile] and strip the attribute.
|
||||
*
|
||||
* @param Document $document Document to be processed.
|
||||
*/
|
||||
public function afterLoad(Document $document)
|
||||
{
|
||||
if (!$document->head->hasAttribute(Attribute::PROFILE)) {
|
||||
return;
|
||||
}
|
||||
$profile = $document->head->getAttribute(Attribute::PROFILE);
|
||||
if ($profile) {
|
||||
$link = $document->createElement(Tag::LINK);
|
||||
$link->setAttribute(Attribute::REL, Attribute::PROFILE);
|
||||
$link->setAttribute(Attribute::HREF, $profile);
|
||||
$document->head->appendChild($link);
|
||||
}
|
||||
$document->head->removeAttribute(Attribute::PROFILE);
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Element;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Tag;
|
||||
use DOMAttr;
|
||||
/**
|
||||
* Filter for deduplicating head and body tags.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class DeduplicateTag implements AfterLoadFilter
|
||||
{
|
||||
/**
|
||||
* Deduplicate head and body tags.
|
||||
*
|
||||
* This keeps the first tag as the main tag and moves over all child nodes and attribute nodes from any subsequent
|
||||
* same tags over to remove them.
|
||||
*
|
||||
* @param Document $document Document to be processed.
|
||||
*/
|
||||
public function afterLoad(Document $document)
|
||||
{
|
||||
$tagNames = [Tag::HEAD, Tag::BODY];
|
||||
foreach ($tagNames as $tagName) {
|
||||
$tags = $document->getElementsByTagName($tagName);
|
||||
/**
|
||||
* Main tag to keep.
|
||||
*
|
||||
* @var Element|null $mainTag
|
||||
*/
|
||||
$mainTag = $tags->item(0);
|
||||
if (null === $mainTag) {
|
||||
continue;
|
||||
}
|
||||
while ($tags->length > 1) {
|
||||
/**
|
||||
* Tag to remove.
|
||||
*
|
||||
* @var Element $tagToRemove
|
||||
*/
|
||||
$tagToRemove = $tags->item(1);
|
||||
foreach ($tagToRemove->childNodes as $childNode) {
|
||||
$mainTag->appendChild($childNode->parentNode->removeChild($childNode));
|
||||
}
|
||||
while ($tagToRemove->hasAttributes()) {
|
||||
/**
|
||||
* Attribute node to move over to the main tag.
|
||||
*
|
||||
* @var DOMAttr $attribute
|
||||
*/
|
||||
$attribute = $tagToRemove->attributes->item(0);
|
||||
$tagToRemove->removeAttributeNode($attribute);
|
||||
// @TODO This doesn't deal properly with attributes present on both tags. Maybe overkill to add?
|
||||
// We could move over the copy_attributes from AMP_DOM_Utils to do this.
|
||||
$mainTag->setAttributeNode($attribute);
|
||||
}
|
||||
$tagToRemove->parentNode->removeChild($tagToRemove);
|
||||
}
|
||||
// Avoid doing the above query again if possible.
|
||||
if (\in_array($tagName, [Tag::HEAD, Tag::BODY], \true)) {
|
||||
$document->{$tagName} = $mainTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Option;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Options;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\InvalidByteSequence;
|
||||
/**
|
||||
* Filter for checking if the markup contains invalid byte sequences.
|
||||
*
|
||||
* If invalid byte sequences are passed to `DOMDocument`, it fails silently and produces Mojibake.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class DetectInvalidByteSequence implements BeforeLoadFilter
|
||||
{
|
||||
/**
|
||||
* Options instance to use.
|
||||
*
|
||||
* @var Options
|
||||
*/
|
||||
private $options;
|
||||
/**
|
||||
* DetectInvalidByteSequence constructor.
|
||||
*
|
||||
* @param Options $options Options instance to use.
|
||||
*/
|
||||
public function __construct(Options $options)
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
/**
|
||||
* Check if the markup contains invalid byte sequences.
|
||||
*
|
||||
* @param string $html String of HTML markup to be preprocessed.
|
||||
* @return string Preprocessed string of HTML markup.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
if ($this->options[Option::CHECK_ENCODING] && \function_exists('mb_check_encoding') && !\mb_check_encoding($html)) {
|
||||
throw InvalidByteSequence::forHtml();
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterSaveFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
/**
|
||||
* Filter to secure and restore the doctype node.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class DoctypeNode implements BeforeLoadFilter, AfterSaveFilter
|
||||
{
|
||||
/**
|
||||
* Regex pattern used for securing the doctype node if it is not the first one.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_SECURE_DOCTYPE_IF_NOT_FIRST_PATTERN = '/(^[^<]*(?>\\s*<!--[^>]*>\\s*)+<)(!)(doctype)(\\s+[^>]+?)(>)/i';
|
||||
/**
|
||||
* Regex replacement template for securing the doctype node.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_SECURE_DOCTYPE_REPLACEMENT_TEMPLATE = '\\1!--amp-\\3\\4-->';
|
||||
/**
|
||||
* Regex pattern used for restoring the doctype node.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_RESTORE_DOCTYPE_PATTERN = '/(^[^<]*(?>\\s*<!--[^>]*>\\s*)*<)(!--amp-)(doctype)(\\s+[^>]+?)(-->)/i';
|
||||
/**
|
||||
* Regex replacement template for restoring the doctype node.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_RESTORE_DOCTYPE_REPLACEMENT_TEMPLATE = '\\1!\\3\\4>';
|
||||
/**
|
||||
* Whether we had secured a doctype that needs restoring or not.
|
||||
*
|
||||
* This is an int as it receives the $count from the preg_replace().
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $securedDoctype = 0;
|
||||
/**
|
||||
* Secure the original doctype node.
|
||||
*
|
||||
* We need to keep elements around that were prepended to the doctype, like comment node used for source-tracking.
|
||||
* As DOM_Document prepends a new doctype node and removes the old one if the first element is not the doctype, we
|
||||
* need to ensure the original one is not stripped (by changing its node type) and restore it later on.
|
||||
*
|
||||
* @param string $html HTML string to adapt.
|
||||
* @return string Adapted HTML string.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
$result = \preg_replace(self::HTML_SECURE_DOCTYPE_IF_NOT_FIRST_PATTERN, self::HTML_SECURE_DOCTYPE_REPLACEMENT_TEMPLATE, $html, 1, $this->securedDoctype);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* Restore the original doctype node.
|
||||
*
|
||||
* @param string $html HTML string to adapt.
|
||||
* @return string Adapted HTML string.
|
||||
*/
|
||||
public function afterSave($html)
|
||||
{
|
||||
if (!$this->securedDoctype) {
|
||||
return $html;
|
||||
}
|
||||
$result = \preg_replace(self::HTML_RESTORE_DOCTYPE_PATTERN, self::HTML_RESTORE_DOCTYPE_REPLACEMENT_TEMPLATE, $html, 1);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Attribute;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Encoding;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Tag;
|
||||
/**
|
||||
* Filter for document encoding.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class DocumentEncoding implements BeforeLoadFilter
|
||||
{
|
||||
/**
|
||||
* Regex pattern to find a tag without an attribute.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_FIND_TAG_WITHOUT_ATTRIBUTE_PATTERN = '/<%1$s[^>]*?>[^<]*(?><\\/%1$s>)?/i';
|
||||
/**
|
||||
* Regex pattern to find a tag with an attribute.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_FIND_TAG_WITH_ATTRIBUTE_PATTERN = '/<%1$s [^>]*?\\s*%2$s\\s*=[^>]*?>[^<]*(?><\\/%1$s>)?/i';
|
||||
/**
|
||||
* Regex pattern to extract an attribute value out of an attribute string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_EXTRACT_ATTRIBUTE_VALUE_PATTERN = '/%s=(?>([\'"])(?<full>.*)?\\1|(?<partial>[^ \'";]+))/';
|
||||
/**
|
||||
* Delimiter used in regular expressions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_FIND_TAG_DELIMITER = '/';
|
||||
/**
|
||||
* Original encoding that was used for the document.
|
||||
*
|
||||
* @var Encoding
|
||||
*/
|
||||
private $originalEncoding;
|
||||
/**
|
||||
* DocumentEncoding constructor.
|
||||
*
|
||||
* @param Encoding $originalEncoding Original encoding that was used for the document.
|
||||
*/
|
||||
public function __construct(Encoding $originalEncoding)
|
||||
{
|
||||
$this->originalEncoding = $originalEncoding;
|
||||
}
|
||||
/**
|
||||
* Detect the encoding of the document.
|
||||
*
|
||||
* @param string $html Content of which to detect the encoding.
|
||||
* @return string Preprocessed string of HTML markup.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
$encoding = (string) $this->originalEncoding;
|
||||
// Check for HTML 4 http-equiv meta tags.
|
||||
foreach ($this->findTags($html, Tag::META, Attribute::HTTP_EQUIV) as $potentialHttpEquivTag) {
|
||||
$encoding = $this->extractValue($potentialHttpEquivTag, Attribute::CHARSET);
|
||||
if (\false !== $encoding) {
|
||||
$httpEquivTag = $potentialHttpEquivTag;
|
||||
}
|
||||
}
|
||||
// Strip all charset tags.
|
||||
if (isset($httpEquivTag)) {
|
||||
$html = \str_replace($httpEquivTag, '', $html);
|
||||
}
|
||||
// Check for HTML 5 charset meta tag. This overrides the HTML 4 charset.
|
||||
$charsetTag = $this->findTag($html, Tag::META, Attribute::CHARSET);
|
||||
if ($charsetTag) {
|
||||
$encoding = $this->extractValue($charsetTag, Attribute::CHARSET);
|
||||
// Strip the encoding if it is not the required one.
|
||||
if (\strtolower($encoding) !== Encoding::AMP) {
|
||||
$html = \str_replace($charsetTag, '', $html);
|
||||
}
|
||||
}
|
||||
$this->originalEncoding = new Encoding($encoding);
|
||||
if (!$this->originalEncoding->equals(Encoding::AMP)) {
|
||||
$html = $this->adaptEncoding($html);
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
/**
|
||||
* Adapt the encoding of the content.
|
||||
*
|
||||
* @param string $source Source content to adapt the encoding of.
|
||||
* @return string Adapted content.
|
||||
*/
|
||||
private function adaptEncoding($source)
|
||||
{
|
||||
// No encoding was provided, so we need to guess.
|
||||
if (\function_exists('mb_detect_encoding') && $this->originalEncoding->equals(Encoding::UNKNOWN)) {
|
||||
$this->originalEncoding = new Encoding($this->detectEncoding($source));
|
||||
}
|
||||
// Guessing the encoding seems to have failed, so we assume UTF-8 instead.
|
||||
// In my testing, this was not possible as long as one ISO-8859-x is in the detection order.
|
||||
if ($this->originalEncoding === null) {
|
||||
$this->originalEncoding = new Encoding(Encoding::AMP);
|
||||
// @codeCoverageIgnore
|
||||
}
|
||||
$this->originalEncoding->sanitize();
|
||||
// Sanitization failed, so we do a last effort to auto-detect.
|
||||
if (\function_exists('mb_detect_encoding') && $this->originalEncoding->equals(Encoding::UNKNOWN)) {
|
||||
$detectedEncoding = $this->detectEncoding($source);
|
||||
if ($detectedEncoding !== \false) {
|
||||
$this->originalEncoding = new Encoding($detectedEncoding);
|
||||
}
|
||||
}
|
||||
$target = \false;
|
||||
if (!$this->originalEncoding->equals(Encoding::AMP)) {
|
||||
$target = \function_exists('mb_convert_encoding') ? \mb_convert_encoding($source, Encoding::AMP, (string) $this->originalEncoding) : \false;
|
||||
}
|
||||
return \false !== $target ? $target : $source;
|
||||
}
|
||||
/**
|
||||
* Find a given tag with a given attribute.
|
||||
*
|
||||
* If multiple tags match, this method will only return the first one.
|
||||
*
|
||||
* @param string $content Content in which to find the tag.
|
||||
* @param string $element Element of the tag.
|
||||
* @param string $attribute Attribute that the tag contains.
|
||||
* @return string[] The requested tags. Returns an empty array if none found.
|
||||
*/
|
||||
private function findTags($content, $element, $attribute = null)
|
||||
{
|
||||
$matches = [];
|
||||
$pattern = empty($attribute) ? \sprintf(self::HTML_FIND_TAG_WITHOUT_ATTRIBUTE_PATTERN, \preg_quote($element, self::HTML_FIND_TAG_DELIMITER)) : \sprintf(self::HTML_FIND_TAG_WITH_ATTRIBUTE_PATTERN, \preg_quote($element, self::HTML_FIND_TAG_DELIMITER), \preg_quote($attribute, self::HTML_FIND_TAG_DELIMITER));
|
||||
if (\preg_match($pattern, $content, $matches)) {
|
||||
return $matches;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
/**
|
||||
* Find a given tag with a given attribute.
|
||||
*
|
||||
* If multiple tags match, this method will only return the first one.
|
||||
*
|
||||
* @param string $content Content in which to find the tag.
|
||||
* @param string $element Element of the tag.
|
||||
* @param string $attribute Attribute that the tag contains.
|
||||
* @return string|false The requested tag, or false if not found.
|
||||
*/
|
||||
private function findTag($content, $element, $attribute = null)
|
||||
{
|
||||
$matches = $this->findTags($content, $element, $attribute);
|
||||
if (empty($matches)) {
|
||||
return \false;
|
||||
}
|
||||
return $matches[0];
|
||||
}
|
||||
/**
|
||||
* Extract an attribute value from an HTML tag.
|
||||
*
|
||||
* @param string $tag Tag from which to extract the attribute.
|
||||
* @param string $attribute Attribute of which to extract the value.
|
||||
* @return string|false Extracted attribute value, false if not found.
|
||||
*/
|
||||
private function extractValue($tag, $attribute)
|
||||
{
|
||||
$matches = [];
|
||||
$pattern = \sprintf(self::HTML_EXTRACT_ATTRIBUTE_VALUE_PATTERN, \preg_quote($attribute, self::HTML_FIND_TAG_DELIMITER));
|
||||
if (\preg_match($pattern, $tag, $matches)) {
|
||||
return empty($matches['full']) ? $matches['partial'] : $matches['full'];
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
/**
|
||||
* Detect character encoding.
|
||||
*
|
||||
* @param string $source Source content to detect the encoding of.
|
||||
* @return string The character encoding of the source.
|
||||
*/
|
||||
private function detectEncoding($source)
|
||||
{
|
||||
$detectionOrder = \PHP_VERSION_ID >= 80100 ? Encoding::DETECTION_ORDER_PHP81 : Encoding::DETECTION_ORDER;
|
||||
return \mb_detect_encoding($source, $detectionOrder, \true);
|
||||
}
|
||||
}
|
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Attribute;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterSaveFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeSaveFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Element;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Tag;
|
||||
use DOMComment;
|
||||
/**
|
||||
* Filter for http-equiv charset.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class HttpEquivCharset implements BeforeLoadFilter, AfterLoadFilter, BeforeSaveFilter, AfterSaveFilter
|
||||
{
|
||||
/**
|
||||
* Value of the content field of the meta tag for the http-equiv compatibility charset.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_HTTP_EQUIV_CONTENT_VALUE = 'text/html; charset=utf-8';
|
||||
/**
|
||||
* Type of the meta tag for the http-equiv compatibility charset.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_HTTP_EQUIV_VALUE = 'content-type';
|
||||
/**
|
||||
* Charset compatibility tag for making DOMDocument behave.
|
||||
*
|
||||
* See: http://php.net/manual/en/domdocument.loadhtml.php#78243.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTTP_EQUIV_META_TAG = '<meta http-equiv="content-type" content="text/html; charset=utf-8">';
|
||||
/**
|
||||
* Regex pattern for adding an http-equiv charset for compatibility by anchoring it to the <head> tag.
|
||||
*
|
||||
* The opening tag pattern contains a comment to make sure we don't match a <head> tag within a comment.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_GET_HEAD_OPENING_TAG_PATTERN = '/(?><!--.*?-->\\s*)*<head(?>\\s+[^>]*)?>/is';
|
||||
/**
|
||||
* Regex replacement template for adding an http-equiv charset for compatibility by anchoring it to the <head> tag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_GET_HEAD_OPENING_TAG_REPLACEMENT = '$0' . self::HTTP_EQUIV_META_TAG;
|
||||
/**
|
||||
* Regex pattern for adding an http-equiv charset for compatibility by anchoring it to the <html> tag.
|
||||
*
|
||||
* The opening tag pattern contains a comment to make sure we don't match a <html> tag within a comment.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_GET_HTML_OPENING_TAG_PATTERN = '/(?><!--.*?-->\\s*)*<html(?>\\s+[^>]*)?>/is';
|
||||
/**
|
||||
* Regex replacement template for adding an http-equiv charset for compatibility by anchoring it to the <html> tag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTML_GET_HTML_OPENING_TAG_REPLACEMENT = '$0<head>' . self::HTTP_EQUIV_META_TAG . '</head>';
|
||||
/**
|
||||
* Regex pattern for matching an existing or added http-equiv charset.
|
||||
*/
|
||||
const HTML_GET_HTTP_EQUIV_TAG_PATTERN = '#<meta http-equiv=([\'"])content-type\\1 ' . 'content=([\'"])text/html; ' . 'charset=utf-8\\2>#i';
|
||||
/**
|
||||
* Temporary http-equiv charset node added to a document before saving.
|
||||
*
|
||||
* @var Element|null
|
||||
*/
|
||||
private $temporaryCharset;
|
||||
/**
|
||||
* Add a http-equiv charset meta tag to the document's <head> node.
|
||||
*
|
||||
* This is needed to make the DOMDocument behave as it should in terms of encoding.
|
||||
* See: http://php.net/manual/en/domdocument.loadhtml.php#78243.
|
||||
*
|
||||
* @param string $html HTML string to add the http-equiv charset to.
|
||||
* @return string Adapted string of HTML.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
$count = 0;
|
||||
// We try first to detect an existing <head> node.
|
||||
$result = \preg_replace(self::HTML_GET_HEAD_OPENING_TAG_PATTERN, self::HTML_GET_HEAD_OPENING_TAG_REPLACEMENT, $html, 1, $count);
|
||||
if (\is_string($result)) {
|
||||
$html = $result;
|
||||
}
|
||||
// If no <head> was found, we look for the <html> tag instead.
|
||||
if ($count < 1) {
|
||||
$result = \preg_replace(self::HTML_GET_HTML_OPENING_TAG_PATTERN, self::HTML_GET_HTML_OPENING_TAG_REPLACEMENT, $html, 1, $count);
|
||||
if (\is_string($result)) {
|
||||
$html = $result;
|
||||
}
|
||||
}
|
||||
// Finally, we just prepend the head with the required http-equiv charset.
|
||||
if ($count < 1) {
|
||||
$html = '<head>' . self::HTTP_EQUIV_META_TAG . '</head>' . $html;
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
/**
|
||||
* Remove http-equiv charset again.
|
||||
*
|
||||
* @param Document $document Document to be processed.
|
||||
*/
|
||||
public function afterLoad(Document $document)
|
||||
{
|
||||
$meta = $document->head->firstChild;
|
||||
// We might have leading comments we need to preserve here.
|
||||
while ($meta instanceof DOMComment) {
|
||||
$meta = $meta->nextSibling;
|
||||
}
|
||||
if ($meta instanceof Element && Tag::META === $meta->tagName && self::HTML_HTTP_EQUIV_VALUE === $meta->getAttribute(Attribute::HTTP_EQUIV) && self::HTML_HTTP_EQUIV_CONTENT_VALUE === $meta->getAttribute(Attribute::CONTENT)) {
|
||||
$document->head->removeChild($meta);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add a temporary http-equiv charset to the document before saving.
|
||||
*
|
||||
* @param Document $document Document to be preprocessed before saving it into HTML.
|
||||
*/
|
||||
public function beforeSave(Document $document)
|
||||
{
|
||||
// Force-add http-equiv charset to make DOMDocument behave as it should.
|
||||
// See: http://php.net/manual/en/domdocument.loadhtml.php#78243.
|
||||
$this->temporaryCharset = $document->createElement(Tag::META);
|
||||
$this->temporaryCharset->setAttribute(Attribute::HTTP_EQUIV, self::HTML_HTTP_EQUIV_VALUE);
|
||||
$this->temporaryCharset->setAttribute(Attribute::CONTENT, self::HTML_HTTP_EQUIV_CONTENT_VALUE);
|
||||
$document->head->insertBefore($this->temporaryCharset, $document->head->firstChild);
|
||||
}
|
||||
/**
|
||||
* Remove the temporary http-equiv charset again.
|
||||
*
|
||||
* It is also removed from the DOM again in case saveHTML() is used multiple times.
|
||||
*
|
||||
* @param string $html String of HTML markup to be preprocessed.
|
||||
* @return string Preprocessed string of HTML markup.
|
||||
*/
|
||||
public function afterSave($html)
|
||||
{
|
||||
if ($this->temporaryCharset instanceof Element) {
|
||||
$this->temporaryCharset->parentNode->removeChild($this->temporaryCharset);
|
||||
}
|
||||
$result = \preg_replace(self::HTML_GET_HTTP_EQUIV_TAG_PATTERN, '', $html, 1);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Option;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Options;
|
||||
/**
|
||||
* Filter for adapting the Libxml error behavior and options.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class LibxmlCompatibility implements BeforeLoadFilter, AfterLoadFilter
|
||||
{
|
||||
/**
|
||||
* Options instance to use.
|
||||
*
|
||||
* @var Options
|
||||
*/
|
||||
private $options;
|
||||
/**
|
||||
* Store the previous state fo the libxml "use internal errors" setting.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $libxmlPreviousState;
|
||||
/**
|
||||
* LibxmlCompatibility constructor.
|
||||
*
|
||||
* @param Options $options Options instance to use.
|
||||
*/
|
||||
public function __construct(Options $options)
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
/**
|
||||
* Preprocess the HTML to be loaded into the Dom\Document.
|
||||
*
|
||||
* @param string $html String of HTML markup to be preprocessed.
|
||||
* @return string Preprocessed string of HTML markup.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
$this->libxmlPreviousState = \libxml_use_internal_errors(\true);
|
||||
$this->options[Option::LIBXML_FLAGS] |= \LIBXML_COMPACT;
|
||||
/*
|
||||
* LIBXML_HTML_NODEFDTD is only available for libxml 2.7.8+.
|
||||
* This should be the case for PHP 5.4+, but some systems seem to compile against a custom libxml version that
|
||||
* is lower than expected.
|
||||
*/
|
||||
if (\defined('LIBXML_HTML_NODEFDTD')) {
|
||||
$this->options[Option::LIBXML_FLAGS] |= \constant('LIBXML_HTML_NODEFDTD');
|
||||
}
|
||||
/**
|
||||
* This flag prevents removing the closing tags used in inline JavaScript variables.
|
||||
*/
|
||||
if (\defined('LIBXML_SCHEMA_CREATE')) {
|
||||
$this->options[Option::LIBXML_FLAGS] |= \constant('LIBXML_SCHEMA_CREATE');
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
/**
|
||||
* Process the Document after the html loaded into the Dom\Document.
|
||||
*
|
||||
* @param Document $document Document to be processed.
|
||||
*/
|
||||
public function afterLoad(Document $document)
|
||||
{
|
||||
\libxml_clear_errors();
|
||||
\libxml_use_internal_errors($this->libxmlPreviousState);
|
||||
}
|
||||
}
|
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterSaveFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeSaveFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Tag;
|
||||
/**
|
||||
* Filter to handle the script[template="amp-mustache"].
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class MustacheScriptTemplates implements BeforeLoadFilter, AfterLoadFilter, BeforeSaveFilter, AfterSaveFilter
|
||||
{
|
||||
/**
|
||||
* Xpath query to fetch the elements containing Mustache templates (both <template type=amp-mustache> and
|
||||
* <script type=text/plain template=amp-mustache>).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const XPATH_MUSTACHE_TEMPLATE_ELEMENTS_QUERY = './/self::template[ @type = "amp-mustache" ]' . '|//self::script[ @type = "text/plain" ' . 'and @template = "amp-mustache" ]';
|
||||
/**
|
||||
* Xpath query to fetch the attributes that are being URL-encoded by saveHTML().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const XPATH_URL_ENCODED_ATTRIBUTES_QUERY = './/*/@src|.//*/@href|.//*/@action';
|
||||
/**
|
||||
* Store whether mustache template tags were replaced and need to be restored.
|
||||
*
|
||||
* @see replaceMustacheTemplateTokens()
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $mustacheTagsReplaced = \false;
|
||||
/**
|
||||
* Secures instances of script[template="amp-mustache"] by renaming element to tmp-script, as a workaround to a
|
||||
* libxml parsing issue.
|
||||
*
|
||||
* This script can have closing tags of its children table and td stripped.
|
||||
* So this changes its name from script to tmp-script to avoid this.
|
||||
*
|
||||
* @link https://github.com/ampproject/amp-wp/issues/4254
|
||||
*
|
||||
* @param string $html To replace the tag name that contains the mustache templates.
|
||||
* @return string The HTML, with the tag name of the mustache templates replaced.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
$result = \preg_replace('#<script(\\s[^>]*?template=(["\']?)amp-mustache\\2[^>]*)>(.*?)</script\\s*?>#is', '<tmp-script$1>$3</tmp-script>', $html);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* Restores the tag names of script[template="amp-mustache"] elements that were replaced earlier.
|
||||
*
|
||||
* @param Document $document Document to be processed.
|
||||
*/
|
||||
public function afterLoad(Document $document)
|
||||
{
|
||||
$tmp_script_elements = \iterator_to_array($document->getElementsByTagName('tmp-script'));
|
||||
foreach ($tmp_script_elements as $tmp_script_element) {
|
||||
$script = $document->createElement(Tag::SCRIPT);
|
||||
foreach ($tmp_script_element->attributes as $attr) {
|
||||
$script->setAttribute($attr->nodeName, $attr->nodeValue);
|
||||
}
|
||||
while ($tmp_script_element->firstChild) {
|
||||
$script->appendChild($tmp_script_element->firstChild);
|
||||
}
|
||||
$tmp_script_element->parentNode->replaceChild($script, $tmp_script_element);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Replace Mustache template tokens to safeguard them from turning into HTML entities.
|
||||
*
|
||||
* Prevents amp-mustache syntax from getting URL-encoded in attributes when saveHTML is done.
|
||||
* While this is applying to the entire document, it only really matters inside of <template>
|
||||
* elements, since URL-encoding of curly braces in href attributes would not normally matter.
|
||||
* But when this is done inside of a <template> then it breaks Mustache. Since Mustache
|
||||
* is logic-less and curly braces are not unsafe for HTML, we can do a global replacement.
|
||||
* The replacement is done on the entire HTML document instead of just inside of the <template>
|
||||
* elements since it is faster and wouldn't change the outcome.
|
||||
*
|
||||
* @param Document $document Document to be processed.
|
||||
*/
|
||||
public function beforeSave(Document $document)
|
||||
{
|
||||
$templates = $document->xpath->query(self::XPATH_MUSTACHE_TEMPLATE_ELEMENTS_QUERY, $document->body);
|
||||
if (0 === $templates->length) {
|
||||
return;
|
||||
}
|
||||
$mustacheTagPlaceholders = $this->getMustacheTagPlaceholders();
|
||||
foreach ($templates as $template) {
|
||||
foreach ($document->xpath->query(self::XPATH_URL_ENCODED_ATTRIBUTES_QUERY, $template) as $attribute) {
|
||||
$value = \preg_replace_callback($this->getMustacheTagPattern(), static function ($matches) use($mustacheTagPlaceholders) {
|
||||
return $mustacheTagPlaceholders[\trim($matches[0])];
|
||||
}, $attribute->nodeValue, -1, $count);
|
||||
if ($count) {
|
||||
// Note we cannot do `$attribute->nodeValue = $value` because the PHP DOM will try to parse any
|
||||
// entities. In the case of a URL value like '/foo/?bar=1&baz=2' the result is a warning for an
|
||||
// unterminated entity reference "baz". When the attribute value is updated via setAttribute() this
|
||||
// same problem does not occur, so that is why the following is used.
|
||||
$attribute->parentNode->setAttribute($attribute->nodeName, $value);
|
||||
$this->mustacheTagsReplaced = \true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Restore Mustache template tokens that were previously replaced.
|
||||
*
|
||||
* @param string $html HTML string to adapt.
|
||||
* @return string Adapted HTML string.
|
||||
*/
|
||||
public function afterSave($html)
|
||||
{
|
||||
if (!$this->mustacheTagsReplaced) {
|
||||
return $html;
|
||||
}
|
||||
$mustacheTagPlaceholders = $this->getMustacheTagPlaceholders();
|
||||
return \str_replace($mustacheTagPlaceholders, \array_keys($mustacheTagPlaceholders), $html);
|
||||
}
|
||||
/**
|
||||
* Get amp-mustache tag/placeholder mappings.
|
||||
*
|
||||
* @return string[] Mapping of mustache tag token to its placeholder.
|
||||
* @see \wpdb::placeholder_escape()
|
||||
*/
|
||||
private function getMustacheTagPlaceholders()
|
||||
{
|
||||
static $placeholders = null;
|
||||
if (null === $placeholders) {
|
||||
$placeholders = [];
|
||||
// Note: The order of these tokens is important, as it determines the order of the replacements.
|
||||
$tokens = ['{{{', '}}}', '{{#', '{{^', '{{/', '{{', '}}'];
|
||||
foreach ($tokens as $token) {
|
||||
$placeholders[$token] = '_amp_mustache_' . \md5(\uniqid($token));
|
||||
}
|
||||
}
|
||||
return $placeholders;
|
||||
}
|
||||
/**
|
||||
* Get a regular expression that matches all amp-mustache tags while consuming whitespace.
|
||||
*
|
||||
* Removing whitespace is needed to avoid DOMDocument turning whitespace into entities, like %20 for spaces.
|
||||
*
|
||||
* @return string Regex pattern to match amp-mustache tags with whitespace.
|
||||
*/
|
||||
private function getMustacheTagPattern()
|
||||
{
|
||||
static $tagPattern = null;
|
||||
if (null === $tagPattern) {
|
||||
$delimiter = ':';
|
||||
$tags = [];
|
||||
foreach (\array_keys($this->getMustacheTagPlaceholders()) as $token) {
|
||||
if ('{' === $token[0]) {
|
||||
$tags[] = \preg_quote($token, $delimiter) . '\\s*';
|
||||
} else {
|
||||
$tags[] = '\\s*' . \preg_quote($token, $delimiter);
|
||||
}
|
||||
}
|
||||
$tagPattern = $delimiter . \implode('|', $tags) . $delimiter;
|
||||
}
|
||||
return $tagPattern;
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterLoadFilter;
|
||||
use DOMAttr;
|
||||
/**
|
||||
* Normalizes HTML attributes to be HTML5 compatible.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class NormalizeHtmlAttributes implements AfterLoadFilter
|
||||
{
|
||||
/**
|
||||
* Normalizes HTML attributes to be HTML5 compatible.
|
||||
*
|
||||
* Conditionally removes html[xmlns], and converts html[xml:lang] to html[lang].
|
||||
*
|
||||
* @param Document $document Document to be processed.
|
||||
*/
|
||||
public function afterLoad(Document $document)
|
||||
{
|
||||
if (!$document->html->hasAttributes()) {
|
||||
return;
|
||||
}
|
||||
$xmlns = $document->html->attributes->getNamedItem('xmlns');
|
||||
if ($xmlns instanceof DOMAttr && 'http://www.w3.org/1999/xhtml' === $xmlns->nodeValue) {
|
||||
$document->html->removeAttributeNode($xmlns);
|
||||
}
|
||||
$xml_lang = $document->html->attributes->getNamedItem('xml:lang');
|
||||
if ($xml_lang instanceof DOMAttr) {
|
||||
$lang_node = $document->html->attributes->getNamedItem('lang');
|
||||
if ((!$lang_node || !$lang_node->nodeValue) && $xml_lang->nodeValue) {
|
||||
// Move the html[xml:lang] value to html[lang].
|
||||
$document->html->setAttribute('lang', $xml_lang->nodeValue);
|
||||
}
|
||||
$document->html->removeAttributeNode($xml_lang);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Option;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Options;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\InvalidOptionValue;
|
||||
/**
|
||||
* Handles the html entities present in the html and prevents them from double encoding.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class NormalizeHtmlEntities implements BeforeLoadFilter
|
||||
{
|
||||
const VALID_NORMALIZE_OPTION_VALUES = [Option::NORMALIZE_HTML_ENTITIES_AUTO, Option::NORMALIZE_HTML_ENTITIES_ALWAYS, Option::NORMALIZE_HTML_ENTITIES_NEVER];
|
||||
/**
|
||||
* Options instance to use.
|
||||
*
|
||||
* @var Options
|
||||
*/
|
||||
private $options;
|
||||
/**
|
||||
* Whether to use the NormalizeHtmlEntities filter or not.
|
||||
*
|
||||
* Accepted values are 'auto', 'always' and 'never'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $normalizeHtmlEntities;
|
||||
/**
|
||||
* NormalizeHtmlEntities constructor.
|
||||
*
|
||||
* @param Options $options Options instance to use.
|
||||
*
|
||||
* @throws InvalidOptionValue If invalid value is set to normalize_html_entities option.
|
||||
*/
|
||||
public function __construct(Options $options)
|
||||
{
|
||||
$this->options = $options;
|
||||
$this->normalizeHtmlEntities = $options[Option::NORMALIZE_HTML_ENTITIES];
|
||||
if (!\in_array($this->normalizeHtmlEntities, self::VALID_NORMALIZE_OPTION_VALUES, \true)) {
|
||||
throw InvalidOptionValue::forValue(Option::NORMALIZE_HTML_ENTITIES, self::VALID_NORMALIZE_OPTION_VALUES, $this->normalizeHtmlEntities);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Preprocess the HTML to be loaded into the Dom\Document.
|
||||
*
|
||||
* @param string $html String of HTML markup to be preprocessed.
|
||||
* @return string Preprocessed string of HTML markup.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
if ($this->normalizeHtmlEntities === Option::NORMALIZE_HTML_ENTITIES_NEVER || $this->normalizeHtmlEntities === Option::NORMALIZE_HTML_ENTITIES_AUTO && !$this->hasHtmlEntities($html)) {
|
||||
return $html;
|
||||
}
|
||||
return \html_entity_decode($html, $this->options[Option::NORMALIZE_HTML_ENTITIES_FLAGS], $this->options[Option::ENCODING]);
|
||||
}
|
||||
/**
|
||||
* Detect the presence of html entities in the html.
|
||||
*
|
||||
* @param string $html The html in which this method will to detect the entities.
|
||||
* @return bool Whether the html contains entities or not.
|
||||
*/
|
||||
protected function hasHtmlEntities($html)
|
||||
{
|
||||
// TODO: Discuss other popular entities to look for, especially for languages
|
||||
// with different punctuation symbols.
|
||||
return \preg_match('/,|.|!|?/', $html);
|
||||
}
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\UniqueIdManager;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Tag;
|
||||
/**
|
||||
* Handle the noscript elements with placeholders.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class NoscriptElements implements BeforeLoadFilter, AfterLoadFilter
|
||||
{
|
||||
/**
|
||||
* UniqueIdManager instance to use.
|
||||
*
|
||||
* @var UniqueIdManager
|
||||
*/
|
||||
private $uniqueIdManager;
|
||||
/**
|
||||
* NoscriptElements constructor.
|
||||
*
|
||||
* @param UniqueIdManager $uniqueIdManager UniqueIdManager instance to use.
|
||||
*/
|
||||
public function __construct(UniqueIdManager $uniqueIdManager)
|
||||
{
|
||||
$this->uniqueIdManager = $uniqueIdManager;
|
||||
}
|
||||
/**
|
||||
* Store the <noscript> markup that was extracted to preserve it during parsing.
|
||||
*
|
||||
* The array keys are the element IDs for placeholder <meta> tags.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $noscriptPlaceholderComments = [];
|
||||
/**
|
||||
* Maybe replace noscript elements with placeholders.
|
||||
*
|
||||
* This is done because libxml<2.8 might parse them incorrectly.
|
||||
* When appearing in the head element, a noscript can cause the head to close prematurely
|
||||
* and the noscript gets moved to the body and anything after it which was in the head.
|
||||
* See <https://stackoverflow.com/questions/39013102/why-does-noscript-move-into-body-tag-instead-of-head-tag>.
|
||||
* This is limited to only running in the head element because this is where the problem lies,
|
||||
* and it is important for the AMP_Script_Sanitizer to be able to access the noscript elements
|
||||
* in the body otherwise.
|
||||
*
|
||||
* @param string $html HTML string to adapt.
|
||||
* @return string Adapted HTML string.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
if (\version_compare(\LIBXML_DOTTED_VERSION, '2.8', '<')) {
|
||||
$result = \preg_replace_callback('#^.+?(?=<body)#is', function ($headMatches) {
|
||||
return \preg_replace_callback('#<noscript[^>]*>.*?</noscript>#si', function ($noscriptMatches) {
|
||||
$id = $this->uniqueIdManager->getUniqueId('noscript');
|
||||
$this->noscriptPlaceholderComments[$id] = $noscriptMatches[0];
|
||||
return \sprintf('<meta class="noscript-placeholder" id="%s">', $id);
|
||||
}, $headMatches[0]);
|
||||
}, $html);
|
||||
if (\is_string($result)) {
|
||||
$html = $result;
|
||||
}
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
/**
|
||||
* Maybe restore noscript elements with placeholders.
|
||||
*
|
||||
* This is done because libxml<2.8 might parse them incorrectly.
|
||||
* When appearing in the head element, a noscript can cause the head to close prematurely
|
||||
* and the noscript gets moved to the body and anything after it which was in the head.
|
||||
* See <https://stackoverflow.com/questions/39013102/why-does-noscript-move-into-body-tag-instead-of-head-tag>.
|
||||
* This is limited to only running in the head element because this is where the problem lies,
|
||||
* and it is important for the AMP_Script_Sanitizer to be able to access the noscript elements
|
||||
* in the body otherwise.
|
||||
*
|
||||
* @param Document $document Document to be processed.
|
||||
*/
|
||||
public function afterLoad(Document $document)
|
||||
{
|
||||
foreach ($this->noscriptPlaceholderComments as $id => $noscriptHtmlFragment) {
|
||||
$placeholderElement = $document->getElementById($id);
|
||||
if (!$placeholderElement || !$placeholderElement->parentNode) {
|
||||
continue;
|
||||
}
|
||||
$noscriptFragmentDocument = $document::fromHtmlFragment($noscriptHtmlFragment);
|
||||
if (!$noscriptFragmentDocument) {
|
||||
continue;
|
||||
}
|
||||
$exportBody = $noscriptFragmentDocument->getElementsByTagName(Tag::BODY)->item(0);
|
||||
if (!$exportBody) {
|
||||
continue;
|
||||
}
|
||||
$importFragment = $document->createDocumentFragment();
|
||||
while ($exportBody->firstChild) {
|
||||
$importNode = $exportBody->removeChild($exportBody->firstChild);
|
||||
$importNode = $document->importNode($importNode, \true);
|
||||
$importFragment->appendChild($importNode);
|
||||
}
|
||||
$placeholderElement->parentNode->replaceChild($importFragment, $placeholderElement);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterSaveFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
/**
|
||||
* Protect the esi tags from being broken.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class ProtectEsiTags implements BeforeLoadFilter, AfterSaveFilter
|
||||
{
|
||||
/**
|
||||
* List of self-closing ESI tags.
|
||||
*
|
||||
* @link https://www.w3.org/TR/esi-lang/
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const SELF_CLOSING_TAGS = ['esi:include', 'esi:comment'];
|
||||
/**
|
||||
* Preprocess the HTML to be loaded into the Dom\Document.
|
||||
*
|
||||
* @param string $html String of HTML markup to be preprocessed.
|
||||
* @return string Preprocessed string of HTML markup.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
$patterns = ['#<(' . \implode('|', self::SELF_CLOSING_TAGS) . ')([^>]*?)(?>\\s*(?<!\\\\)/)?>(?!</\\1>)#', '/(<esi:include.+?)(src)=/', '/(<\\/?)esi:/'];
|
||||
$replacements = ['<$1$2></$1>', '$1esi-src=', '$1esi-'];
|
||||
$result = \preg_replace($patterns, $replacements, $html);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* Process the Dom\Document after being saved from Dom\Document.
|
||||
*
|
||||
* @param string $html String of HTML markup to be preprocessed.
|
||||
* @return string Preprocessed string of HTML markup.
|
||||
*/
|
||||
public function afterSave($html)
|
||||
{
|
||||
$patterns = ['/(<\\/?)esi-/', '/(<esi:include.+?)(esi-src)=/', '#></(' . \implode('|', self::SELF_CLOSING_TAGS) . ')>#i'];
|
||||
$replacements = ['$1esi:', '$1src=', '/>'];
|
||||
$result = \preg_replace($patterns, $replacements, $html);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterSaveFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Tag;
|
||||
/**
|
||||
* Filter to secure and restore self-closing SVG related elements.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class SelfClosingSVGElements implements BeforeLoadFilter, AfterSaveFilter
|
||||
{
|
||||
/**
|
||||
* SVG elements that are self-closing.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const SELF_CLOSING_TAGS = [Tag::CIRCLE, Tag::G, Tag::PATH];
|
||||
/**
|
||||
* Force all self-closing tags to have closing tags.
|
||||
*
|
||||
* @param string $html HTML string to adapt.
|
||||
* @return string Adapted HTML string.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
static $regexPattern = null;
|
||||
if (null === $regexPattern) {
|
||||
$regexPattern = '#<(' . \implode('|', self::SELF_CLOSING_TAGS) . ')((?>\\s*[^/>]*))/?>(?!.*</\\1>)#is';
|
||||
}
|
||||
$result = \preg_replace($regexPattern, '<$1$2></$1>', $html);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* Restore all self-closing tags again.
|
||||
*
|
||||
* @param string $html HTML string to adapt.
|
||||
* @return string Adapted HTML string.
|
||||
*/
|
||||
public function afterSave($html)
|
||||
{
|
||||
static $regexPattern = null;
|
||||
if (null === $regexPattern) {
|
||||
$regexPattern = '#<(' . \implode('|', self::SELF_CLOSING_TAGS) . ')((?>\\s*[^>]*))>(?><\\/\\1>)#i';
|
||||
}
|
||||
$result = \preg_replace($regexPattern, '<$1$2 />', $html);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterSaveFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\BeforeLoadFilter;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Tag;
|
||||
/**
|
||||
* Filter to secure and restore self-closing tags.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class SelfClosingTags implements BeforeLoadFilter, AfterSaveFilter
|
||||
{
|
||||
/**
|
||||
* Whether the self-closing tags were transformed and need to be restored.
|
||||
*
|
||||
* This avoids duplicating this effort (maybe corrupting the DOM) on multiple calls to saveHTML().
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $selfClosingTagsTransformed = \false;
|
||||
/**
|
||||
* Force all self-closing tags to have closing tags.
|
||||
*
|
||||
* This is needed because DOMDocument isn't fully aware of these.
|
||||
*
|
||||
* @param string $html HTML string to adapt.
|
||||
* @return string Adapted HTML string.
|
||||
*/
|
||||
public function beforeLoad($html)
|
||||
{
|
||||
static $regexPattern = null;
|
||||
if (null === $regexPattern) {
|
||||
$regexPattern = '#<(' . \implode('|', Tag::SELF_CLOSING_TAGS) . ')([^>]*?)(?>\\s*(?<!\\\\)/)?>(?!</\\1>)#';
|
||||
}
|
||||
$this->selfClosingTagsTransformed = \true;
|
||||
$result = \preg_replace($regexPattern, '<$1$2></$1>', $html);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* Restore all self-closing tags again.
|
||||
*
|
||||
* @param string $html HTML string to adapt.
|
||||
* @return string Adapted HTML string.
|
||||
*/
|
||||
public function afterSave($html)
|
||||
{
|
||||
static $regexPattern = null;
|
||||
if (!$this->selfClosingTagsTransformed) {
|
||||
return $html;
|
||||
}
|
||||
if (null === $regexPattern) {
|
||||
$regexPattern = '#</(' . \implode('|', Tag::SELF_CLOSING_TAGS) . ')>#i';
|
||||
}
|
||||
$this->selfClosingTagsTransformed = \false;
|
||||
$result = \preg_replace($regexPattern, '', $html);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document\Filter;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document\AfterSaveFilter;
|
||||
/**
|
||||
* Filter for fixing the mangled encoding of src attributes with SVG data.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class SvgSourceAttributeEncoding implements AfterSaveFilter
|
||||
{
|
||||
/**
|
||||
* Regex pattern to match an SVG sizer element.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const I_AMPHTML_SIZER_REGEX_PATTERN = '/(?<before_src><i-amphtml-sizer\\s+[^>]*>\\s*<img\\s+[^>]*?\\s+src=([\'"]))' . '(?<src>.*?)' . '(?<after_src>\\2><\\/i-amphtml-sizer>)/i';
|
||||
/**
|
||||
* Regex pattern for extracting fields to adapt out of a src attribute.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SRC_SVG_REGEX_PATTERN = '/^\\s*(?<type>[^<]+)(?<value><svg[^>]+>)\\s*$/i';
|
||||
/**
|
||||
* Process SVG sizers to ensure they match the required format to validate against AMP.
|
||||
*
|
||||
* @param string $html HTML output string to tweak.
|
||||
* @return string Tweaked HTML output string.
|
||||
*/
|
||||
public function afterSave($html)
|
||||
{
|
||||
$result = \preg_replace_callback(self::I_AMPHTML_SIZER_REGEX_PATTERN, [$this, 'adaptSizer'], $html);
|
||||
if (!\is_string($result)) {
|
||||
return $html;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* Adapt the sizer element so that it validates against the AMP spec.
|
||||
*
|
||||
* @param array $matches Matches that the regular expression collected.
|
||||
* @return string Adapted string to use as replacement.
|
||||
*/
|
||||
private function adaptSizer($matches)
|
||||
{
|
||||
$src = $matches['src'];
|
||||
$src = \htmlspecialchars_decode($src, \ENT_NOQUOTES);
|
||||
$src = \preg_replace_callback(self::SRC_SVG_REGEX_PATTERN, [$this, 'adaptSvg'], $src);
|
||||
if (!\is_string($src)) {
|
||||
// The regex replace failed, so revert to the initial src.
|
||||
$src = $matches['src'];
|
||||
}
|
||||
return $matches['before_src'] . $src . $matches['after_src'];
|
||||
}
|
||||
/**
|
||||
* Adapt the SVG syntax within the sizer element so that it validates against the AMP spec.
|
||||
*
|
||||
* @param array $matches Matches that the regular expression collected.
|
||||
* @return string Adapted string to use as replacement.
|
||||
*/
|
||||
private function adaptSvg($matches)
|
||||
{
|
||||
return $matches['type'] . \urldecode($matches['value']);
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
|
||||
|
||||
/**
|
||||
* Option constants that can be used to configure a Dom\Document instance.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface Option
|
||||
{
|
||||
/**
|
||||
* Option to configure the preferred amp-bind syntax.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP_BIND_SYNTAX = 'amp_bind_syntax';
|
||||
/**
|
||||
* Option to provide the encoding of the document.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ENCODING = 'encoding';
|
||||
/**
|
||||
* Option to provide additional libxml flags to configure parsing of the document.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LIBXML_FLAGS = 'libxml_flags';
|
||||
/**
|
||||
* Option to check encoding in order to detect invalid byte sequences.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CHECK_ENCODING = 'check_encoding';
|
||||
/**
|
||||
* Option to use the NormalizeHtmlEntities filter.
|
||||
*
|
||||
* Accepted values are 'auto', 'always' and 'never'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NORMALIZE_HTML_ENTITIES = 'normalize_html_entities';
|
||||
/**
|
||||
* Flags option for html entities.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NORMALIZE_HTML_ENTITIES_FLAGS = 'normalize_html_entities_flags';
|
||||
/**
|
||||
* Associative array of known options and their respective default value.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const DEFAULTS = [self::AMP_BIND_SYNTAX => self::AMP_BIND_SYNTAX_AUTO, self::ENCODING => null, self::LIBXML_FLAGS => 0, self::CHECK_ENCODING => \false, self::NORMALIZE_HTML_ENTITIES => self::NORMALIZE_HTML_ENTITIES_AUTO, self::NORMALIZE_HTML_ENTITIES_FLAGS => \ENT_HTML5];
|
||||
/**
|
||||
* Possible value 'auto' for the 'amp_bind_syntax' option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP_BIND_SYNTAX_AUTO = 'auto';
|
||||
/**
|
||||
* Possible value 'data_attribute' for the 'amp_bind_syntax' option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP_BIND_SYNTAX_DATA_ATTRIBUTE = 'data_attribute';
|
||||
/**
|
||||
* Possible value 'square_brackets' for the 'amp_bind_syntax' option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP_BIND_SYNTAX_SQUARE_BRACKETS = 'square_brackets';
|
||||
/**
|
||||
* Possible value 'auto' for the 'normalize_html_entities' option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NORMALIZE_HTML_ENTITIES_AUTO = 'auto';
|
||||
/**
|
||||
* Possible value 'always' for the 'normalize_html_entities' option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NORMALIZE_HTML_ENTITIES_ALWAYS = 'always';
|
||||
/**
|
||||
* Possible value 'never' for the 'normalize_html_entities' option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NORMALIZE_HTML_ENTITIES_NEVER = 'never';
|
||||
}
|
270
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Dom/Element.php
vendored
Normal file
270
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Dom/Element.php
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Attribute;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\MaxCssByteCountExceeded;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\CssRule;
|
||||
use DOMAttr;
|
||||
use DOMElement;
|
||||
/**
|
||||
* Abstract away some convenience logic for handling DOMElement objects.
|
||||
*
|
||||
* @property Document $ownerDocument The ownerDocument for these elements should always be a Dom\Document.
|
||||
* @property int $inlineStyleByteCount Number of bytes that are consumed by the inline style attribute.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class Element extends DOMElement
|
||||
{
|
||||
/**
|
||||
* Regular expression pattern to match events and actions within an 'on' attribute.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP_EVENT_ACTIONS_REGEX_PATTERN = '/((?<event>[^:;]+):(?<actions>(?:[^;,\\(]+(?:\\([^\\)]+\\))?,?)+))+?/';
|
||||
/**
|
||||
* Regular expression pattern to match individual actions within an event.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP_ACTION_REGEX_PATTERN = '/(?<action>[^(),\\s]+(?:\\([^\\)]+\\))?)+/';
|
||||
/**
|
||||
* Error message to use when the __get() is triggered for an unknown property.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PROPERTY_GETTER_ERROR_MESSAGE = 'Undefined property: AmpProject\\Dom\\Element::';
|
||||
/**
|
||||
* Associative array for lazily-created, cached properties for the document.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $properties = [];
|
||||
/**
|
||||
* Add CSS styles to the element as an inline style attribute.
|
||||
*
|
||||
* @param string $style CSS style(s) to add to the inline style attribute.
|
||||
* @param bool $prepend Optional. Whether to prepend the new style to existing styles or not. Defaults to false.
|
||||
* @return DOMAttr|false The new or modified DOMAttr or false if an error occurred.
|
||||
* @throws MaxCssByteCountExceeded If the allowed max byte count is exceeded.
|
||||
*/
|
||||
public function addInlineStyle($style, $prepend = \false)
|
||||
{
|
||||
$style = \trim($style, CssRule::CSS_TRIM_CHARACTERS);
|
||||
$existingStyle = (string) \trim($this->getAttribute(Attribute::STYLE));
|
||||
if (!empty($existingStyle)) {
|
||||
$existingStyle = \rtrim($existingStyle, ';') . ';';
|
||||
}
|
||||
$newStyle = $prepend ? $style . ';' . $existingStyle : $existingStyle . $style;
|
||||
return $this->setAttribute(Attribute::STYLE, $newStyle);
|
||||
}
|
||||
/**
|
||||
* Sets or modifies an attribute.
|
||||
*
|
||||
* @link https://php.net/manual/en/domelement.setattribute.php
|
||||
* @param string $name The name of the attribute.
|
||||
* @param string $value The value of the attribute.
|
||||
* @return DOMAttr|false The new or modified DOMAttr or false if an error occurred.
|
||||
* @throws MaxCssByteCountExceeded If the allowed max byte count is exceeded.
|
||||
*/
|
||||
public function setAttribute($name, $value)
|
||||
{
|
||||
// Make sure $value is always a string and not null.
|
||||
$value = \strval($value);
|
||||
if ($name === Attribute::STYLE && $this->ownerDocument->isCssMaxByteCountEnforced()) {
|
||||
$newByteCount = \strlen($value);
|
||||
if ($this->ownerDocument->getRemainingCustomCssSpace() < $newByteCount - $this->inlineStyleByteCount) {
|
||||
throw MaxCssByteCountExceeded::forInlineStyle($this, $value);
|
||||
}
|
||||
$this->ownerDocument->addInlineStyleByteCount($newByteCount - $this->inlineStyleByteCount);
|
||||
$this->inlineStyleByteCount = $newByteCount;
|
||||
return parent::setAttribute(Attribute::STYLE, $value);
|
||||
}
|
||||
return parent::setAttribute($name, $value);
|
||||
}
|
||||
/**
|
||||
* Adds a boolean attribute without value.
|
||||
*
|
||||
* @param string $name The name of the attribute.
|
||||
* @return DOMAttr|false The new or modified DOMAttr or false if an error occurred.
|
||||
* @throws MaxCssByteCountExceeded If the allowed max byte count is exceeded.
|
||||
*/
|
||||
public function addBooleanAttribute($name)
|
||||
{
|
||||
$attribute = new DOMAttr($name);
|
||||
$result = $this->setAttributeNode($attribute);
|
||||
if (!$result instanceof DOMAttr) {
|
||||
return \false;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* Copy one or more attributes from this element to another element.
|
||||
*
|
||||
* @param array|string $attributes Attribute name or array of attribute names to copy.
|
||||
* @param Element $target Target Dom\Element to copy the attributes to.
|
||||
* @param string $defaultSeparator Default separator to use for multiple values if the attribute is not known.
|
||||
*/
|
||||
public function copyAttributes($attributes, Element $target, $defaultSeparator = ',')
|
||||
{
|
||||
foreach ((array) $attributes as $attribute) {
|
||||
if ($this->hasAttribute($attribute)) {
|
||||
$values = $this->getAttribute($attribute);
|
||||
if ($target->hasAttribute($attribute)) {
|
||||
switch ($attribute) {
|
||||
case Attribute::ON:
|
||||
$values = self::mergeAmpActions($target->getAttribute($attribute), $values);
|
||||
break;
|
||||
case Attribute::CLASS_:
|
||||
$values = $target->getAttribute($attribute) . ' ' . $values;
|
||||
break;
|
||||
default:
|
||||
$values = $target->getAttribute($attribute) . $defaultSeparator . $values;
|
||||
}
|
||||
}
|
||||
$target->setAttribute($attribute, $values);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Register an AMP action to an event.
|
||||
*
|
||||
* If the element already contains one or more events or actions, the method
|
||||
* will assemble them in a smart way.
|
||||
*
|
||||
* @param string $event Event to trigger the action on.
|
||||
* @param string $action Action to add.
|
||||
*/
|
||||
public function addAmpAction($event, $action)
|
||||
{
|
||||
$eventActionString = "{$event}:{$action}";
|
||||
if (!$this->hasAttribute(Attribute::ON)) {
|
||||
// There's no "on" attribute yet, so just add it and be done.
|
||||
$this->setAttribute(Attribute::ON, $eventActionString);
|
||||
return;
|
||||
}
|
||||
$this->setAttribute(Attribute::ON, self::mergeAmpActions($this->getAttribute(Attribute::ON), $eventActionString));
|
||||
}
|
||||
/**
|
||||
* Merge two sets of AMP events & actions.
|
||||
*
|
||||
* @param string $first First event/action string.
|
||||
* @param string $second First event/action string.
|
||||
* @return string Merged event/action string.
|
||||
*/
|
||||
public static function mergeAmpActions($first, $second)
|
||||
{
|
||||
$events = [];
|
||||
foreach ([$first, $second] as $eventActionString) {
|
||||
$matches = [];
|
||||
$results = \preg_match_all(self::AMP_EVENT_ACTIONS_REGEX_PATTERN, $eventActionString, $matches);
|
||||
if (!$results || !isset($matches['event'])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($matches['event'] as $index => $event) {
|
||||
$events[$event][] = $matches['actions'][$index];
|
||||
}
|
||||
}
|
||||
$valueStrings = [];
|
||||
foreach ($events as $event => $actionStringsArray) {
|
||||
$actionsArray = [];
|
||||
\array_walk($actionStringsArray, static function ($actions) use(&$actionsArray) {
|
||||
$matches = [];
|
||||
$results = \preg_match_all(self::AMP_ACTION_REGEX_PATTERN, $actions, $matches);
|
||||
if (!$results || !isset($matches['action'])) {
|
||||
$actionsArray[] = $actions;
|
||||
return;
|
||||
}
|
||||
$actionsArray = \array_merge($actionsArray, $matches['action']);
|
||||
});
|
||||
$actions = \implode(',', \array_unique(\array_filter($actionsArray)));
|
||||
$valueStrings[] = "{$event}:{$actions}";
|
||||
}
|
||||
return \implode(';', $valueStrings);
|
||||
}
|
||||
/**
|
||||
* Extract this element's HTML attributes and return as an associative array.
|
||||
*
|
||||
* @return string[] The attributes for the passed node, or an empty array if it has no attributes.
|
||||
*/
|
||||
public function getAttributesAsAssocArray()
|
||||
{
|
||||
$attributes = [];
|
||||
if (!$this->hasAttributes()) {
|
||||
return $attributes;
|
||||
}
|
||||
foreach ($this->attributes as $attribute) {
|
||||
$attributes[$attribute->nodeName] = $attribute->nodeValue;
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
/**
|
||||
* Add one or more HTML element attributes to this element.
|
||||
*
|
||||
* @param string[] $attributes One or more attributes for the node's HTML element.
|
||||
*/
|
||||
public function setAttributes($attributes)
|
||||
{
|
||||
foreach ($attributes as $name => $value) {
|
||||
try {
|
||||
$this->setAttribute($name, $value);
|
||||
} catch (MaxCssByteCountExceeded $e) {
|
||||
/*
|
||||
* Catch a "Invalid Character Error" when libxml is able to parse attributes with invalid characters,
|
||||
* but it throws error when attempting to set them via DOM methods. For example, '...this' can be parsed
|
||||
* as an attribute but it will throw an exception when attempting to setAttribute().
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Magic getter to implement lazily-created, cached properties for the element.
|
||||
*
|
||||
* @param string $name Name of the property to get.
|
||||
* @return mixed Value of the property, or null if unknown property was requested.
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'inlineStyleByteCount':
|
||||
if (!isset($this->properties['inlineStyleByteCount'])) {
|
||||
$this->properties['inlineStyleByteCount'] = \strlen((string) $this->getAttribute(Attribute::STYLE));
|
||||
}
|
||||
return $this->properties['inlineStyleByteCount'];
|
||||
}
|
||||
// Mimic regular PHP behavior for missing notices.
|
||||
\trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, \E_USER_NOTICE);
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Magic setter to implement lazily-created, cached properties for the element.
|
||||
*
|
||||
* @param string $name Name of the property to set.
|
||||
* @param mixed $value Value of the property.
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
if ($name !== 'inlineStyleByteCount') {
|
||||
// Mimic regular PHP behavior for missing notices.
|
||||
\trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, \E_USER_NOTICE);
|
||||
return;
|
||||
}
|
||||
$this->properties[$name] = $value;
|
||||
}
|
||||
/**
|
||||
* Magic callback for lazily-created, cached properties for the element.
|
||||
*
|
||||
* @param string $name Name of the property to set.
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
if ($name !== 'inlineStyleByteCount') {
|
||||
// Mimic regular PHP behavior for missing notices.
|
||||
\trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, \E_USER_NOTICE);
|
||||
return \false;
|
||||
}
|
||||
return isset($this->properties[$name]);
|
||||
}
|
||||
}
|
73
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Dom/ElementDump.php
vendored
Normal file
73
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Dom/ElementDump.php
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Str;
|
||||
use DOMAttr;
|
||||
/**
|
||||
* Dump an element with its attributes.
|
||||
*
|
||||
* This is meant for quick identification of an element and does not dump the child element or other inner content
|
||||
* from that element.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class ElementDump
|
||||
{
|
||||
/**
|
||||
* Element to dump.
|
||||
*
|
||||
* @var Element
|
||||
*/
|
||||
private $element;
|
||||
/**
|
||||
* Maximum length to truncate attributes and textContent to.
|
||||
*
|
||||
* Defaults to 120.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $truncate;
|
||||
/**
|
||||
* Instantiate an ElementDump object.
|
||||
*
|
||||
* The object is meant to be cast to a string to do its magic.
|
||||
*
|
||||
* @param Element $element Element to dump.
|
||||
* @param int $truncate Optional. Maximum length to truncate attributes and textContent to. Defaults to 120.
|
||||
*/
|
||||
public function __construct(Element $element, $truncate = 120)
|
||||
{
|
||||
$this->element = $element;
|
||||
$this->truncate = $truncate;
|
||||
}
|
||||
/**
|
||||
* Dump the provided element into a string.
|
||||
*
|
||||
* @return string Dump of the element.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$attributes = $this->maybeTruncate(\array_reduce(\iterator_to_array($this->element->attributes, \true), static function ($text, DOMAttr $attribute) {
|
||||
return $text . " {$attribute->nodeName}=\"{$attribute->value}\"";
|
||||
}, ''));
|
||||
$textContent = $this->maybeTruncate($this->element->textContent);
|
||||
return \sprintf('<%1$s%2$s>%3$s</%1$s>', $this->element->tagName, $attributes, $textContent);
|
||||
}
|
||||
/**
|
||||
* Truncate the provided text if needed.
|
||||
*
|
||||
* @param string $text Text to truncate.
|
||||
* @return string Potentially truncated text.
|
||||
*/
|
||||
private function maybeTruncate($text)
|
||||
{
|
||||
if ($this->truncate <= 0) {
|
||||
return $text;
|
||||
}
|
||||
if (Str::length($text) > $this->truncate) {
|
||||
return Str::substring($text, 0, $this->truncate - 1) . '…';
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
}
|
252
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Dom/LinkManager.php
vendored
Normal file
252
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Dom/LinkManager.php
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Attribute;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\FailedToCreateLink;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\RequestDestination;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Tag;
|
||||
use DOMNode;
|
||||
/**
|
||||
* Link manager class that is used to manage the <link> tags within a document's <head>.
|
||||
*
|
||||
* These can be used for example to give the browser hints about how to prioritize resources.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class LinkManager
|
||||
{
|
||||
/**
|
||||
* List of relations currently managed by the link manager.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
const MANAGED_RELATIONS = [Attribute::REL_DNS_PREFETCH, Attribute::REL_MODULEPRELOAD, Attribute::REL_PRECONNECT, Attribute::REL_PREFETCH, Attribute::REL_PRELOAD, Attribute::REL_PRERENDER];
|
||||
/**
|
||||
* Document to manage the links for.
|
||||
*
|
||||
* @var Document
|
||||
*/
|
||||
private $document;
|
||||
/**
|
||||
* Reference node to attach the resource hint to.
|
||||
*
|
||||
* @var DOMNode|null
|
||||
*/
|
||||
private $referenceNode;
|
||||
/**
|
||||
* Collection of links already attached to the document.
|
||||
*
|
||||
* The key of the array is a concatenation of the HREF and the REL attributes.
|
||||
*
|
||||
* @var Element[]
|
||||
*/
|
||||
private $links = [];
|
||||
/**
|
||||
* LinkManager constructor.
|
||||
*
|
||||
* @param Document $document Document to manage the links for.
|
||||
*/
|
||||
public function __construct(Document $document)
|
||||
{
|
||||
$this->document = $document;
|
||||
$this->detectExistingLinks();
|
||||
}
|
||||
private function detectExistingLinks()
|
||||
{
|
||||
$node = $this->document->head->firstChild;
|
||||
while ($node) {
|
||||
$nextSibling = $node->nextSibling;
|
||||
if (!$node instanceof Element || $node->tagName !== Tag::LINK) {
|
||||
$node = $nextSibling;
|
||||
continue;
|
||||
}
|
||||
$key = $this->getKey($node);
|
||||
if ($key !== '') {
|
||||
$this->links[$this->getKey($node)] = $node;
|
||||
}
|
||||
$node = $nextSibling;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the key to use for storing the element in the links cache.
|
||||
*
|
||||
* @param Element $element Element to get the key for.
|
||||
* @return string Key to use. Returns an empty string for invalid elements.
|
||||
*/
|
||||
private function getKey(Element $element)
|
||||
{
|
||||
$href = $element->getAttribute(Attribute::HREF);
|
||||
$rel = $element->getAttribute(Attribute::REL);
|
||||
if (empty($href) || !\in_array($rel, self::MANAGED_RELATIONS, \true)) {
|
||||
return '';
|
||||
}
|
||||
return "{$href}{$rel}";
|
||||
}
|
||||
/**
|
||||
* Add a dns-prefetch resource hint.
|
||||
*
|
||||
* @see https://www.w3.org/TR/resource-hints/#dns-prefetch
|
||||
*
|
||||
* @param string $href Origin to prefetch the DNS for.
|
||||
*/
|
||||
public function addDnsPrefetch($href)
|
||||
{
|
||||
$this->add(Attribute::REL_DNS_PREFETCH, $href);
|
||||
}
|
||||
/**
|
||||
* Add a modulepreload declarative fetch primitive.
|
||||
*
|
||||
* @see https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload
|
||||
*
|
||||
* @param string $href Modular resource to preload.
|
||||
* @param string|null $type Optional. Type of the resource. Defaults to not specified, which equals 'script'.
|
||||
* @param bool|string $crossorigin Optional. Whether and how to configure CORS. Accepts a boolean for adding a
|
||||
* boolean crossorigin flag, or a string to set a specific crossorigin strategy.
|
||||
* Allowed values are 'anonymous' and 'use-credentials'. Defaults to true.
|
||||
*/
|
||||
public function addModulePreload($href, $type = null, $crossorigin = \true)
|
||||
{
|
||||
$attributes = [];
|
||||
if ($type !== null) {
|
||||
$attributes = [Attribute::AS_ => $type];
|
||||
}
|
||||
if ($crossorigin !== \false) {
|
||||
$attributes[Attribute::CROSSORIGIN] = \is_string($crossorigin) ? $crossorigin : null;
|
||||
}
|
||||
$this->add(Attribute::REL_MODULEPRELOAD, $href, $attributes);
|
||||
}
|
||||
/**
|
||||
* Add a preconnect resource hint.
|
||||
*
|
||||
* @see https://www.w3.org/TR/resource-hints/#dfn-preconnect
|
||||
*
|
||||
* @param string $href Origin to preconnect to.
|
||||
* @param bool|string $crossorigin Optional. Whether and how to configure CORS. Accepts a boolean for adding a
|
||||
* boolean crossorigin flag, or a string to set a specific crossorigin strategy.
|
||||
* Allowed values are 'anonymous' and 'use-credentials'. Defaults to true.
|
||||
*/
|
||||
public function addPreconnect($href, $crossorigin = \true)
|
||||
{
|
||||
$this->add(Attribute::REL_PRECONNECT, $href, $crossorigin !== \false ? [Attribute::CROSSORIGIN => \is_string($crossorigin) ? $crossorigin : null] : []);
|
||||
// Use dns-prefetch as fallback for browser that don't support preconnect.
|
||||
// See https://web.dev/preconnect-and-dns-prefetch/#resolve-domain-name-early-with-reldns-prefetch.
|
||||
$this->addDnsPrefetch($href);
|
||||
}
|
||||
/**
|
||||
* Add a prefetch resource hint.
|
||||
*
|
||||
* @see https://www.w3.org/TR/resource-hints/#prefetch
|
||||
*
|
||||
* @param string $href URL to the resource to prefetch.
|
||||
* @param string $type Optional. Type of the resource. Defaults to type 'image'.
|
||||
* @param bool|string $crossorigin Optional. Whether and how to configure CORS. Accepts a boolean for adding a
|
||||
* boolean crossorigin flag, or a string to set a specific crossorigin strategy.
|
||||
* Allowed values are 'anonymous' and 'use-credentials'. Defaults to true.
|
||||
*/
|
||||
public function addPrefetch($href, $type = RequestDestination::IMAGE, $crossorigin = \true)
|
||||
{
|
||||
// TODO: Should we enforce a valid $type here?
|
||||
$attributes = [Attribute::AS_ => $type];
|
||||
if ($crossorigin !== \false) {
|
||||
$attributes[Attribute::CROSSORIGIN] = \is_string($crossorigin) ? $crossorigin : null;
|
||||
}
|
||||
$this->add(Attribute::REL_PREFETCH, $href, $attributes);
|
||||
}
|
||||
/**
|
||||
* Add a preload declarative fetch primitive.
|
||||
*
|
||||
* @see https://www.w3.org/TR/preload/
|
||||
*
|
||||
* @param string $href Resource to preload.
|
||||
* @param string $type Optional. Type of the resource. Defaults to type 'image'.
|
||||
* @param string|null $media Optional. Media query to add to the preload. Defaults to none.
|
||||
* @param bool|string $crossorigin Optional. Whether and how to configure CORS. Accepts a boolean for adding a
|
||||
* boolean crossorigin flag, or a string to set a specific crossorigin strategy.
|
||||
* Allowed values are 'anonymous' and 'use-credentials'. Defaults to true.
|
||||
*/
|
||||
public function addPreload($href, $type = RequestDestination::IMAGE, $media = null, $crossorigin = \true)
|
||||
{
|
||||
// TODO: Should we enforce a valid $type here?
|
||||
$attributes = [Attribute::AS_ => $type];
|
||||
if (!empty($media)) {
|
||||
$attributes[Attribute::MEDIA] = $media;
|
||||
}
|
||||
if ($crossorigin !== \false) {
|
||||
$attributes[Attribute::CROSSORIGIN] = \is_string($crossorigin) ? $crossorigin : null;
|
||||
}
|
||||
$this->add(Attribute::REL_PRELOAD, $href, $attributes);
|
||||
}
|
||||
/**
|
||||
* Add a prerender resource hint.
|
||||
*
|
||||
* @see https://www.w3.org/TR/resource-hints/#prerender
|
||||
*
|
||||
* @param string $href URL of the page to prerender.
|
||||
*/
|
||||
public function addPrerender($href)
|
||||
{
|
||||
$this->add(Attribute::REL_PRERENDER, $href);
|
||||
}
|
||||
/**
|
||||
* Add a link to the document.
|
||||
*
|
||||
* @param string $rel A 'rel' string.
|
||||
* @param string $href URL to link to.
|
||||
* @param string[] $attributes Associative array of attributes and their values.
|
||||
*/
|
||||
public function add($rel, $href, $attributes = [])
|
||||
{
|
||||
$link = $this->document->createElement(Tag::LINK);
|
||||
$link->setAttribute(Attribute::REL, $rel);
|
||||
$link->setAttribute(Attribute::HREF, $href);
|
||||
foreach ($attributes as $attribute => $value) {
|
||||
$link->setAttribute($attribute, $value);
|
||||
}
|
||||
$this->remove($rel, $href);
|
||||
if (!isset($this->referenceNode)) {
|
||||
$this->referenceNode = $this->document->viewport;
|
||||
}
|
||||
if ($this->referenceNode) {
|
||||
$link = $this->document->head->insertBefore($link, $this->referenceNode->nextSibling);
|
||||
} else {
|
||||
$link = $this->document->head->appendChild($link);
|
||||
}
|
||||
if (!$link instanceof Element) {
|
||||
throw FailedToCreateLink::forLink($link);
|
||||
}
|
||||
$this->links[$this->getKey($link)] = $link;
|
||||
$this->referenceNode = $link;
|
||||
}
|
||||
/**
|
||||
* Get a specific link from the link manager.
|
||||
*
|
||||
* @param string $rel Relation to fetch.
|
||||
* @param string $href Reference to fetch.
|
||||
* @return Element|null Requested link as a Dom\Element, or null if not found.
|
||||
*/
|
||||
public function get($rel, $href)
|
||||
{
|
||||
$key = "{$href}{$rel}";
|
||||
if (!\array_key_exists($key, $this->links)) {
|
||||
return null;
|
||||
}
|
||||
return $this->links[$key];
|
||||
}
|
||||
/**
|
||||
* Remove a specific link from the document.
|
||||
*
|
||||
* @param string $rel Relation of the link to remove.
|
||||
* @param string $href Reference of the link to remove.
|
||||
*/
|
||||
public function remove($rel, $href)
|
||||
{
|
||||
$key = "{$href}{$rel}";
|
||||
if (!\array_key_exists($key, $this->links)) {
|
||||
return;
|
||||
}
|
||||
$link = $this->links[$key];
|
||||
$link->parentNode->removeChild($link);
|
||||
unset($this->links[$key]);
|
||||
}
|
||||
}
|
51
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Dom/NodeWalker.php
vendored
Normal file
51
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Dom/NodeWalker.php
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom;
|
||||
|
||||
use DOMNode;
|
||||
/**
|
||||
* Walk a hierarchical tree of nodes in a sequential manner.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class NodeWalker
|
||||
{
|
||||
/**
|
||||
* Depth-first walk through the DOM tree.
|
||||
*
|
||||
* @param DOMNode $node Node to start walking from.
|
||||
* @return DOMNode|null Next node, or null if none found.
|
||||
*/
|
||||
public static function nextNode(DOMNode $node)
|
||||
{
|
||||
// Walk downwards if there are children.
|
||||
if ($node->firstChild) {
|
||||
return $node->firstChild;
|
||||
}
|
||||
// Return direct sibling or walk upwards until we find a node with a sibling.
|
||||
while ($node) {
|
||||
if ($node->nextSibling) {
|
||||
return $node->nextSibling;
|
||||
}
|
||||
$node = $node->parentNode;
|
||||
}
|
||||
// Out of nodes, so we're done.
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Skip the subtree that is descending from the provided node.
|
||||
*
|
||||
* @param DOMNode $node Node to skip the subtree of.
|
||||
* @return DOMNode|null The appropriate "next" node that will skip the current subtree, null if none found.
|
||||
*/
|
||||
public static function skipNodeAndChildren(DOMNode $node)
|
||||
{
|
||||
if ($node->nextSibling) {
|
||||
return $node->nextSibling;
|
||||
}
|
||||
if ($node->parentNode === null) {
|
||||
return null;
|
||||
}
|
||||
return self::skipNodeAndChildren($node->parentNode);
|
||||
}
|
||||
}
|
91
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Dom/Options.php
vendored
Normal file
91
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Dom/Options.php
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom;
|
||||
|
||||
use ArrayAccess;
|
||||
/**
|
||||
* Configuration options for DOM document.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class Options implements ArrayAccess
|
||||
{
|
||||
/**
|
||||
* Associative array of options to configure the behavior of the DOM document abstraction.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $options;
|
||||
/**
|
||||
* Creates a new AmpProject\Dom\Options object
|
||||
*
|
||||
* @param array $options Associative array of configuration options.
|
||||
*/
|
||||
public function __construct($options)
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
/**
|
||||
* Sets a value at a specified offset.
|
||||
*
|
||||
* @param string $option The option name.
|
||||
* @param mixed $value Option value.
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($option, $value)
|
||||
{
|
||||
$this->options[$option] = $value;
|
||||
}
|
||||
/**
|
||||
* Determines whether an option exists.
|
||||
*
|
||||
* @param string $option Option name.
|
||||
* @return bool True if the option exists, false otherwise.
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($option)
|
||||
{
|
||||
return isset($this->options[$option]);
|
||||
}
|
||||
/**
|
||||
* Unsets a specified option.
|
||||
*
|
||||
* @param string $option Option name.
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($option)
|
||||
{
|
||||
unset($this->options[$option]);
|
||||
}
|
||||
/**
|
||||
* Retrieves a value at a specified option.
|
||||
*
|
||||
* @param string $option Option name.
|
||||
* @return mixed If set, the value of the option, null otherwise.
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($option)
|
||||
{
|
||||
return isset($this->options[$option]) ? $this->options[$option] : null;
|
||||
}
|
||||
/**
|
||||
* Merge new options with the existing ones.
|
||||
*
|
||||
* @param array $options Associative array of new options.
|
||||
* @return Options Cloned version of the Options object.
|
||||
*/
|
||||
public function merge($options)
|
||||
{
|
||||
$clonedOptions = clone $this;
|
||||
$clonedOptions->options = \array_merge($this->options, $options);
|
||||
return $clonedOptions;
|
||||
}
|
||||
/**
|
||||
* Get the options in associative array.
|
||||
* @return array Associative array of options.
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Dom;
|
||||
|
||||
/**
|
||||
* Unique ID manager.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class UniqueIdManager
|
||||
{
|
||||
/**
|
||||
* Store the current index by prefix.
|
||||
*
|
||||
* This is used to generate unique-per-prefix IDs.
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
private $indexCounter = [];
|
||||
/**
|
||||
* Get auto-incremented ID unique to this class's instantiation.
|
||||
*
|
||||
* @param string $prefix Prefix.
|
||||
* @return string ID.
|
||||
*/
|
||||
public function getUniqueId($prefix = '')
|
||||
{
|
||||
if (\array_key_exists($prefix, $this->indexCounter)) {
|
||||
++$this->indexCounter[$prefix];
|
||||
} else {
|
||||
$this->indexCounter[$prefix] = 0;
|
||||
}
|
||||
$uniqueId = (string) $this->indexCounter[$prefix];
|
||||
if ($prefix) {
|
||||
$uniqueId = "{$prefix}-{$uniqueId}";
|
||||
}
|
||||
return $uniqueId;
|
||||
}
|
||||
}
|
126
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Encoding.php
vendored
Normal file
126
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Encoding.php
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject;
|
||||
|
||||
/**
|
||||
* Encoding constants that are used to control Dom\Document encoding.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class Encoding
|
||||
{
|
||||
/**
|
||||
* UTF-8 encoding, which is the fallback.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const UTF8 = 'utf-8';
|
||||
/**
|
||||
* AMP requires the HTML markup to be encoded in UTF-8.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP = self::UTF8;
|
||||
/**
|
||||
* Encoding detection order in case we have to guess.
|
||||
*
|
||||
* This list of encoding detection order is just a wild guess and might need fine-tuning over time.
|
||||
* If the charset was not provided explicitly, we can really only guess, as the detection can
|
||||
* never be 100% accurate and reliable.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DETECTION_ORDER = 'JIS, UTF-8, EUC-JP, eucJP-win, ISO-2022-JP, ISO-8859-15, ISO-8859-1, ASCII';
|
||||
/**
|
||||
* Encoding detection order for PHP 8.1.
|
||||
*
|
||||
* In PHP 8.1, mb_detect_encoding gives different result than the lower versions. This alternative detection order
|
||||
* list fixes this issue.
|
||||
*/
|
||||
const DETECTION_ORDER_PHP81 = 'UTF-8, EUC-JP, eucJP-win, ISO-8859-15, JIS, ISO-2022-JP, ISO-8859-1, ASCII';
|
||||
/**
|
||||
* Associative array of encoding mappings.
|
||||
*
|
||||
* Translates HTML charsets into encodings PHP can understand.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const MAPPINGS = [
|
||||
// Assume ISO-8859-1 for some charsets.
|
||||
'latin-1' => 'ISO-8859-1',
|
||||
// US-ASCII is one of the most popular ASCII names and used as HTML charset,
|
||||
// but mb_list_encodings contains only "ASCII".
|
||||
'us-ascii' => 'ascii',
|
||||
];
|
||||
/**
|
||||
* Encoding identifier to use for an unknown encoding.
|
||||
*
|
||||
* "auto" is recognized by mb_convert_encoding() as a special value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const UNKNOWN = 'auto';
|
||||
/**
|
||||
* Current value of the encoding.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $encoding;
|
||||
/**
|
||||
* Encoding constructor.
|
||||
*
|
||||
* @param mixed $encoding Value of the encoding.
|
||||
*/
|
||||
public function __construct($encoding)
|
||||
{
|
||||
if (!\is_string($encoding)) {
|
||||
$encoding = self::UNKNOWN;
|
||||
}
|
||||
$this->encoding = $encoding;
|
||||
}
|
||||
/**
|
||||
* Check whether the encoding equals a provided encoding.
|
||||
*
|
||||
* @param Encoding|string $encoding Encoding to check against.
|
||||
* @return bool Whether the encodings are the same.
|
||||
*/
|
||||
public function equals($encoding)
|
||||
{
|
||||
return \strtolower($this->encoding) === \strtolower((string) $encoding);
|
||||
}
|
||||
/**
|
||||
* Sanitize the encoding that was detected.
|
||||
*
|
||||
* If sanitization fails, it will return 'auto', letting the conversion
|
||||
* logic try to figure it out itself.
|
||||
*/
|
||||
public function sanitize()
|
||||
{
|
||||
$this->encoding = \strtolower($this->encoding);
|
||||
if ($this->encoding === self::UTF8) {
|
||||
return;
|
||||
}
|
||||
if (!\function_exists('mb_list_encodings')) {
|
||||
return;
|
||||
}
|
||||
static $knownEncodings = null;
|
||||
if (null === $knownEncodings) {
|
||||
$knownEncodings = \array_map('strtolower', \mb_list_encodings());
|
||||
}
|
||||
if (\array_key_exists($this->encoding, self::MAPPINGS)) {
|
||||
$this->encoding = self::MAPPINGS[$this->encoding];
|
||||
}
|
||||
if (!\in_array($this->encoding, $knownEncodings, \true)) {
|
||||
$this->encoding = self::UNKNOWN;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return the value of the encoding as a string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->encoding;
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
/**
|
||||
* Marker interface to distinguish exceptions for the CLI.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface AmpCliException extends AmpException
|
||||
{
|
||||
/**
|
||||
* No error code specified.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const E_ANY = -1;
|
||||
/**
|
||||
* Command is not valid.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const E_INVALID_CMD = 6;
|
||||
/**
|
||||
* Could not read or parse arguments.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const E_ARG_READ = 5;
|
||||
/**
|
||||
* Option requires an argument.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const E_OPT_ABIGUOUS = 4;
|
||||
/**
|
||||
* Argument not allowed for option.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const E_OPT_ARG_DENIED = 3;
|
||||
/**
|
||||
* Option ambiguous.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const E_OPT_ARG_REQUIRED = 2;
|
||||
/**
|
||||
* Option unknown.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const E_UNKNOWN_OPT = 1;
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
/**
|
||||
* Marker interface to enable consumers to catch all exceptions for this particular library.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface AmpException
|
||||
{
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\AmpCliException;
|
||||
use InvalidArgumentException;
|
||||
/**
|
||||
* Exception thrown when an invalid argument was provided to the CLI.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidArgument extends InvalidArgumentException implements AmpCliException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidArgument exception when arguments could not be read.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function forUnreadableArguments()
|
||||
{
|
||||
$message = 'Could not read command arguments. Is register_argc_argv off?';
|
||||
return new self($message, AmpCliException::E_ARG_READ);
|
||||
}
|
||||
/**
|
||||
* Instantiate an InvalidArgument exception when a short option is too long.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function forMultiCharacterShortOption()
|
||||
{
|
||||
$message = 'Short options should be exactly one ASCII character.';
|
||||
return new self($message, AmpCliException::E_OPT_ARG_DENIED);
|
||||
}
|
||||
/**
|
||||
* Instantiate an InvalidArgument exception for file that could not be read.
|
||||
*
|
||||
* @param string $file File that could not be read.
|
||||
* @return self
|
||||
*/
|
||||
public static function forUnreadableFile($file)
|
||||
{
|
||||
$message = "Could not read file: '{$file}'.";
|
||||
return new self($message, AmpCliException::E_OPT_ARG_DENIED);
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\AmpCliException;
|
||||
use OutOfBoundsException;
|
||||
/**
|
||||
* Exception thrown when an invalid color was provided to the CLI.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidColor extends OutOfBoundsException implements AmpCliException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidColor exception for an unknown color that was passed to the CLI.
|
||||
*
|
||||
* @param string $color Unknown color that was passed to the CLI.
|
||||
* @return self
|
||||
*/
|
||||
public static function forUnknownColor($color)
|
||||
{
|
||||
$message = "Unknown color: '{$color}'.";
|
||||
return new self($message, AmpCliException::E_ANY);
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\AmpCliException;
|
||||
use OutOfBoundsException;
|
||||
/**
|
||||
* Exception thrown when an invalid option was provided to the CLI.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidColumnFormat extends OutOfBoundsException implements AmpCliException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidColumn exception for multiple fluid columns.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function forMultipleFluidColumns()
|
||||
{
|
||||
$message = 'Only one fluid column allowed.';
|
||||
return new self($message, AmpCliException::E_ANY);
|
||||
}
|
||||
/**
|
||||
* Instantiate an InvalidColumn exception for an unknown column format.
|
||||
*
|
||||
* @param string $column Unknown column format.
|
||||
* @return self
|
||||
*/
|
||||
public static function forUnknownColumnFormat($column)
|
||||
{
|
||||
$message = "Unknown column format: '{$column}'.";
|
||||
return new self($message, AmpCliException::E_ANY);
|
||||
}
|
||||
/**
|
||||
* Instantiate an InvalidColumn exception for an unknown column format.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function forExceededMaxWidth()
|
||||
{
|
||||
$message = 'Total of requested column widths exceeds available space.';
|
||||
return new self($message, AmpCliException::E_ANY);
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\AmpCliException;
|
||||
use InvalidArgumentException;
|
||||
/**
|
||||
* Exception thrown when an invalid command was provided to the CLI.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidCommand extends InvalidArgumentException implements AmpCliException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidCommand exception for an unregistered command that is being referenced.
|
||||
*
|
||||
* @param string $command Unregistered command that is being referenced.
|
||||
* @return self
|
||||
*/
|
||||
public static function forUnregisteredCommand($command)
|
||||
{
|
||||
$message = "Command not registered: '{$command}'.";
|
||||
return new self($message, AmpCliException::E_INVALID_CMD);
|
||||
}
|
||||
/**
|
||||
* Instantiate an InvalidCommand exception for an already registered command that is to be re-registered.
|
||||
*
|
||||
* @param string $command Already registered command that is supposed to be registered.
|
||||
* @return self
|
||||
*/
|
||||
public static function forAlreadyRegisteredCommand($command)
|
||||
{
|
||||
$message = "Command already registered: '{$command}'.";
|
||||
return new self($message, AmpCliException::E_INVALID_CMD);
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\AmpCliException;
|
||||
use OutOfBoundsException;
|
||||
/**
|
||||
* Exception thrown when an invalid option was provided to the CLI.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidOption extends OutOfBoundsException implements AmpCliException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidOption exception for an unknown option that was passed to the CLI.
|
||||
*
|
||||
* @param string $option Unknown option that was passed to the CLI.
|
||||
* @return self
|
||||
*/
|
||||
public static function forUnknownOption($option)
|
||||
{
|
||||
$message = "Unknown option: '{$option}'.";
|
||||
return new self($message, AmpCliException::E_UNKNOWN_OPT);
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\AmpCliException;
|
||||
use OutOfBoundsException;
|
||||
/**
|
||||
* Exception thrown when an invalid option was provided to the CLI.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidSapi extends OutOfBoundsException implements AmpCliException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidSapi exception for a SAPI other than 'cli'.
|
||||
*
|
||||
* @param string $sapi Invalid SAPI that was detected.
|
||||
* @return self
|
||||
*/
|
||||
public static function forSapi($sapi)
|
||||
{
|
||||
$message = "This has to be run from the command line (detected SAPI '{$sapi}').";
|
||||
return new self($message, AmpCliException::E_ANY);
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception\Cli;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\AmpCliException;
|
||||
use DomainException;
|
||||
/**
|
||||
* Exception thrown when an invalid argument was provided to the CLI.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class MissingArgument extends DomainException implements AmpCliException
|
||||
{
|
||||
/**
|
||||
* Instantiate a MissingArgument exception for an argument that is required but missing.
|
||||
*
|
||||
* @param string $option Option for which the argument is missing.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function forNoArgument($option)
|
||||
{
|
||||
$message = "Option '{$option}' requires an argument.";
|
||||
return new self($message, AmpCliException::E_OPT_ARG_REQUIRED);
|
||||
}
|
||||
/**
|
||||
* Instantiate a MissingArgument exception for when too few arguments were passed.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function forNotEnoughArguments()
|
||||
{
|
||||
$message = 'Not enough arguments provided.';
|
||||
return new self($message, AmpCliException::E_OPT_ARG_REQUIRED);
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
/**
|
||||
* Marker interface to enable consumers to catch all exceptions for failed remote requests.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface FailedRemoteRequest extends AmpException
|
||||
{
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
/**
|
||||
* Exception thrown when a link could not be created.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class FailedToCreateLink extends RuntimeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate a FailedToCreateLink exception for a link that could not be created.
|
||||
*
|
||||
* @param mixed $link Link that was not as expected.
|
||||
* @return self
|
||||
*/
|
||||
public static function forLink($link)
|
||||
{
|
||||
$type = \is_object($link) ? \get_class($link) : \gettype($link);
|
||||
$message = "Failed to create a link via the link manager. " . "Expected to produce an 'AmpProject\\Dom\\Element', got '{$type}' instead.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
/**
|
||||
* Exception thrown when a cached remote response could not be retrieved.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class FailedToGetCachedResponse extends RuntimeException implements FailedRemoteRequest
|
||||
{
|
||||
/**
|
||||
* Instantiate a FailedToGetCachedResponse exception for a URL if the cached response data could not be
|
||||
* retrieved.
|
||||
*
|
||||
* @param string $url URL that failed to be fetched.
|
||||
* @return self
|
||||
*/
|
||||
public static function withUrl($url)
|
||||
{
|
||||
$message = "Failed to retrieve the cached response for the URL '{$url}'.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
/**
|
||||
* Exception thrown when a remote request failed.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class FailedToGetFromRemoteUrl extends RuntimeException implements FailedRemoteRequest
|
||||
{
|
||||
/**
|
||||
* Status code of the failed request.
|
||||
*
|
||||
* This is not always set.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private $statusCode;
|
||||
/**
|
||||
* Instantiate a FailedToGetFromRemoteUrl exception for a URL if an HTTP status code is available.
|
||||
*
|
||||
* @param string $url URL that failed to be fetched.
|
||||
* @param int $status HTTP Status that was returned.
|
||||
* @return self
|
||||
*/
|
||||
public static function withHttpStatus($url, $status)
|
||||
{
|
||||
$message = "Failed to fetch the contents from the URL '{$url}' as it returned HTTP status {$status}.";
|
||||
$exception = new self($message);
|
||||
$exception->statusCode = $status;
|
||||
return $exception;
|
||||
}
|
||||
/**
|
||||
* Instantiate a FailedToGetFromRemoteUrl exception for a URL if an HTTP status code is not available.
|
||||
*
|
||||
* @param string $url URL that failed to be fetched.
|
||||
* @return self
|
||||
*/
|
||||
public static function withoutHttpStatus($url)
|
||||
{
|
||||
$message = "Failed to fetch the contents from the URL '{$url}'.";
|
||||
return new self($message);
|
||||
}
|
||||
/**
|
||||
* Instantiate a FailedToGetFromRemoteUrl exception for a URL if an exception was thrown.
|
||||
*
|
||||
* @param string $url URL that failed to be fetched.
|
||||
* @param Exception $exception Exception that was thrown.
|
||||
* @return self
|
||||
*/
|
||||
public static function withException($url, Exception $exception)
|
||||
{
|
||||
$message = "Failed to fetch the contents from the URL '{$url}': {$exception->getMessage()}.";
|
||||
return new self($message, 0, $exception);
|
||||
}
|
||||
/**
|
||||
* Check whether the status code is set for this exception.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasStatusCode()
|
||||
{
|
||||
return isset($this->statusCode);
|
||||
}
|
||||
/**
|
||||
* Get the HTTP status code associated with this exception.
|
||||
*
|
||||
* Returns -1 if no status code was provided.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStatusCode()
|
||||
{
|
||||
return $this->hasStatusCode() ? $this->statusCode : -1;
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Str;
|
||||
use InvalidArgumentException;
|
||||
/**
|
||||
* Exception thrown when an HTML document could not be parsed.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class FailedToParseHtml extends InvalidArgumentException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate a FailedToParseHtml exception for a HTML that could not be parsed.
|
||||
*
|
||||
* @param string $html HTML that failed to be parsed.
|
||||
* @return self
|
||||
*/
|
||||
public static function forHtml($html)
|
||||
{
|
||||
if (Str::length($html) > 80) {
|
||||
$html = Str::substring($html, 0, 77) . '...';
|
||||
}
|
||||
$message = "Failed to parse the provided HTML document ({$html}).";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use InvalidArgumentException;
|
||||
/**
|
||||
* Exception thrown when a URL could not be parsed.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class FailedToParseUrl extends InvalidArgumentException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate a FailedToParseUrl exception for a URL that could not be parsed.
|
||||
*
|
||||
* @param string $url URL that failed to be parsed.
|
||||
* @return self
|
||||
*/
|
||||
public static function forUrl($url)
|
||||
{
|
||||
$message = "Failed to parse the URL '{$url}'.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use LogicException;
|
||||
/**
|
||||
* Exception thrown when a required DOM element could not be retrieved from the document.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class FailedToRetrieveRequiredDomElement extends LogicException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate a FailedToRetrieveRequiredDomElement exception for the <html> DOM element.
|
||||
*
|
||||
* @param mixed $retrievedElement What was returned when trying to retrieve the element.
|
||||
* @return FailedToRetrieveRequiredDomElement
|
||||
*/
|
||||
public static function forHtmlElement($retrievedElement)
|
||||
{
|
||||
$type = \is_object($retrievedElement) ? \get_class($retrievedElement) : \gettype($retrievedElement);
|
||||
$message = "Failed to retrieve required <html> DOM element, got '{$type}' instead.";
|
||||
return new self($message);
|
||||
}
|
||||
/**
|
||||
* Instantiate a FailedToRetrieveRequiredDomElement exception for the <head> DOM element.
|
||||
*
|
||||
* @param mixed $retrievedElement What was returned when trying to retrieve the element.
|
||||
* @return FailedToRetrieveRequiredDomElement
|
||||
*/
|
||||
public static function forHeadElement($retrievedElement)
|
||||
{
|
||||
$type = \is_object($retrievedElement) ? \get_class($retrievedElement) : \gettype($retrievedElement);
|
||||
$message = "Failed to retrieve required <head> DOM element, got '{$type}' instead.";
|
||||
return new self($message);
|
||||
}
|
||||
/**
|
||||
* Instantiate a FailedToRetrieveRequiredDomElement exception for the <body> DOM element.
|
||||
*
|
||||
* @param mixed $retrievedElement What was returned when trying to retrieve the element.
|
||||
* @return FailedToRetrieveRequiredDomElement
|
||||
*/
|
||||
public static function forBodyElement($retrievedElement)
|
||||
{
|
||||
$type = \is_object($retrievedElement) ? \get_class($retrievedElement) : \gettype($retrievedElement);
|
||||
$message = "Failed to retrieve required <body> DOM element, got '{$type}' instead.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use OutOfRangeException;
|
||||
/**
|
||||
* Exception thrown when an invalid attribute name is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidAttributeName extends OutOfRangeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidAttributeName exception for an attribute that is not found within name index.
|
||||
*
|
||||
* @param string $attribute Name of the attribute that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forAttribute($attribute)
|
||||
{
|
||||
$message = "Invalid attribute '{$attribute}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use InvalidArgumentException;
|
||||
/**
|
||||
* Exception thrown when a HTML contains invalid byte sequences.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidByteSequence extends InvalidArgumentException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate a InvalidByteSequence exception for a HTML with invalid byte sequences.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function forHtml()
|
||||
{
|
||||
$message = 'Provided HTML contains invalid byte sequences. ' . 'This is usually fixed by replacing string manipulation functions ' . 'with their `mb_*` multibyte counterparts.';
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use OutOfRangeException;
|
||||
/**
|
||||
* Exception thrown when an invalid CSS ruleset name is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidCssRulesetName extends OutOfRangeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidCssRulesetName exception for a CSS ruleset that is not found within the CSS rulesets index.
|
||||
*
|
||||
* @param string $cssRulesetName CSS ruleset name that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forCssRulesetName($cssRulesetName)
|
||||
{
|
||||
$message = "Invalid CSS ruleset name '{$cssRulesetName}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use OutOfRangeException;
|
||||
/**
|
||||
* Exception thrown when an invalid declaration name is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidDeclarationName extends OutOfRangeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidDeclarationName exception for an declaration that is not found within name index.
|
||||
*
|
||||
* @param string $declaration Name of the declaration that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forDeclaration($declaration)
|
||||
{
|
||||
$message = "Invalid declaration '{$declaration}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use OutOfRangeException;
|
||||
/**
|
||||
* Exception thrown when an invalid document ruleset name is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidDocRulesetName extends OutOfRangeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidDocRulesetName exception for a document ruleset that is not found within the document
|
||||
* rulesets index.
|
||||
*
|
||||
* @param string $docRulesetName document ruleset name that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forDocRulesetName($docRulesetName)
|
||||
{
|
||||
$message = "Invalid document ruleset name '{$docRulesetName}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use InvalidArgumentException;
|
||||
/**
|
||||
* Exception thrown when an invalid tag ID is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidDocumentFilter extends InvalidArgumentException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidDocumentFilter exception for a class that was not a valid filter.
|
||||
*
|
||||
* @param mixed $filter Filter that was registered.
|
||||
* @return self
|
||||
*/
|
||||
public static function forFilter($filter)
|
||||
{
|
||||
$type = \is_object($filter) ? \get_class($filter) : \gettype($filter);
|
||||
$message = \is_string($filter) ? "Invalid document filter '{$filter}' was registered with the AmpProject\\Dom\\Document class." : "Invalid document filter of type '{$type}' was registered with the AmpProject\\Dom\\Document class, '\n . 'expected AmpProject\\Dom\\Document\\Filter.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use OutOfRangeException;
|
||||
/**
|
||||
* Exception thrown when an invalid error code is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidErrorCode extends OutOfRangeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidErrorCode exception for an unknown error code.
|
||||
*
|
||||
* @param string $errorCode Error code that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forErrorCode($errorCode)
|
||||
{
|
||||
$message = "Invalid error code '{$errorCode}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use OutOfRangeException;
|
||||
/**
|
||||
* Exception thrown when an invalid extension is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidExtension extends OutOfRangeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidExtension exception for an extension that is not found within the extension spec index.
|
||||
*
|
||||
* @param string $extension Spec name that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forExtension($extension)
|
||||
{
|
||||
$message = "Invalid extension '{$extension}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use OutOfRangeException;
|
||||
/**
|
||||
* Exception thrown when an invalid format is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidFormat extends OutOfRangeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidFormat exception when an invalid AMP format is being requested.
|
||||
*
|
||||
* @param string $format Format that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forFormat($format)
|
||||
{
|
||||
$message = "Invalid format '{$format}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use OutOfRangeException;
|
||||
/**
|
||||
* Exception thrown when an invalid list name is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidListName extends OutOfRangeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidListName exception for a attribute that is not found within the attribute
|
||||
* list name index.
|
||||
*
|
||||
* @param string $attributeList Name of the attribute list that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forAttributeList($attributeList)
|
||||
{
|
||||
$message = "Invalid attribute list '{$attributeList}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
/**
|
||||
* Instantiate an InvalidListName exception for a declaration that is not found within the declaration
|
||||
* list name index.
|
||||
*
|
||||
* @param string $declarationList Name of the declaration list that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forDeclarationList($declarationList)
|
||||
{
|
||||
$message = "Invalid declaration list '{$declarationList}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
/**
|
||||
* Instantiate an InvalidListName exception for a descendant tag that is not found within the descendant tag
|
||||
* list name index.
|
||||
*
|
||||
* @param string $descendantTagList Name of the descendant tag list that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forDescendantTagList($descendantTagList)
|
||||
{
|
||||
$message = "Invalid descendant tag list '{$descendantTagList}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use DomainException;
|
||||
/**
|
||||
* Exception thrown when an invalid option value was provided.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidOptionValue extends DomainException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidOptionValue exception for an invalid option value.
|
||||
*
|
||||
* @param string $option Name of the option.
|
||||
* @param array<string> $accepted Array of acceptable values.
|
||||
* @param string $actual Value that was actually provided.
|
||||
* @return self
|
||||
*/
|
||||
public static function forValue($option, $accepted, $actual)
|
||||
{
|
||||
$acceptedString = \implode(', ', $accepted);
|
||||
$message = "The value for the option '{$option}' expected the value to be one of " . "[{$acceptedString}], got '{$actual}' instead.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use OutOfRangeException;
|
||||
/**
|
||||
* Exception thrown when an invalid spec name is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidSpecName extends OutOfRangeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidSpecName exception for a spec that is not found within the spec name index.
|
||||
*
|
||||
* @param string $specName Spec name that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forSpecName($specName)
|
||||
{
|
||||
$message = "Invalid spec name '{$specName}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use OutOfRangeException;
|
||||
/**
|
||||
* Exception thrown when an invalid spec rule name is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidSpecRuleName extends OutOfRangeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidSpecRuleName exception for a spec rule that is not found within the spec index.
|
||||
*
|
||||
* @param string $specRuleName Spec rule name that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forSpecRuleName($specRuleName)
|
||||
{
|
||||
$message = "Invalid spec rule name '{$specRuleName}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use OutOfRangeException;
|
||||
/**
|
||||
* Exception thrown when an invalid tag ID is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidTagId extends OutOfRangeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidTagId exception for a tag that is not found within the tag name index.
|
||||
*
|
||||
* @param string $tagId Spec name that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forTagId($tagId)
|
||||
{
|
||||
$message = "Invalid tag ID '{$tagId}' was requested from the validator tag.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use OutOfRangeException;
|
||||
/**
|
||||
* Exception thrown when an invalid tag name is requested from the validator spec.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class InvalidTagName extends OutOfRangeException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate an InvalidTagName exception for a tag that is not found within the tag name index.
|
||||
*
|
||||
* @param string $tagName Tag name that was requested.
|
||||
* @return self
|
||||
*/
|
||||
public static function forTagName($tagName)
|
||||
{
|
||||
$message = "Invalid tag name '{$tagName}' was requested from the validator spec.";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Exception;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\Element;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Dom\ElementDump;
|
||||
use OverflowException;
|
||||
/**
|
||||
* Exception thrown when the maximum CSS byte count has been exceeded.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class MaxCssByteCountExceeded extends OverflowException implements AmpException
|
||||
{
|
||||
/**
|
||||
* Instantiate a MaxCssByteCountExceeded exception for an inline style that exceeds the maximum byte count.
|
||||
*
|
||||
* @param Element $element Element that was supposed to receive the inline style.
|
||||
* @param string $style Inline style that was supposed to be added.
|
||||
* @return self
|
||||
*/
|
||||
public static function forInlineStyle(Element $element, $style)
|
||||
{
|
||||
$message = "Maximum allowed CSS byte count exceeded for inline style '{$style}': " . new ElementDump($element);
|
||||
return new self($message);
|
||||
}
|
||||
/**
|
||||
* Instantiate a MaxCssByteCountExceeded exception for an amp-custom style that exceeds the maximum byte count.
|
||||
*
|
||||
* @param string $style Amp-custom style that was supposed to be added.
|
||||
* @return self
|
||||
*/
|
||||
public static function forAmpCustom($style)
|
||||
{
|
||||
$message = "Maximum allowed CSS byte count exceeded for amp-custom style '{$style}'";
|
||||
return new self($message);
|
||||
}
|
||||
}
|
191
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Extension.php
vendored
Normal file
191
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Extension.php
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject;
|
||||
|
||||
/**
|
||||
* Interface with constants for AMP extensions.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface Extension
|
||||
{
|
||||
const ACCESS = 'amp-access';
|
||||
const ACCESS_LATERPAY = 'amp-access-laterpay';
|
||||
const ACCESS_POOOL = 'amp-access-poool';
|
||||
const ACCESS_SCROLL = 'amp-access-scroll';
|
||||
const ACCORDION = 'amp-accordion';
|
||||
const ACTION_MACRO = 'amp-action-macro';
|
||||
const AD = 'amp-ad';
|
||||
const ADDTHIS = 'amp-addthis';
|
||||
const AD_CUSTOM = 'amp-ad-custom';
|
||||
const AD_EXIT = 'amp-ad-exit';
|
||||
const ANALYTICS = 'amp-analytics';
|
||||
const ANIM = 'amp-anim';
|
||||
const ANIMATION = 'amp-animation';
|
||||
const APESTER_MEDIA = 'amp-apester-media';
|
||||
const APP_BANNER = 'amp-app-banner';
|
||||
const AUDIO = 'amp-audio';
|
||||
const AUTOCOMPLETE = 'amp-autocomplete';
|
||||
const AUTO_ADS = 'amp-auto-ads';
|
||||
const BASE_CAROUSEL = 'amp-base-carousel';
|
||||
const BEOPINION = 'amp-beopinion';
|
||||
const BIND = 'amp-bind';
|
||||
const BIND_MACRO = 'amp-bind-macro';
|
||||
const BODYMOVIN_ANIMATION = 'amp-bodymovin-animation';
|
||||
const BRID_PLAYER = 'amp-brid-player';
|
||||
const BRIGHTCOVE = 'amp-brightcove';
|
||||
const BYSIDE_CONTENT = 'amp-byside-content';
|
||||
const CACHE_URL = 'amp-cache-url';
|
||||
const CALL_TRACKING = 'amp-call-tracking';
|
||||
const CAROUSEL = 'amp-carousel';
|
||||
const CONNATIX_PLAYER = 'amp-connatix-player';
|
||||
const CONSENT = 'amp-consent';
|
||||
const DAILYMOTION = 'amp-dailymotion';
|
||||
const DATE_COUNTDOWN = 'amp-date-countdown';
|
||||
const DATE_DISPLAY = 'amp-date-display';
|
||||
const DATE_PICKER = 'amp-date-picker';
|
||||
const DELIGHT_PLAYER = 'amp-delight-player';
|
||||
const DYNAMIC_CSS_CLASSES = 'amp-dynamic-css-classes';
|
||||
const EMBED = 'amp-embed';
|
||||
const EMBEDLY_CARD = 'amp-embedly-card';
|
||||
const EMBEDLY_KEY = 'amp-embedly-key';
|
||||
const EXPERIMENT = 'amp-experiment';
|
||||
const FACEBOOK = 'amp-facebook';
|
||||
const FACEBOOK_COMMENTS = 'amp-facebook-comments';
|
||||
const FACEBOOK_LIKE = 'amp-facebook-like';
|
||||
const FACEBOOK_PAGE = 'amp-facebook-page';
|
||||
const FIT_TEXT = 'amp-fit-text';
|
||||
const FONT = 'amp-font';
|
||||
const FORM = 'amp-form';
|
||||
const FX_COLLECTION = 'amp-fx-collection';
|
||||
const FX_FLYING_CARPET = 'amp-flying-carpet';
|
||||
const GEO = 'amp-geo';
|
||||
const GFYCAT = 'amp-gfycat';
|
||||
const GIST = 'amp-gist';
|
||||
const GOOGLE_ASSISTANT_ASSISTJS = 'amp-google-assistant-assistjs';
|
||||
const GOOGLE_ASSISTANT_ASSISTJS_CONFIG = 'amp-google-assistant-assistjs-config';
|
||||
const GOOGLE_ASSISTANT_INLINE_SUGGESTION_BAR = 'amp-google-assistant-inline-suggestion-bar';
|
||||
const GOOGLE_ASSISTANT_VOICE_BAR = 'amp-google-assistant-voice-bar';
|
||||
const GOOGLE_ASSISTANT_VOICE_BUTTON = 'amp-google-assistant-voice-button';
|
||||
const GOOGLE_DOCUMENT_EMBED = 'amp-google-document-embed';
|
||||
const GOOGLE_READ_ALOUD_PLAYER = 'amp-google-read-aloud-player';
|
||||
const GWD_ANIMATION = 'amp-gwd-animation';
|
||||
const HULU = 'amp-hulu';
|
||||
const IFRAME = 'amp-iframe';
|
||||
const IFRAMELY = 'amp-iframely';
|
||||
const IMAGE_LIGHTBOX = 'amp-image-lightbox';
|
||||
const IMAGE_SLIDER = 'amp-image-slider';
|
||||
const IMA_VIDEO = 'amp-ima-video';
|
||||
const IMG = 'amp-img';
|
||||
const IMGUR = 'amp-imgur';
|
||||
const INPUTMASK = 'amp-inputmask';
|
||||
const INLINE_GALLERY = 'amp-inline-gallery';
|
||||
const INLINE_GALLERY_PAGINATION = 'amp-inline-gallery-pagination';
|
||||
const INLINE_GALLERY_THUMBNAILS = 'amp-inline-gallery-thumbnails';
|
||||
const INSTAGRAM = 'amp-instagram';
|
||||
const INSTALL_SERVICEWORKER = 'amp-install-serviceworker';
|
||||
const IZLESENE = 'amp-izlesene';
|
||||
const JWPLAYER = 'amp-jwplayer';
|
||||
const KALTURA_PLAYER = 'amp-kaltura-player';
|
||||
const LAYOUT = 'amp-layout';
|
||||
const LIGHTBOX = 'amp-lightbox';
|
||||
const LIGHTBOX_GALLERY = 'amp-lightbox-gallery';
|
||||
const LINK_REWRITER = 'amp-link-rewriter';
|
||||
const LIST_ = 'amp-list';
|
||||
const LIST_LOAD_MORE = 'amp-list-load-more';
|
||||
const LIVE_LIST = 'amp-live-list';
|
||||
const MATHML = 'amp-mathml';
|
||||
const MEGAPHONE = 'amp-megaphone';
|
||||
const MEGA_MENU = 'amp-mega-menu';
|
||||
const MINUTE_MEDIA_PLAYER = 'amp-minute-media-player';
|
||||
const MOWPLAYER = 'amp-mowplayer';
|
||||
const MRAID = 'amp-mraid';
|
||||
const MUSTACHE = 'amp-mustache';
|
||||
const NESTED_MENU = 'amp-nested-menu';
|
||||
const NEXT_PAGE = 'amp-next-page';
|
||||
const NEXXTV_PLAYER = 'amp-nexxtv-player';
|
||||
const O2_PLAYER = 'amp-o2-player';
|
||||
const ONETAP_GOOGLE = 'amp-onetap-google';
|
||||
const OOYALA_PLAYER = 'amp-ooyala-player';
|
||||
const ORIENTATION_OBSERVER = 'amp-orientation-observer';
|
||||
const PAN_ZOOM = 'amp-pan-zoom';
|
||||
const PINTEREST = 'amp-pinterest';
|
||||
const PIXEL = 'amp-pixel';
|
||||
const PLAYBUZZ = 'amp-playbuzz';
|
||||
const POSITION_OBSERVER = 'amp-position-observer';
|
||||
const POWR_PLAYER = 'amp-powr-player';
|
||||
const REACH_PLAYER = 'amp-reach-player';
|
||||
const RECAPTCHA_INPUT = 'amp-recaptcha-input';
|
||||
const REDBULL_PLAYER = 'amp-redbull-player';
|
||||
const REDDIT = 'amp-reddit';
|
||||
const RENDER = 'amp-render';
|
||||
const RIDDLE_QUIZ = 'amp-riddle-quiz';
|
||||
const SCRIPT = 'amp-script';
|
||||
const SELECTOR = 'amp-selector';
|
||||
const SIDEBAR = 'amp-sidebar';
|
||||
const SKIMLINKS = 'amp-skimlinks';
|
||||
const SLIDES = 'amp-slides';
|
||||
const SMARTLINKS = 'amp-smartlinks';
|
||||
const SOCIAL_SHARE = 'amp-social-share';
|
||||
const SOUNDCLOUD = 'amp-soundcloud';
|
||||
const SPRINGBOARD_PLAYER = 'amp-springboard-player';
|
||||
const STATE = 'amp-state';
|
||||
const STICKY_AD = 'amp-sticky-ad';
|
||||
const STORY = 'amp-story';
|
||||
const STORY_360 = 'amp-story-360';
|
||||
const STORY_ANIMATION = 'amp-story-animation';
|
||||
const STORY_AUTO_ADS = 'amp-story-auto-ads';
|
||||
const STORY_AUTO_ANALYTICS = 'amp-story-auto-analytics';
|
||||
const STORY_BOOKEND = 'amp-story-bookend';
|
||||
const STORY_CAPTIONS = 'amp-story-captions';
|
||||
const STORY_CONSENT = 'amp-story-consent';
|
||||
const STORY_CTA_LAYER = 'amp-story-cta-layer';
|
||||
const STORY_GRID_LAYER = 'amp-story-grid-layer';
|
||||
const STORY_INTERACTIVE = 'amp-story-interactive';
|
||||
const STORY_INTERACTIVE_BINARY_POLL = 'amp-story-interactive-binary-poll';
|
||||
const STORY_INTERACTIVE_IMG_POLL = 'amp-story-interactive-img-poll';
|
||||
const STORY_INTERACTIVE_IMG_QUIZ = 'amp-story-interactive-img-quiz';
|
||||
const STORY_INTERACTIVE_POLL = 'amp-story-interactive-poll';
|
||||
const STORY_INTERACTIVE_QUIZ = 'amp-story-interactive-quiz';
|
||||
const STORY_INTERACTIVE_RESULTS = 'amp-story-interactive-results';
|
||||
const STORY_PAGE = 'amp-story-page';
|
||||
const STORY_PAGE_ATTACHMENT = 'amp-story-page-attachment';
|
||||
const STORY_PAGE_OUTLINK = 'amp-story-page-outlink';
|
||||
const STORY_PANNING_MEDIA = 'amp-story-panning-media';
|
||||
const STORY_PLAYER = 'amp-story-player';
|
||||
const STORY_SHOPPING = 'amp-story-shopping';
|
||||
const STORY_SHOPPING_ATTACHMENT = 'amp-story-shopping-attachment';
|
||||
const STORY_SHOPPING_CONFIG = 'amp-story-shopping-config';
|
||||
const STORY_SHOPPING_TAG = 'amp-story-shopping-tag';
|
||||
const STORY_SOCIAL_SHARE = 'amp-story-social-share';
|
||||
const STORY_SUBSCRIPTIONS = 'amp-story-subscriptions';
|
||||
const STREAM_GALLERY = 'amp-stream-gallery';
|
||||
const SUBSCRIPTIONS = 'amp-subscriptions';
|
||||
const SUBSCRIPTIONS_GOOGLE = 'amp-subscriptions-google';
|
||||
const TIKTOK = 'amp-tiktok';
|
||||
const TIMEAGO = 'amp-timeago';
|
||||
const TRUNCATE_TEXT = 'amp-truncate-text';
|
||||
const TWITTER = 'amp-twitter';
|
||||
const USER_NOTIFICATION = 'amp-user-notification';
|
||||
const VIDEO = 'amp-video';
|
||||
const VIDEO_DOCKING = 'amp-video-docking';
|
||||
const VIDEO_IFRAME = 'amp-video-iframe';
|
||||
const VIMEO = 'amp-vimeo';
|
||||
const VINE = 'amp-vine';
|
||||
const VIQEO_PLAYER = 'amp-viqeo-player';
|
||||
const VK = 'amp-vk';
|
||||
const WEB_PUSH = 'amp-web-push';
|
||||
const WEB_PUSH_WIDGET = 'amp-web-push-widget';
|
||||
const WISTIA_PLAYER = 'amp-wistia-player';
|
||||
const WORDPRESS_EMBED = 'amp-wordpress-embed';
|
||||
const YOTPO = 'amp-yotpo';
|
||||
const YOUTUBE = 'amp-youtube';
|
||||
const _3D_GLTF = 'amp-3d-gltf';
|
||||
const _3Q_PLAYER = 'amp-3q-player';
|
||||
/**
|
||||
* Prefix of an AMP extension.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PREFIX = 'amp-';
|
||||
}
|
306
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/FakeEnum.php
vendored
Normal file
306
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/FakeEnum.php
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file was copied from the myclabs/php-enum package, and only adapted for matching this package's namespace, code
|
||||
* style and minimum PHP requirement.
|
||||
*
|
||||
* Note: The base class was renamed from Enum to FakeEnum to avoid conflicts with PHP 8.1's enum language construct.
|
||||
*
|
||||
* @link http://github.com/myclabs/php-enum
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT
|
||||
*/
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject;
|
||||
|
||||
use BadMethodCallException;
|
||||
use JsonSerializable;
|
||||
use ReflectionClass;
|
||||
use UnexpectedValueException;
|
||||
/**
|
||||
* Base FakeEnum class.
|
||||
*
|
||||
* Create an enum by implementing this class and adding class constants.
|
||||
*
|
||||
* Original code found in myclabs/php-enum.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
* @author Daniel Costa <danielcosta@gmail.com>
|
||||
* @author Mirosław Filip <mirfilip@gmail.com>
|
||||
*
|
||||
* @psalm-template T
|
||||
* @psalm-immutable
|
||||
* @psalm-consistent-constructor
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
abstract class FakeEnum implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Enum value.
|
||||
*
|
||||
* @var mixed
|
||||
* @psalm-var T
|
||||
*/
|
||||
protected $value;
|
||||
/**
|
||||
* Enum key, the constant name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $key;
|
||||
/**
|
||||
* Store existing constants in a static cache per object.
|
||||
*
|
||||
* @var array
|
||||
* @psalm-var array<class-string, array<string, mixed>>
|
||||
*/
|
||||
protected static $cache = [];
|
||||
/**
|
||||
* Cache of instances of the FakeEnum class.
|
||||
*
|
||||
* @var array
|
||||
* @psalm-var array<class-string, array<string, static>>
|
||||
*/
|
||||
protected static $instances = [];
|
||||
/**
|
||||
* Creates a new value of some type.
|
||||
*
|
||||
* @psalm-pure
|
||||
* @param mixed $value Value to create the new enum instance for.
|
||||
*
|
||||
* @psalm-param T $value
|
||||
* @throws UnexpectedValueException If incompatible type is given.
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
if ($value instanceof static) {
|
||||
/** @psalm-var T */
|
||||
$value = $value->getValue();
|
||||
}
|
||||
/** @psalm-suppress ImplicitToStringCast assertValidValueReturningKey returns always a string but psalm has currently an issue here */
|
||||
$this->key = static::assertValidValueReturningKey($value);
|
||||
/** @psalm-var T $value */
|
||||
$this->value = $value;
|
||||
}
|
||||
/**
|
||||
* This method exists only for the compatibility reason when deserializing a previously serialized version
|
||||
* that didn't have the key property.
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
/** @psalm-suppress DocblockTypeContradiction key can be null when deserializing an enum without the key */
|
||||
if ($this->key === null) {
|
||||
/**
|
||||
* @psalm-suppress InaccessibleProperty key is not readonly as marked by psalm
|
||||
* @psalm-suppress PossiblyFalsePropertyAssignmentValue deserializing a case that was removed
|
||||
*/
|
||||
$this->key = static::search($this->value);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create a new enum instance from a value.
|
||||
*
|
||||
* @param mixed $value Value to create the new enum instance for.
|
||||
* @return static
|
||||
* @psalm-return static<T>
|
||||
* @throws UnexpectedValueException If the value is not part of the enum.
|
||||
*/
|
||||
public static function from($value)
|
||||
{
|
||||
$key = static::assertValidValueReturningKey($value);
|
||||
return self::__callStatic($key, []);
|
||||
}
|
||||
/**
|
||||
* @psalm-pure
|
||||
* @return mixed
|
||||
* @psalm-return T
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
/**
|
||||
* Returns the enum key (i.e. the constant name).
|
||||
*
|
||||
* @psalm-pure
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
/**
|
||||
* @psalm-pure
|
||||
* @psalm-suppress InvalidCast
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->value;
|
||||
}
|
||||
/**
|
||||
* Determines if Enum should be considered equal with the variable passed as a parameter.
|
||||
* Returns false if an argument is an object of different class or not an object.
|
||||
*
|
||||
* This method is final, for more information read https://github.com/myclabs/php-enum/issues/4
|
||||
*
|
||||
* @psalm-pure
|
||||
* @psalm-param mixed $variable
|
||||
* @param mixed $variable Variable to compare the enum to.
|
||||
* @return bool
|
||||
*/
|
||||
public final function equals($variable = null)
|
||||
{
|
||||
return $variable instanceof self && $this->getValue() === $variable->getValue() && static::class === \get_class($variable);
|
||||
}
|
||||
/**
|
||||
* Returns the names (keys) of all constants in the FakeEnum class.
|
||||
*
|
||||
* @psalm-pure
|
||||
* @psalm-return list<string>
|
||||
* @return array
|
||||
*/
|
||||
public static function keys()
|
||||
{
|
||||
return \array_keys(static::toArray());
|
||||
}
|
||||
/**
|
||||
* Returns instances of the FakeEnum class of all Enum constants.
|
||||
*
|
||||
* @psalm-pure
|
||||
* @psalm-return array<string, static>
|
||||
* @return static[] Constant name in key, FakeEnum instance in value.
|
||||
*/
|
||||
public static function values()
|
||||
{
|
||||
$values = [];
|
||||
/** @psalm-var T $value */
|
||||
foreach (static::toArray() as $key => $value) {
|
||||
$values[$key] = new static($value);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
/**
|
||||
* Returns all possible values as an array.
|
||||
*
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureStaticProperty
|
||||
*
|
||||
* @psalm-return array<string, mixed>
|
||||
* @return array Constant name in key, constant value in value
|
||||
*/
|
||||
public static function toArray()
|
||||
{
|
||||
$class = static::class;
|
||||
if (!isset(static::$cache[$class])) {
|
||||
/** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */
|
||||
$reflection = new ReflectionClass($class);
|
||||
/** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */
|
||||
static::$cache[$class] = $reflection->getConstants();
|
||||
}
|
||||
return static::$cache[$class];
|
||||
}
|
||||
/**
|
||||
* Check if is valid enum value.
|
||||
*
|
||||
* @param mixed $value Value to check for validity.
|
||||
* @psalm-param mixed $value
|
||||
* @psalm-pure
|
||||
* @psalm-assert-if-true T $value
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValid($value)
|
||||
{
|
||||
return \in_array($value, static::toArray(), \true);
|
||||
}
|
||||
/**
|
||||
* Asserts valid enum value.
|
||||
*
|
||||
* @psalm-pure
|
||||
* @psalm-assert T $value
|
||||
* @param mixed $value Value to assert for validity.
|
||||
* @throws UnexpectedValueException If the value is not part of the enum.
|
||||
*/
|
||||
public static function assertValidValue($value)
|
||||
{
|
||||
self::assertValidValueReturningKey($value);
|
||||
}
|
||||
/**
|
||||
* Asserts valid enum value.
|
||||
*
|
||||
* @psalm-pure
|
||||
* @psalm-assert T $value
|
||||
* @param mixed $value Value to assert for validity.
|
||||
* @return string
|
||||
* @throws UnexpectedValueException If the value is not part of the enum.
|
||||
*/
|
||||
protected static function assertValidValueReturningKey($value)
|
||||
{
|
||||
if (\false === ($key = static::search($value))) {
|
||||
throw new UnexpectedValueException("Value '{$value}' is not part of the enum " . static::class);
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
/**
|
||||
* Check if is valid enum key.
|
||||
*
|
||||
* @param string $key Key to check for validity.
|
||||
* @psalm-param string $key
|
||||
* @psalm-pure
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValidKey($key)
|
||||
{
|
||||
$array = static::toArray();
|
||||
return isset($array[$key]) || \array_key_exists($key, $array);
|
||||
}
|
||||
/**
|
||||
* Return key for value.
|
||||
*
|
||||
* @param mixed $value Value to search for.
|
||||
*
|
||||
* @psalm-param mixed $value
|
||||
* @psalm-pure
|
||||
* @return string|false
|
||||
*/
|
||||
public static function search($value)
|
||||
{
|
||||
return \array_search($value, static::toArray(), \true);
|
||||
}
|
||||
/**
|
||||
* Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant.
|
||||
*
|
||||
* @param string $name Name of the method that was called.
|
||||
* @param array $arguments Arguments provided to the method.
|
||||
*
|
||||
* @return static
|
||||
* @throws BadMethodCallException If the the method was not a known constant.
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function __callStatic($name, $arguments)
|
||||
{
|
||||
$class = static::class;
|
||||
if (!isset(self::$instances[$class][$name])) {
|
||||
$array = static::toArray();
|
||||
if (!isset($array[$name]) && !\array_key_exists($name, $array)) {
|
||||
$message = "No static method or enum constant '{$name}' in class " . static::class;
|
||||
throw new BadMethodCallException($message);
|
||||
}
|
||||
return self::$instances[$class][$name] = new static($array[$name]);
|
||||
}
|
||||
return clone self::$instances[$class][$name];
|
||||
}
|
||||
/**
|
||||
* Specify data which should be serialized to JSON. This method returns data that can be serialized by json_encode()
|
||||
* natively.
|
||||
*
|
||||
* @return mixed
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @psalm-pure
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->getValue();
|
||||
}
|
||||
}
|
30
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Format.php
vendored
Normal file
30
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Format.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject;
|
||||
|
||||
/**
|
||||
* Interface with constants for the different AMP HTML formats.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface Format
|
||||
{
|
||||
/**
|
||||
* AMP for websites format.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP = 'AMP';
|
||||
/**
|
||||
* AMP for ads format.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP4ADS = 'AMP4ADS';
|
||||
/**
|
||||
* AMP for email format.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AMP4EMAIL = 'AMP4EMAIL';
|
||||
}
|
26
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Html/AtRule.php
vendored
Normal file
26
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Html/AtRule.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html;
|
||||
|
||||
/**
|
||||
* Interface with constants for the different "at" rules.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface AtRule
|
||||
{
|
||||
const _MOZ_DOCUMENT = '-moz-document';
|
||||
const CHARSET = 'charset';
|
||||
const COUNTER_STYLE = 'counter-style';
|
||||
const DOCUMENT = 'document';
|
||||
const FONT_FACE = 'font-face';
|
||||
const FONT_FEATURE_VALUES = 'font-feature-values';
|
||||
const IMPORT = 'import';
|
||||
const KEYFRAMES = 'keyframes';
|
||||
const MEDIA = 'media';
|
||||
const NAMESPACE_ = 'namespace';
|
||||
const PAGE = 'page';
|
||||
const PROPERTY = 'property';
|
||||
const SUPPORTS = 'supports';
|
||||
const VIEWPORT = 'viewport';
|
||||
}
|
1161
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Html/Attribute.php
vendored
Normal file
1161
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Html/Attribute.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
183
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Html/LengthUnit.php
vendored
Normal file
183
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Html/LengthUnit.php
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html;
|
||||
|
||||
/**
|
||||
* Unit of a length.
|
||||
*
|
||||
* This interface defines the available units that can be recognized in HTML and/or CSS dimensions.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class LengthUnit
|
||||
{
|
||||
/**
|
||||
* Centimeters.
|
||||
*
|
||||
* 1cm = 96px/2.54.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CM = 'cm';
|
||||
/**
|
||||
* Millimeters.
|
||||
*
|
||||
* 1mm = 1/10th of 1cm.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MM = 'mm';
|
||||
/**
|
||||
* Quarter-millimeters.
|
||||
*
|
||||
* 1Q = 1/40th of 1cm.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const Q = 'q';
|
||||
/**
|
||||
* Inches.
|
||||
*
|
||||
* 1in = 2.54cm = 96px.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const IN = 'in';
|
||||
/**
|
||||
* Picas.
|
||||
*
|
||||
* 1pc = 1/6th of 1in.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PC = 'pc';
|
||||
/**
|
||||
* Points.
|
||||
*
|
||||
* 1pt = 1/72th of 1in.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PT = 'pt';
|
||||
/**
|
||||
* Pixels.
|
||||
*
|
||||
* 1px = 1/96th of 1in.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PX = 'px';
|
||||
/**
|
||||
* Font size of the parent, in the case of typographical properties like font-size, and font size of the element
|
||||
* itself, in the case of other properties like width.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const EM = 'em';
|
||||
/**
|
||||
* The x-height of the element's font.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const EX = 'ex';
|
||||
/**
|
||||
* The advance measure (width) of the glyph "0" of the element's font.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CH = 'ch';
|
||||
/**
|
||||
* Font size of the root element.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const REM = 'rem';
|
||||
/**
|
||||
* Line height of the element.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LH = 'lh';
|
||||
/**
|
||||
* 1% of the viewport's width.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VW = 'vw';
|
||||
/**
|
||||
* 1% of the viewport's height.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VH = 'vh';
|
||||
/**
|
||||
* 1% of the viewport's smaller dimension.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VMIN = 'vmin';
|
||||
/**
|
||||
* 1% of the viewport's larger dimension.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VMAX = 'vmax';
|
||||
/**
|
||||
* Set of known absolute units.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const ABSOLUTE_UNITS = [self::CM, self::MM, self::Q, self::IN, self::PC, self::PT, self::PX];
|
||||
/**
|
||||
* Set of known relative units.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const RELATIVE_UNITS = [self::EM, self::EX, self::CH, self::REM, self::LH, self::VW, self::VH, self::VMIN, self::VMAX];
|
||||
/**
|
||||
* Pixels per inch resolution to use for conversions.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const PPI = 96;
|
||||
/**
|
||||
* Centimeters per inch.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
const CM_PER_IN = 2.54;
|
||||
/**
|
||||
* Convert a unit-based length into a number of pixels.
|
||||
*
|
||||
* @param int|float $value Value to convert.
|
||||
* @param string $unit Unit of the value.
|
||||
* @return int|float|false Converted value, or false if it could not be converted.
|
||||
*/
|
||||
public static function convertIntoPixels($value, $unit)
|
||||
{
|
||||
if (0 === $value) {
|
||||
return 0;
|
||||
}
|
||||
switch ($unit) {
|
||||
case self::CM:
|
||||
return $value * self::PPI / self::CM_PER_IN;
|
||||
case self::MM:
|
||||
return $value * self::PPI / self::CM_PER_IN / 10;
|
||||
case self::Q:
|
||||
return $value * self::PPI / self::CM_PER_IN / 40;
|
||||
case self::IN:
|
||||
return $value * self::PPI;
|
||||
case self::PC:
|
||||
return $value * self::PPI / 6;
|
||||
case self::PT:
|
||||
return $value * self::PPI / 72;
|
||||
case self::PX:
|
||||
// No conversion needed for pixel values.
|
||||
return $value;
|
||||
default:
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html;
|
||||
|
||||
/**
|
||||
* Interface with constants for the different types of tags.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface LowerCaseTag extends Tag
|
||||
{
|
||||
}
|
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html\Parser;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Str;
|
||||
/**
|
||||
* Helper for determining the line/column information for SAX events that are being received by a HtmlSaxHandler.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class DocLocator
|
||||
{
|
||||
/**
|
||||
* Line mapped to a given position.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $lineByPosition = [];
|
||||
/**
|
||||
* Column mapped to a given position.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $columnByPosition = [];
|
||||
/**
|
||||
* Size of the document in bytes.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $documentByteSize;
|
||||
/**
|
||||
* The current position in the htmlText.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $position = 0;
|
||||
/**
|
||||
* The previous position in the htmlText.
|
||||
*
|
||||
* We need this to know where a tag or attribute etc. started.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $previousPosition = 0;
|
||||
/**
|
||||
* Line within the document.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $line = 1;
|
||||
/**
|
||||
* Column within the document.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $column = 0;
|
||||
/**
|
||||
* DocLocator constructor.
|
||||
*
|
||||
* @param string $htmlText String of HTML.
|
||||
*/
|
||||
public function __construct($htmlText)
|
||||
{
|
||||
/*
|
||||
* Precomputes a mapping from positions within htmlText to line / column numbers.
|
||||
*
|
||||
* TODO: This uses a fair amount of space and we can probably do better, but it's also quite simple so here we
|
||||
* are for now.
|
||||
*/
|
||||
$currentLine = 1;
|
||||
$currentColumn = 0;
|
||||
$length = Str::length($htmlText);
|
||||
for ($index = 0; $index < $length; ++$index) {
|
||||
$this->lineByPosition[$index] = $currentLine;
|
||||
$this->columnByPosition[$index] = $currentColumn;
|
||||
$character = Str::substring($htmlText, $index, 1);
|
||||
if ($character === "\n") {
|
||||
++$currentLine;
|
||||
$currentColumn = 0;
|
||||
} else {
|
||||
++$currentColumn;
|
||||
}
|
||||
}
|
||||
$this->documentByteSize = Str::length($htmlText);
|
||||
}
|
||||
/**
|
||||
* Advances the internal position by the characters in $tokenText.
|
||||
*
|
||||
* This method is to be called only from within the parser.
|
||||
*
|
||||
* @param string $tokenText The token text which we examine to advance the line / column location within the doc.
|
||||
*/
|
||||
public function advancePosition($tokenText)
|
||||
{
|
||||
$this->previousPosition = $this->position;
|
||||
$this->position += Str::length($tokenText);
|
||||
}
|
||||
/**
|
||||
* Snapshots the previous internal position so that getLine / getCol will return it.
|
||||
*
|
||||
* These snapshots happen as the parser enter / exits a tag.
|
||||
*
|
||||
* This method is to be called only from within the parser.
|
||||
*/
|
||||
public function snapshotPosition()
|
||||
{
|
||||
if ($this->previousPosition < \count($this->lineByPosition)) {
|
||||
$this->line = $this->lineByPosition[$this->previousPosition];
|
||||
$this->column = $this->columnByPosition[$this->previousPosition];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the current line in the HTML source from which the most recent SAX event was generated. This value is only
|
||||
* sensible once an event has been generated, that is, in practice from within the context of the HtmlSaxHandler
|
||||
* methods - e.g., startTag(), pcdata(), etc.
|
||||
*
|
||||
* @return int The current line.
|
||||
*/
|
||||
public function getLine()
|
||||
{
|
||||
return $this->line;
|
||||
}
|
||||
/**
|
||||
* Get the current column in the HTML source from which the most recent SAX event was generated. This value is only
|
||||
* sensible once an event has been generated, that is, in practice from within the context of the HtmlSaxHandler
|
||||
* methods - e.g., startTag(), pcdata(), etc.
|
||||
*
|
||||
* @return int The current column.
|
||||
*/
|
||||
public function getColumn()
|
||||
{
|
||||
return $this->column;
|
||||
}
|
||||
/**
|
||||
* Get the size of the document in bytes.
|
||||
*
|
||||
* @return int The size of the document in bytes.
|
||||
*/
|
||||
public function getDocumentByteSize()
|
||||
{
|
||||
return $this->documentByteSize;
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html\Parser;
|
||||
|
||||
/**
|
||||
* The html eflags, used internally by the parser.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface EFlags
|
||||
{
|
||||
const OPTIONAL_ENDTAG = 1;
|
||||
const EMPTY_ = 2;
|
||||
const CDATA = 4;
|
||||
const RCDATA = 8;
|
||||
const UNSAFE = 16;
|
||||
const FOLDABLE = 32;
|
||||
const UNKNOWN_OR_CUSTOM = 64;
|
||||
}
|
@@ -0,0 +1,393 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html\Parser;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Encoding;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Exception\FailedToParseHtml;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\UpperCaseTag as Tag;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Str;
|
||||
/**
|
||||
* An Html parser.
|
||||
*
|
||||
* The parse() method takes a string and calls methods on HtmlSaxHandler while it is visiting its tokens.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class HtmlParser
|
||||
{
|
||||
/**
|
||||
* Regular expression that matches the next token to be processed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const INSIDE_TAG_TOKEN = '%^[ \\t\\n\\f\\r\\v]*(?:' . '(?:' . '([^\\t\\r\\n /=>][^\\t\\r\\n =>]*|' . '[^\\t\\r\\n =>]+[^ >]|' . '\\/+(?!>))' . '(' . '\\s*=\\s*' . '(' . '\\"[^\\"]*\\"' . '|\'[^\']*\'' . '|(?=[a-z][a-z-]*\\s+=)' . '|[^>\\s]*' . ')' . ')' . '?' . ')' . '|(/?>)' . '|[^a-z\\s>]+)' . '%i';
|
||||
/**
|
||||
* Regular expression that matches the next token to be processed when we are outside a tag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const OUTSIDE_TAG_TOKEN = '%^(?:' . '&(\\#[0-9]+|\\#[x][0-9a-f]+|\\w+);' . '|<[!]--[\\s\\S]*?(?:--[!]?>|$)' . '|<(/)?([a-z!\\?][^\\0 \\n\\r\\t\\f\\v>/]*)' . '|([^<&>]+)' . '|([<&>]))' . '%i';
|
||||
/**
|
||||
* Regular expression that matches null characters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NULL_REGEX = "/\x00/g";
|
||||
/**
|
||||
* Regular expression that matches entities.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ENTITY_REGEX = '/&(#\\d+|#x[0-9A-Fa-f]+|\\w+);/g';
|
||||
/**
|
||||
* Regular expression that matches loose &s.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LOOSE_AMP_REGEX = '/&([^a-z#]|#(?:[^0-9x]|x(?:[^0-9a-f]|$)|$)|$)/gi';
|
||||
/**
|
||||
* Regular expression that matches <.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LT_REGEX = '/</g';
|
||||
/**
|
||||
* Regular expression that matches >.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const GT_REGEX = '/>/g';
|
||||
/**
|
||||
* Regular expression that matches decimal numbers.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DECIMAL_ESCAPE_REGEX = '/^#(\\d+)$/';
|
||||
/**
|
||||
* Regular expression that matches hexadecimal numbers.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HEX_ESCAPE_REGEX = '/^#x([0-9A-Fa-f]+)$/';
|
||||
/**
|
||||
* HTML entities that are encoded/decoded.
|
||||
*
|
||||
* @type array<string>
|
||||
*/
|
||||
const ENTITIES = ['colon' => ':', 'lt' => '<', 'gt' => '>', 'amp' => '&', 'nbsp' => '\\u00a0', 'quot' => '"', 'apos' => '\''];
|
||||
/**
|
||||
* A map of element to a bitmap of flags it has, used internally on the parser.
|
||||
*
|
||||
* @var array<int>
|
||||
*/
|
||||
const ELEMENTS = [
|
||||
Tag::A => 0,
|
||||
Tag::ABBR => 0,
|
||||
Tag::ACRONYM => 0,
|
||||
Tag::ADDRESS => 0,
|
||||
Tag::APPLET => EFlags::UNSAFE,
|
||||
Tag::AREA => EFlags::EMPTY_,
|
||||
Tag::B => 0,
|
||||
Tag::BASE => EFlags::EMPTY_ | EFlags::UNSAFE,
|
||||
Tag::BASEFONT => EFlags::EMPTY_ | EFlags::UNSAFE,
|
||||
Tag::BDO => 0,
|
||||
Tag::BIG => 0,
|
||||
Tag::BLOCKQUOTE => 0,
|
||||
Tag::BODY => EFlags::OPTIONAL_ENDTAG | EFlags::UNSAFE | EFlags::FOLDABLE,
|
||||
Tag::BR => EFlags::EMPTY_,
|
||||
Tag::BUTTON => 0,
|
||||
Tag::CANVAS => 0,
|
||||
Tag::CAPTION => 0,
|
||||
Tag::CENTER => 0,
|
||||
Tag::CITE => 0,
|
||||
Tag::CODE => 0,
|
||||
Tag::COL => EFlags::EMPTY_,
|
||||
Tag::COLGROUP => EFlags::OPTIONAL_ENDTAG,
|
||||
Tag::DD => EFlags::OPTIONAL_ENDTAG,
|
||||
Tag::DEL => 0,
|
||||
Tag::DFN => 0,
|
||||
Tag::DIR => 0,
|
||||
Tag::DIV => 0,
|
||||
Tag::DL => 0,
|
||||
Tag::DT => EFlags::OPTIONAL_ENDTAG,
|
||||
Tag::EM => 0,
|
||||
Tag::FIELDSET => 0,
|
||||
Tag::FONT => 0,
|
||||
Tag::FORM => 0,
|
||||
Tag::FRAME => EFlags::EMPTY_ | EFlags::UNSAFE,
|
||||
Tag::FRAMESET => EFlags::UNSAFE,
|
||||
Tag::H1 => 0,
|
||||
Tag::H2 => 0,
|
||||
Tag::H3 => 0,
|
||||
Tag::H4 => 0,
|
||||
Tag::H5 => 0,
|
||||
Tag::H6 => 0,
|
||||
Tag::HEAD => EFlags::OPTIONAL_ENDTAG | EFlags::UNSAFE | EFlags::FOLDABLE,
|
||||
Tag::HR => EFlags::EMPTY_,
|
||||
Tag::HTML => EFlags::OPTIONAL_ENDTAG | EFlags::UNSAFE | EFlags::FOLDABLE,
|
||||
Tag::I => 0,
|
||||
Tag::IFRAME => EFlags::UNSAFE | EFlags::CDATA,
|
||||
Tag::IMG => EFlags::EMPTY_,
|
||||
Tag::INPUT => EFlags::EMPTY_,
|
||||
Tag::INS => 0,
|
||||
Tag::ISINDEX => EFlags::EMPTY_ | EFlags::UNSAFE,
|
||||
Tag::KBD => 0,
|
||||
Tag::LABEL => 0,
|
||||
Tag::LEGEND => 0,
|
||||
Tag::LI => EFlags::OPTIONAL_ENDTAG,
|
||||
Tag::LINK => EFlags::EMPTY_ | EFlags::UNSAFE,
|
||||
Tag::MAP => 0,
|
||||
Tag::MENU => 0,
|
||||
Tag::META => EFlags::EMPTY_ | EFlags::UNSAFE,
|
||||
Tag::NOFRAMES => EFlags::UNSAFE | EFlags::CDATA,
|
||||
// TODO: This used to read:
|
||||
// Tag::NOSCRIPT => EFlags::UNSAFE | EFlags::CDATA,
|
||||
// It appears that the effect of that is that anything inside is then considered cdata, so
|
||||
// <noscript><style>foo</noscript></noscript> never sees a style start tag / end tag event. But we must
|
||||
// recognize such style tags and they're also allowed by HTML, e.g. see:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript
|
||||
// On a broader note this also means we may be missing other start/end tag events inside elements marked as
|
||||
// CDATA which our parser should better reject. Yikes.
|
||||
Tag::NOSCRIPT => EFlags::UNSAFE,
|
||||
Tag::OBJECT => EFlags::UNSAFE,
|
||||
Tag::OL => 0,
|
||||
Tag::OPTGROUP => 0,
|
||||
Tag::OPTION => EFlags::OPTIONAL_ENDTAG,
|
||||
Tag::P => EFlags::OPTIONAL_ENDTAG,
|
||||
Tag::PARAM => EFlags::EMPTY_ | EFlags::UNSAFE,
|
||||
Tag::PRE => 0,
|
||||
Tag::Q => 0,
|
||||
Tag::S => 0,
|
||||
Tag::SAMP => 0,
|
||||
Tag::SCRIPT => EFlags::UNSAFE | EFlags::CDATA,
|
||||
Tag::SELECT => 0,
|
||||
Tag::SMALL => 0,
|
||||
Tag::SPAN => 0,
|
||||
Tag::STRIKE => 0,
|
||||
Tag::STRONG => 0,
|
||||
Tag::STYLE => EFlags::UNSAFE | EFlags::CDATA,
|
||||
Tag::SUB => 0,
|
||||
Tag::SUP => 0,
|
||||
Tag::TABLE => 0,
|
||||
Tag::TBODY => EFlags::OPTIONAL_ENDTAG,
|
||||
Tag::TD => EFlags::OPTIONAL_ENDTAG,
|
||||
Tag::TEXTAREA => EFlags::RCDATA,
|
||||
Tag::TFOOT => EFlags::OPTIONAL_ENDTAG,
|
||||
Tag::TH => EFlags::OPTIONAL_ENDTAG,
|
||||
Tag::THEAD => EFlags::OPTIONAL_ENDTAG,
|
||||
Tag::TITLE => EFlags::RCDATA | EFlags::UNSAFE,
|
||||
Tag::TR => EFlags::OPTIONAL_ENDTAG,
|
||||
Tag::TT => 0,
|
||||
Tag::U => 0,
|
||||
Tag::UL => 0,
|
||||
Tag::VAR_ => 0,
|
||||
];
|
||||
/**
|
||||
* Given a SAX-like HtmlSaxHandler, this parses a $htmlText and lets the $handler know the structure while visiting
|
||||
* the nodes. If the provided handler is an implementation of HtmlSaxHandlerWithLocation, then its setDocLocator()
|
||||
* method will get called prior to startDoc(), and the getLine() / getColumn() methods will reflect the current
|
||||
* line / column while a SAX callback (e.g., startTag()) is active.
|
||||
*
|
||||
* @param HtmlSaxHandler $handler The HtmlSaxHandler that will receive the events.
|
||||
* @param string $htmlText The html text.
|
||||
*/
|
||||
public function parse(HtmlSaxHandler $handler, $htmlText)
|
||||
{
|
||||
$htmlUpper = null;
|
||||
$inTag = \false;
|
||||
// True iff we're currently processing a tag.
|
||||
$attributes = [];
|
||||
// Accumulates attribute names and values.
|
||||
$tagName = null;
|
||||
// The name of the tag currently being processed.
|
||||
$eflags = null;
|
||||
// The element flags for the current tag.
|
||||
$openTag = \false;
|
||||
// True if the current tag is an open tag.
|
||||
$tagStack = new TagNameStack($handler);
|
||||
Str::setEncoding(Encoding::AMP);
|
||||
// Only provide location information if the handler implements the setDocLocator method.
|
||||
$locator = null;
|
||||
if ($handler instanceof HtmlSaxHandlerWithLocation) {
|
||||
$locator = new DocLocator($htmlText);
|
||||
$handler->setDocLocator($locator);
|
||||
}
|
||||
// Lets the handler know that we are starting to parse the document.
|
||||
$handler->startDoc();
|
||||
// Consumes tokens from the htmlText and stops once all tokens are processed.
|
||||
while ($htmlText) {
|
||||
$regex = $inTag ? self::INSIDE_TAG_TOKEN : self::OUTSIDE_TAG_TOKEN;
|
||||
// Gets the next token.
|
||||
$matches = null;
|
||||
Str::regexMatch($regex, $htmlText, $matches);
|
||||
// Avoid infinite loop in case nothing could be matched.
|
||||
// This can be caused by documents provided in the wrong encoding, which the regex engine fails to handle.
|
||||
if (empty($matches[0])) {
|
||||
throw FailedToParseHtml::forHtml($htmlText);
|
||||
}
|
||||
if ($locator) {
|
||||
$locator->advancePosition($matches[0]);
|
||||
}
|
||||
// And removes it from the string.
|
||||
$htmlText = Str::substring($htmlText, Str::length($matches[0]));
|
||||
if ($inTag) {
|
||||
if (!empty($matches[1])) {
|
||||
// Attribute.
|
||||
// SetAttribute with uppercase names doesn't work on IE6.
|
||||
$attributeName = Str::toLowerCase($matches[1]);
|
||||
// Use empty string as value for valueless attribs, so <input type=checkbox checked> gets attributes
|
||||
// ['type', 'checkbox', 'checked', ''].
|
||||
$decodedValue = '';
|
||||
if (!empty($matches[2])) {
|
||||
$encodedValue = $matches[3];
|
||||
switch (Str::substring($encodedValue, 0, 1)) {
|
||||
// Strip quotes.
|
||||
case '"':
|
||||
case "'":
|
||||
$encodedValue = Str::substring($encodedValue, 1, Str::length($encodedValue) - 2);
|
||||
break;
|
||||
}
|
||||
$decodedValue = $this->unescapeEntities($this->stripNULs($encodedValue));
|
||||
}
|
||||
$attributes[] = $attributeName;
|
||||
$attributes[] = $decodedValue;
|
||||
} elseif (!empty($matches[4])) {
|
||||
if ($eflags !== null) {
|
||||
// False if not in allowlist.
|
||||
if ($openTag) {
|
||||
$tagStack->startTag(new ParsedTag($tagName, $attributes));
|
||||
} else {
|
||||
$tagStack->endTag(new ParsedTag($tagName));
|
||||
}
|
||||
}
|
||||
if ($openTag && $eflags & (EFlags::CDATA | EFlags::RCDATA)) {
|
||||
if ($htmlUpper === null) {
|
||||
$htmlUpper = Str::toUpperCase($htmlText);
|
||||
} else {
|
||||
$htmlUpper = Str::substring($htmlUpper, Str::length($htmlUpper) - Str::length($htmlText));
|
||||
}
|
||||
$dataEnd = Str::position($htmlUpper, "</{$tagName}");
|
||||
if ($dataEnd < 0) {
|
||||
$dataEnd = Str::length($htmlText);
|
||||
}
|
||||
if ($eflags & EFlags::CDATA) {
|
||||
$handler->cdata(Str::substring($htmlText, 0, $dataEnd));
|
||||
} else {
|
||||
$handler->rcdata($this->normalizeRCData(Str::substring($htmlText, 0, $dataEnd)));
|
||||
}
|
||||
if ($locator) {
|
||||
$locator->advancePosition(Str::substring($htmlText, 0, $dataEnd));
|
||||
}
|
||||
$htmlText = Str::substring($htmlText, $dataEnd);
|
||||
}
|
||||
$tagName = null;
|
||||
$eflags = null;
|
||||
$openTag = \false;
|
||||
$attributes = [];
|
||||
if ($locator) {
|
||||
$locator->snapshotPosition();
|
||||
}
|
||||
$inTag = \false;
|
||||
}
|
||||
} else {
|
||||
if (!empty($matches[1])) {
|
||||
// Entity.
|
||||
$tagStack->pcdata($matches[0]);
|
||||
} elseif (!empty($matches[3])) {
|
||||
// Tag.
|
||||
$openTag = !$matches[2];
|
||||
if ($locator) {
|
||||
$locator->snapshotPosition();
|
||||
}
|
||||
$inTag = \true;
|
||||
$tagName = Str::toUpperCase($matches[3]);
|
||||
$eflags = \array_key_exists($tagName, self::ELEMENTS) ? self::ELEMENTS[$tagName] : EFlags::UNKNOWN_OR_CUSTOM;
|
||||
} elseif (!empty($matches[4])) {
|
||||
// Text.
|
||||
if ($locator) {
|
||||
$locator->snapshotPosition();
|
||||
}
|
||||
$tagStack->pcdata($matches[4]);
|
||||
} elseif (!empty($matches[5])) {
|
||||
// Cruft.
|
||||
switch ($matches[5]) {
|
||||
case '<':
|
||||
$tagStack->pcdata('<');
|
||||
break;
|
||||
case '>':
|
||||
$tagStack->pcdata('>');
|
||||
break;
|
||||
default:
|
||||
$tagStack->pcdata('&');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$inTag && $locator) {
|
||||
$locator->snapshotPosition();
|
||||
}
|
||||
// Lets the handler know that we are done parsing the document.
|
||||
$tagStack->exitRemainingTags();
|
||||
$handler->effectiveBodyTag($tagStack->effectiveBodyAttributes());
|
||||
$handler->endDoc();
|
||||
}
|
||||
/**
|
||||
* Decode an HTML entity.
|
||||
*
|
||||
* This method is public as it needs to be passed into Str::regexReplaceCallback().
|
||||
*
|
||||
* @param string $entity The full entity (including the & and the ;).
|
||||
* @return string A single unicode code-point as a string.
|
||||
*/
|
||||
public function lookupEntity($entity)
|
||||
{
|
||||
$name = Str::toLowerCase(Str::substring($entity, Str::length($entity) - 1));
|
||||
if (\array_key_exists($name, self::ENTITIES)) {
|
||||
return self::ENTITIES[$name];
|
||||
}
|
||||
$matches = [];
|
||||
if (Str::regexMatch(self::DECIMAL_ESCAPE_REGEX, $name, $matches)) {
|
||||
return \chr((int) $matches[1]);
|
||||
}
|
||||
if (Str::regexMatch(self::HEX_ESCAPE_REGEX, $name, $matches)) {
|
||||
return \chr(\hexdec($matches[1]));
|
||||
}
|
||||
// If unable to decode, return the name.
|
||||
return $name;
|
||||
}
|
||||
/**
|
||||
* Remove null characters on the string.
|
||||
*
|
||||
* @param string $text The string to have the null characters removed.
|
||||
* @return string A string without null characters.
|
||||
* @private
|
||||
*/
|
||||
private function stripNULs($text)
|
||||
{
|
||||
return Str::regexReplace(self::NULL_REGEX, '', $text);
|
||||
}
|
||||
/**
|
||||
* The plain text of a chunk of HTML CDATA which possibly containing.
|
||||
*
|
||||
* @param string $text A chunk of HTML CDATA. It must not start or end inside an HTML entity.
|
||||
* @return string The unescaped entities.
|
||||
*/
|
||||
private function unescapeEntities($text)
|
||||
{
|
||||
return Str::regexReplaceCallback(self::ENTITY_REGEX, [$this, 'lookupEntity'], $text);
|
||||
}
|
||||
/**
|
||||
* Escape entities in RCDATA that can be escaped without changing the meaning.
|
||||
*
|
||||
* @param string $rcdata The RCDATA string we want to normalize.
|
||||
* @return string A normalized version of RCDATA.
|
||||
*/
|
||||
private function normalizeRCData($rcdata)
|
||||
{
|
||||
$rcdata = Str::regexReplace(self::LOOSE_AMP_REGEX, '&$1', $rcdata);
|
||||
$rcdata = Str::regexReplace(self::LT_REGEX, '<', $rcdata);
|
||||
$rcdata = Str::regexReplace(self::GT_REGEX, '>', $rcdata);
|
||||
return $rcdata;
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html\Parser;
|
||||
|
||||
/**
|
||||
* An interface to the HtmlParser visitor that gets called while the HTML is being parsed.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface HtmlSaxHandler
|
||||
{
|
||||
/**
|
||||
* Handler called when the parser found a new tag.
|
||||
*
|
||||
* @param ParsedTag $tag New tag that was found.
|
||||
* @return void
|
||||
*/
|
||||
public function startTag(ParsedTag $tag);
|
||||
/**
|
||||
* Handler called when the parser found a closing tag.
|
||||
*
|
||||
* @param ParsedTag $tag Closing tag that was found.
|
||||
* @return void
|
||||
*/
|
||||
public function endTag(ParsedTag $tag);
|
||||
/**
|
||||
* Handler called when PCDATA is found.
|
||||
*
|
||||
* @param string $text The PCDATA that was found.
|
||||
* @return void
|
||||
*/
|
||||
public function pcdata($text);
|
||||
/**
|
||||
* Handler called when RCDATA is found.
|
||||
*
|
||||
* @param string $text The RCDATA that was found.
|
||||
* @return void
|
||||
*/
|
||||
public function rcdata($text);
|
||||
/**
|
||||
* Handler called when CDATA is found.
|
||||
*
|
||||
* @param string $text The CDATA that was found.
|
||||
* @return void
|
||||
*/
|
||||
public function cdata($text);
|
||||
/**
|
||||
* Handler called when the parser is starting to parse the document.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function startDoc();
|
||||
/**
|
||||
* Handler called when the parsing is done.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function endDoc();
|
||||
/**
|
||||
* Callback for informing that the parser is manufacturing a <body> tag not actually found on the page. This will be
|
||||
* followed by a startTag() with the actual body tag in question.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function markManufacturedBody();
|
||||
/**
|
||||
* HTML5 defines how parsers treat documents with multiple body tags: they merge the attributes from the later ones
|
||||
* into the first one. Therefore, just before the parser sends the endDoc event, it will also send this event which
|
||||
* will provide the attributes from the effective body tag to the client (the handler).
|
||||
*
|
||||
* @param array<ParsedAttribute> $attributes Array of parsed attributes.
|
||||
* @return void
|
||||
*/
|
||||
public function effectiveBodyTag($attributes);
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html\Parser;
|
||||
|
||||
/**
|
||||
* An interface to the HtmlParser visitor that gets called while the HTML is being parsed.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface HtmlSaxHandlerWithLocation extends HtmlSaxHandler
|
||||
{
|
||||
/**
|
||||
* Called prior to parsing a document, that is, before startTag().
|
||||
*
|
||||
* @param DocLocator $locator A locator instance which provides access to the line/column information while SAX
|
||||
* events are being received by the handler.
|
||||
* @return void
|
||||
*/
|
||||
public function setDocLocator(DocLocator $locator);
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html\Parser;
|
||||
|
||||
/**
|
||||
* Name/Value pair representing an HTML Tag attribute.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class ParsedAttribute
|
||||
{
|
||||
/**
|
||||
* Name of the attribute.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* Value of the attribute.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $value;
|
||||
/**
|
||||
* ParsedAttribute constructor.
|
||||
*
|
||||
* @param string $name Name of the attribute.
|
||||
* @param string $value Value of the attribute.
|
||||
*/
|
||||
public function __construct($name, $value)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
}
|
||||
/**
|
||||
* Get the name of the attribute.
|
||||
*
|
||||
* @return string Name of the attribute.
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
/**
|
||||
* Get the value of the attribute.
|
||||
*
|
||||
* @return string Value of the attribute.
|
||||
*/
|
||||
public function value()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html\Parser;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\ScriptReleaseVersion;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Str;
|
||||
/**
|
||||
* The Html parser makes method calls with ParsedTags as arguments.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class ParsedTag
|
||||
{
|
||||
/**
|
||||
* Name of the parsed tag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $tagName;
|
||||
/**
|
||||
* Associative array of attributes.
|
||||
*
|
||||
* @var array<ParsedAttribute>
|
||||
*/
|
||||
private $attributes = [];
|
||||
/**
|
||||
* Lazily allocated map from attribute name to value.
|
||||
*
|
||||
* @var array<string>|null
|
||||
*/
|
||||
private $attributesByKey;
|
||||
/**
|
||||
* State of a script tag.
|
||||
*
|
||||
* @var ScriptTag
|
||||
*/
|
||||
private $scriptTag;
|
||||
/**
|
||||
* ParsedTag constructor.
|
||||
*
|
||||
* @param string $tagName Name of the parsed tag.
|
||||
* @param array $alternatingAttributes Optional. Array of alternating (name, value) pairs.
|
||||
*/
|
||||
public function __construct($tagName, $alternatingAttributes = [])
|
||||
{
|
||||
/*
|
||||
* Tag and Attribute names are case-insensitive. For error messages, we would like to use lower-case names as
|
||||
* they read a little nicer. However, in validator environments where the parsing is done by the actual browser,
|
||||
* the DOM API returns tag names in upper case. We stick with this convention for tag names, for performance,
|
||||
* but convert to lower when producing error messages. Error messages aren't produced in latency sensitive
|
||||
* contexts.
|
||||
*/
|
||||
if (!\is_array($alternatingAttributes)) {
|
||||
$alternatingAttributes = [];
|
||||
}
|
||||
$this->tagName = Str::toUpperCase($tagName);
|
||||
// Convert attribute names to lower case, not values, which are case-sensitive.
|
||||
$count = \count($alternatingAttributes);
|
||||
for ($index = 0; $index < $count; $index += 2) {
|
||||
$name = Str::toLowerCase($alternatingAttributes[$index]);
|
||||
$value = $alternatingAttributes[$index + 1];
|
||||
// Our html parser repeats the key as the value if there is no value. We
|
||||
// replace the value with an empty string instead in this case.
|
||||
if ($name === $value) {
|
||||
$value = '';
|
||||
}
|
||||
$this->attributes[] = new ParsedAttribute($name, $value);
|
||||
}
|
||||
// Sort the attribute array by (lower case) name.
|
||||
\usort($this->attributes, function (ParsedAttribute $a, ParsedAttribute $b) {
|
||||
if (\PHP_MAJOR_VERSION < 7 && $a->name() === $b->name()) {
|
||||
// Hack required for PHP 5.6, as it does not maintain stable order for equal items.
|
||||
// See https://bugs.php.net/bug.php?id=69158.
|
||||
// To get around this, we compare the index within $this->attributes instead to maintain existing order.
|
||||
return \strcmp(\array_search($a, $this->attributes, \true), \array_search($b, $this->attributes, \true));
|
||||
}
|
||||
return \strcmp($a->name(), $b->name());
|
||||
});
|
||||
$this->scriptTag = new ScriptTag($this->tagName, $this->attributes);
|
||||
}
|
||||
/**
|
||||
* Get the lower-case tag name.
|
||||
*
|
||||
* @return string Lower-case tag name.
|
||||
*/
|
||||
public function lowerName()
|
||||
{
|
||||
return Str::toLowerCase($this->tagName);
|
||||
}
|
||||
/**
|
||||
* Get the upper-case tag name.
|
||||
*
|
||||
* @return string Upper-case tag name.
|
||||
*/
|
||||
public function upperName()
|
||||
{
|
||||
return $this->tagName;
|
||||
}
|
||||
/**
|
||||
* Returns an array of attributes.
|
||||
*
|
||||
* Each attribute has two fields: name and value. Name is always lower-case, value is the case from the original
|
||||
* document. Values are unescaped.
|
||||
*
|
||||
* @return array<ParsedAttribute>
|
||||
*/
|
||||
public function attributes()
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
/**
|
||||
* Returns an object mapping attribute name to attribute value.
|
||||
*
|
||||
* This is populated lazily, as it's not used for most tags.
|
||||
*
|
||||
* @return array<string>
|
||||
* */
|
||||
public function attributesByKey()
|
||||
{
|
||||
if ($this->attributesByKey === null) {
|
||||
$this->attributesByKey = [];
|
||||
foreach ($this->attributes as $attribute) {
|
||||
$this->attributesByKey[$attribute->name()] = $attribute->value();
|
||||
}
|
||||
}
|
||||
return $this->attributesByKey;
|
||||
}
|
||||
/**
|
||||
* Returns a duplicate attribute name if the tag contains two attributes named the same, but with different
|
||||
* attribute values.
|
||||
*
|
||||
* Same attribute name AND value is OK. Returns null if there are no such duplicate attributes.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function hasDuplicateAttributes()
|
||||
{
|
||||
$lastAttributeName = '';
|
||||
$lastAttributeValue = '';
|
||||
foreach ($this->attributes as $attribute) {
|
||||
if ($lastAttributeName === $attribute->name() && $lastAttributeValue !== $attribute->value()) {
|
||||
return $attribute->name();
|
||||
}
|
||||
$lastAttributeName = $attribute->name();
|
||||
$lastAttributeValue = $attribute->value();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Removes duplicate attributes from the attribute list.
|
||||
*
|
||||
* This is consistent with HTML5 parsing error handling rules, only the first attribute with each attribute name is
|
||||
* considered, the remainder are ignored.
|
||||
*/
|
||||
public function dedupeAttributes()
|
||||
{
|
||||
$newAttributes = [];
|
||||
$lastAttributeName = '';
|
||||
foreach ($this->attributes as $attribute) {
|
||||
if ($lastAttributeName !== $attribute->name()) {
|
||||
$newAttributes[] = $attribute;
|
||||
}
|
||||
$lastAttributeName = $attribute->name();
|
||||
}
|
||||
$this->attributes = $newAttributes;
|
||||
}
|
||||
/**
|
||||
* Returns the value of a given attribute name. If it does not exist then returns null.
|
||||
*
|
||||
* @param string $name Name of the attribute.
|
||||
* @return string|null Value of the attribute, or null if it does not exist.
|
||||
*/
|
||||
public function getAttributeValueOrNull($name)
|
||||
{
|
||||
$attributesByKey = $this->attributesByKey();
|
||||
return \array_key_exists($name, $attributesByKey) ? $attributesByKey[$name] : null;
|
||||
}
|
||||
/**
|
||||
* Returns the script release version, otherwise ScriptReleaseVersion::UNKNOWN.
|
||||
*
|
||||
* @return ScriptReleaseVersion
|
||||
*/
|
||||
public function getScriptReleaseVersion()
|
||||
{
|
||||
return $this->scriptTag->releaseVersion();
|
||||
}
|
||||
/**
|
||||
* Tests if this tag is a script with a src of an AMP domain.
|
||||
*
|
||||
* @return bool Whether this tag is a script with a src of an AMP domain.
|
||||
*/
|
||||
public function isAmpDomain()
|
||||
{
|
||||
return $this->scriptTag->isAmpDomain();
|
||||
}
|
||||
/**
|
||||
* Tests if this is the AMP runtime script tag.
|
||||
*
|
||||
* @return bool Whether this is the AMP runtime script tag.
|
||||
*/
|
||||
public function isAmpRuntimeScript()
|
||||
{
|
||||
return $this->scriptTag->isRuntime();
|
||||
}
|
||||
/**
|
||||
* Tests if this is an extension script tag.
|
||||
*
|
||||
* @return bool Whether this is an extension script tag.
|
||||
*/
|
||||
public function isExtensionScript()
|
||||
{
|
||||
return $this->scriptTag->isExtension();
|
||||
}
|
||||
}
|
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html\Parser;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Amp;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Attribute;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\Tag;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\ScriptReleaseVersion;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Str;
|
||||
/**
|
||||
* Represents the state of a script tag.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class ScriptTag
|
||||
{
|
||||
/**
|
||||
* Name of the tag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $tagName;
|
||||
/**
|
||||
* Array of parsed attributes.
|
||||
*
|
||||
* @var array<ParsedAttribute>
|
||||
*/
|
||||
private $attributes;
|
||||
/**
|
||||
* Lazily evaluated collection of properties about the script tag.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $parsedProperties;
|
||||
/**
|
||||
* Standard and Nomodule JavaScript.
|
||||
*
|
||||
* Examples:
|
||||
* - v0.js
|
||||
* - v0/amp-ad-0.1.js
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STANDARD_SCRIPT_PATH_REGEX = '/(v0|v0/amp-[a-z0-9-]*-[a-z0-9.]*)\\.js$/i';
|
||||
/**
|
||||
* LTS and Nomodule LTS JavaScript.
|
||||
*
|
||||
* Examples:
|
||||
* - lts/v0.js
|
||||
* - lts/v0/amp-ad-0.1.js
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LTS_SCRIPT_PATH_REGEX = '/lts/(v0|v0/amp-[a-z0-9-]*-[a-z0-9.]*)\\.js$/i';
|
||||
/**
|
||||
* Module JavaScript.
|
||||
*
|
||||
* Examples:
|
||||
* - v0.mjs
|
||||
* - amp-ad-0.1.mjs
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MODULE_SCRIPT_PATH_REGEX = '/(v0|v0/amp-[a-z0-9-]*-[a-z0-9.]*)\\.mjs$/i';
|
||||
/**
|
||||
* Module LTS JavaScript.
|
||||
*
|
||||
* Examples:
|
||||
* - lts/v0.mjs
|
||||
* - lts/v0/amp-ad-0.1.mjs
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MODULE_LTS_SCRIPT_PATH_REGEX = '/lts/(v0|v0/amp-[a-z0-9-]*-[a-z0-9.]*)\\.mjs$/i';
|
||||
/**
|
||||
* Runtime JavaScript.
|
||||
*
|
||||
* Examples:
|
||||
* - v0.js
|
||||
* - v0.mjs
|
||||
* - v0.mjs?f=sxg
|
||||
* - lts/v0.js
|
||||
* - lts/v0.js?f=sxg
|
||||
* -lts/v0.mjs
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const RUNTIME_SCRIPT_PATH_REGEX = '/(lts/)?v0\\.m?js(\\?f=sxg)?/i';
|
||||
/**
|
||||
* ScriptTag constructor.
|
||||
*
|
||||
* @param string $tagName Name of the tag.
|
||||
* @param array<ParsedAttribute> $attributes Array of parsed attributes.
|
||||
*/
|
||||
public function __construct($tagName, $attributes)
|
||||
{
|
||||
$this->tagName = $tagName;
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
/**
|
||||
* Returns the script release version, otherwise ScriptReleaseVersion::UNKNOWN.
|
||||
*
|
||||
* @return ScriptReleaseVersion
|
||||
*/
|
||||
public function releaseVersion()
|
||||
{
|
||||
if ($this->tagName !== Tag::SCRIPT) {
|
||||
return ScriptReleaseVersion::UNKNOWN();
|
||||
}
|
||||
$properties = $this->parseAttributes();
|
||||
return $properties['releaseVersion'];
|
||||
}
|
||||
/**
|
||||
* Tests if this tag is a script with a src of an AMP domain.
|
||||
*
|
||||
* @return bool Whether this tag is a script with a src of an AMP domain.
|
||||
*/
|
||||
public function isAmpDomain()
|
||||
{
|
||||
if ($this->tagName !== Tag::SCRIPT) {
|
||||
return \false;
|
||||
}
|
||||
$properties = $this->parseAttributes();
|
||||
return $properties['isAmpDomain'];
|
||||
}
|
||||
/**
|
||||
* Tests if this is the AMP runtime script tag.
|
||||
*
|
||||
* @return bool Whether this is the AMP runtime script tag.
|
||||
*/
|
||||
public function isRuntime()
|
||||
{
|
||||
if ($this->tagName !== Tag::SCRIPT) {
|
||||
return \false;
|
||||
}
|
||||
$properties = $this->parseAttributes();
|
||||
return $properties['isRuntime'];
|
||||
}
|
||||
/**
|
||||
* Tests if this is an extension script tag.
|
||||
*
|
||||
* @return bool Whether this is an extension script tag.
|
||||
*/
|
||||
public function isExtension()
|
||||
{
|
||||
if ($this->tagName !== Tag::SCRIPT) {
|
||||
return \false;
|
||||
}
|
||||
$properties = $this->parseAttributes();
|
||||
return $properties['isExtension'];
|
||||
}
|
||||
/**
|
||||
* Parse attributes to determine script properties.
|
||||
*
|
||||
* @return array Associative array of parsed properties.
|
||||
*/
|
||||
private function parseAttributes()
|
||||
{
|
||||
if ($this->parsedProperties !== null) {
|
||||
return $this->parsedProperties;
|
||||
}
|
||||
$properties = ['isAsync' => \false, 'isModule' => \false, 'isNomodule' => \false, 'isExtension' => \false, 'path' => '', 'src' => ''];
|
||||
foreach ($this->attributes as $attribute) {
|
||||
if ($attribute->name() === Attribute::ASYNC) {
|
||||
$properties['isAsync'] = \true;
|
||||
} elseif ($attribute->name() === Attribute::CUSTOM_ELEMENT || $attribute->name() === Attribute::CUSTOM_TEMPLATE || $attribute->name() === Attribute::HOST_SERVICE) {
|
||||
$properties['isExtension'] = \true;
|
||||
} elseif ($attribute->name() === Attribute::NOMODULE) {
|
||||
$properties['isNomodule'] = \true;
|
||||
} elseif ($attribute->name() === Attribute::SRC) {
|
||||
$properties['src'] = $attribute->value();
|
||||
} elseif ($attribute->name() === Attribute::TYPE && $attribute->value() === Attribute::TYPE_MODULE) {
|
||||
$properties['isModule'] = \true;
|
||||
}
|
||||
}
|
||||
// Determine if this has a valid AMP domain and separate the path from the attribute 'src'.
|
||||
if (Str::position($properties['src'], Amp::CACHE_ROOT_URL) === 0) {
|
||||
$properties['isAmpDomain'] = \true;
|
||||
$properties['path'] = Str::substring($properties['src'], Str::length(Amp::CACHE_ROOT_URL));
|
||||
// Only look at script tags that have attribute 'async'.
|
||||
if ($properties['isAsync']) {
|
||||
// Determine if this is the AMP Runtime.
|
||||
if (!$properties['isExtension'] && Str::regexMatch(self::RUNTIME_SCRIPT_PATH_REGEX, $properties['path'])) {
|
||||
$properties['isRuntime'] = \true;
|
||||
}
|
||||
// Determine the release version (LTS, module, standard, etc).
|
||||
if ($properties['isModule'] && Str::regexMatch(self::MODULE_LTS_SCRIPT_PATH_REGEX, $properties['path']) || $properties['isNomodule'] && Str::regexMatch(self::LTS_SCRIPT_PATH_REGEX, $properties['path'])) {
|
||||
$properties['releaseVersion'] = ScriptReleaseVersion::MODULE_NOMODULE_LTS();
|
||||
} elseif ($properties['isModule'] && Str::regexMatch(self::MODULE_SCRIPT_PATH_REGEX, $properties['path']) || $properties['isNomodule'] && Str::regexMatch(self::STANDARD_SCRIPT_PATH_REGEX, $properties['path'])) {
|
||||
$properties['releaseVersion'] = ScriptReleaseVersion::MODULE_NOMODULE();
|
||||
} elseif (Str::regexMatch(self::LTS_SCRIPT_PATH_REGEX, $properties['path'])) {
|
||||
$properties['releaseVersion'] = ScriptReleaseVersion::LTS();
|
||||
} elseif (Str::regexMatch(self::STANDARD_SCRIPT_PATH_REGEX, $properties['path'])) {
|
||||
$properties['releaseVersion'] = ScriptReleaseVersion::STANDARD();
|
||||
} else {
|
||||
$properties['releaseVersion'] = ScriptReleaseVersion::UNKNOWN();
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->parsedProperties = $properties;
|
||||
return $this->parsedProperties;
|
||||
}
|
||||
}
|
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html\Parser;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Html\UpperCaseTag as Tag;
|
||||
use Google\Web_Stories_Dependencies\AmpProject\Str;
|
||||
/**
|
||||
* Abstraction to keep track of which tags have been opened / closed as we traverse the tags in the document.
|
||||
*
|
||||
* Closing tags is tricky:
|
||||
* - Some tags have no end tag per spec. For example, there is no </img> tag per spec. Since we are making
|
||||
* startTag()/endTag() calls, we manufacture endTag() calls for these immediately after the startTag().
|
||||
* - We assume all end tags are optional and we pop tags off our stack as we encounter parent closing tags. This part
|
||||
* differs slightly from the behavior per spec: instead of closing an <option> tag when a following <option> tag
|
||||
* is seen, we close it when the parent closing tag (in practice <select>) is encountered.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class TagNameStack
|
||||
{
|
||||
/**
|
||||
* Regular expression that matches strings composed of all space characters, as defined in
|
||||
* https://infra.spec.whatwg.org/#ascii-whitespace, and in the various HTML parsing rules at
|
||||
* https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhtml.
|
||||
*
|
||||
* Note: Do not USE \s to match whitespace as this includes many other characters that HTML parsing does not
|
||||
* consider whitespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SPACE_REGEX = '/^[ \\f\\n\\r\\t]*$/';
|
||||
/**
|
||||
* Regular expression that matches the characters considered whitespace by the C++ HTML parser.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CPP_SPACE_REGEX = '/^[ \\f\\n\\r\\t\\v' . '\\x{00a0}\\x{1680}\\x{2000}-\\x{200a}\\x{2028}\\x{2029}\\x{202f}\\x{205f}\\x{3000}]*$/u';
|
||||
/**
|
||||
* The handler to manage the stack for.
|
||||
*
|
||||
* @var HtmlSaxHandler
|
||||
*/
|
||||
private $handler;
|
||||
/**
|
||||
* The current tag name and its parents.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
private $stack = [];
|
||||
/**
|
||||
* The current region within the document.
|
||||
*
|
||||
* @var TagRegion
|
||||
*/
|
||||
private $region;
|
||||
/**
|
||||
* Keeps track of the attributes from all body tags encountered within the document.
|
||||
*
|
||||
* @var array<ParsedAttribute>
|
||||
*/
|
||||
private $effectiveBodyAttributes = [];
|
||||
/**
|
||||
* TagNameStack constructor.
|
||||
*
|
||||
* @param HtmlSaxHandler $handler Handler to handle the HTML SAX parser events.
|
||||
*/
|
||||
public function __construct(HtmlSaxHandler $handler)
|
||||
{
|
||||
$this->handler = $handler;
|
||||
$this->region = TagRegion::PRE_DOCTYPE();
|
||||
}
|
||||
/**
|
||||
* Returns the attributes from all body tags within the document.
|
||||
*
|
||||
* @return array<ParsedAttribute>
|
||||
*/
|
||||
public function effectiveBodyAttributes()
|
||||
{
|
||||
return $this->effectiveBodyAttributes;
|
||||
}
|
||||
/**
|
||||
* Enter a tag, opening a scope for child tags. Entering a tag can close the previous tag or enter other tags (such
|
||||
* as opening a <body> tag when encountering a tag not allowed outside the body.
|
||||
*
|
||||
* @param ParsedTag $tag Tag that is being started.
|
||||
*/
|
||||
public function startTag(ParsedTag $tag)
|
||||
{
|
||||
// We only report the first body for each document - either a manufactured one, or the first one encountered.
|
||||
// However, we collect all attributes in $this->effectiveBodyAttributes.
|
||||
if ($tag->upperName() === Tag::BODY) {
|
||||
$this->effectiveBodyAttributes = \array_merge($this->effectiveBodyAttributes, $tag->attributes());
|
||||
}
|
||||
// This section deals with manufacturing <head>, </head>, and <body> tags if the document has left them out or
|
||||
// placed them in the wrong location.
|
||||
switch ($this->region->getValue()) {
|
||||
case TagRegion::PRE_DOCTYPE:
|
||||
if ($tag->upperName() === Tag::_DOCTYPE) {
|
||||
$this->region = TagRegion::PRE_HTML();
|
||||
} elseif ($tag->upperName() === Tag::HTML) {
|
||||
$this->region = TagRegion::PRE_HEAD();
|
||||
} elseif ($tag->upperName() === Tag::HEAD) {
|
||||
$this->region = TagRegion::IN_HEAD();
|
||||
} elseif ($tag->upperName() === Tag::BODY) {
|
||||
$this->region = TagRegion::IN_BODY();
|
||||
} elseif (!\in_array($tag->upperName(), Tag::STRUCTURE_TAGS, \true)) {
|
||||
if (\in_array($tag->upperName(), Tag::ELEMENTS_ALLOWED_IN_HEAD, \true)) {
|
||||
$this->startTag(new ParsedTag(Tag::HEAD));
|
||||
} else {
|
||||
$this->handler->markManufacturedBody();
|
||||
$this->startTag(new ParsedTag(Tag::BODY));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TagRegion::PRE_HTML:
|
||||
// Stray DOCTYPE/HTML tags are ignored, not emitted twice.
|
||||
if ($tag->upperName() === Tag::_DOCTYPE) {
|
||||
return;
|
||||
}
|
||||
if ($tag->upperName() === Tag::HTML) {
|
||||
$this->region = TagRegion::PRE_HEAD();
|
||||
} elseif ($tag->upperName() === Tag::HEAD) {
|
||||
$this->region = TagRegion::IN_HEAD();
|
||||
} elseif ($tag->upperName() === Tag::BODY) {
|
||||
$this->region = TagRegion::IN_BODY();
|
||||
} elseif (!\in_array($tag->upperName(), Tag::STRUCTURE_TAGS, \true)) {
|
||||
if (\in_array($tag->upperName(), Tag::ELEMENTS_ALLOWED_IN_HEAD, \true)) {
|
||||
$this->startTag(new ParsedTag(Tag::HEAD));
|
||||
} else {
|
||||
$this->handler->markManufacturedBody();
|
||||
$this->startTag(new ParsedTag(Tag::BODY));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TagRegion::PRE_HEAD:
|
||||
// Stray DOCTYPE/HTML tags are ignored, not emitted twice.
|
||||
if ($tag->upperName() === Tag::_DOCTYPE || $tag->upperName() === Tag::HTML) {
|
||||
return;
|
||||
}
|
||||
if ($tag->upperName() === Tag::HEAD) {
|
||||
$this->region = TagRegion::IN_HEAD();
|
||||
} elseif ($tag->upperName() === Tag::BODY) {
|
||||
$this->region = TagRegion::IN_BODY();
|
||||
} elseif (!\in_array($tag->upperName(), Tag::STRUCTURE_TAGS, \true)) {
|
||||
if (\in_array($tag->upperName(), Tag::ELEMENTS_ALLOWED_IN_HEAD, \true)) {
|
||||
$this->startTag(new ParsedTag(Tag::HEAD));
|
||||
} else {
|
||||
$this->handler->markManufacturedBody();
|
||||
$this->startTag(new ParsedTag(Tag::BODY));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TagRegion::IN_HEAD:
|
||||
// Stray DOCTYPE/HTML/HEAD tags are ignored, not emitted twice.
|
||||
if ($tag->upperName() === Tag::_DOCTYPE || $tag->upperName() === Tag::HTML || $tag->upperName() === Tag::HEAD) {
|
||||
return;
|
||||
}
|
||||
if (!\in_array($tag->upperName(), Tag::ELEMENTS_ALLOWED_IN_HEAD, \true)) {
|
||||
$this->endTag(new ParsedTag(Tag::HEAD));
|
||||
if ($tag->upperName() !== Tag::BODY) {
|
||||
$this->handler->markManufacturedBody();
|
||||
$this->startTag(new ParsedTag(Tag::BODY));
|
||||
} else {
|
||||
$this->region = TagRegion::IN_BODY();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TagRegion::PRE_BODY:
|
||||
// Stray DOCTYPE/HTML/HEAD tags are ignored, not emitted twice.
|
||||
if ($tag->upperName() === Tag::_DOCTYPE || $tag->upperName() === Tag::HTML || $tag->upperName() === Tag::HEAD) {
|
||||
return;
|
||||
}
|
||||
if ($tag->upperName() !== Tag::BODY) {
|
||||
$this->handler->markManufacturedBody();
|
||||
$this->startTag(new ParsedTag(Tag::BODY));
|
||||
} else {
|
||||
$this->region = TagRegion::IN_BODY();
|
||||
}
|
||||
break;
|
||||
case TagRegion::IN_BODY:
|
||||
// Stray DOCTYPE/HTML/HEAD tags are ignored, not emitted twice.
|
||||
if ($tag->upperName() === Tag::_DOCTYPE || $tag->upperName() === Tag::HTML || $tag->upperName() === Tag::HEAD) {
|
||||
return;
|
||||
}
|
||||
if ($tag->upperName() === Tag::BODY) {
|
||||
// We only report the first body for each document - either a manufactured one, or the first one
|
||||
// encountered.
|
||||
return;
|
||||
}
|
||||
if ($tag->upperName() === Tag::SVG) {
|
||||
$this->region = TagRegion::IN_SVG();
|
||||
break;
|
||||
}
|
||||
// Check implicit tag closing due to opening tags.
|
||||
if (\count($this->stack) > 0) {
|
||||
$parentTagName = $this->stack[\count($this->stack) - 1];
|
||||
// <p> tags can be implicitly closed by certain other start tags.
|
||||
// See https://www.w3.org/TR/html-markup/p.html.
|
||||
if ($parentTagName === Tag::P && \in_array($tag->upperName(), Tag::P_CLOSING_TAGS, \true)) {
|
||||
$this->endTag(new ParsedTag(Tag::P));
|
||||
// <dd> and <dt> tags can be implicitly closed by other <dd> and <dt> tags.
|
||||
// See https://www.w3.org/TR/html-markup/dd.html.
|
||||
} elseif (($parentTagName === Tag::DD || $parentTagName === Tag::DT) && ($tag->upperName() === Tag::DD || $tag->upperName() === Tag::DT)) {
|
||||
$this->endTag(new ParsedTag($parentTagName));
|
||||
// <li> tags can be implicitly closed by other <li> tags.
|
||||
// See https://www.w3.org/TR/html-markup/li.html.
|
||||
} elseif ($parentTagName === Tag::LI && $tag->upperName() === Tag::LI) {
|
||||
$this->endTag(new ParsedTag(Tag::LI));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TagRegion::IN_SVG:
|
||||
$this->handler->startTag($tag);
|
||||
$this->stack[] = $tag->upperName();
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$this->handler->startTag($tag);
|
||||
if (\in_array($tag->upperName(), Tag::SELF_CLOSING_TAGS, \true)) {
|
||||
// Ignore attributes in end tags.
|
||||
$this->handler->endTag(new ParsedTag($tag->upperName()));
|
||||
} else {
|
||||
$this->stack[] = $tag->upperName();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Callback for pcdata.
|
||||
*
|
||||
* Some text nodes can trigger the start of the body region.
|
||||
*
|
||||
* @param string $text Text of the text node.
|
||||
*/
|
||||
public function pcdata($text)
|
||||
{
|
||||
if (Str::regexMatch(self::SPACE_REGEX, $text)) {
|
||||
// Only ASCII whitespace; this can be ignored for validator's purposes.
|
||||
} elseif (Str::regexMatch(self::CPP_SPACE_REGEX, $text)) {
|
||||
// Non-ASCII whitespace; if this occurs outside <body>, output a manufactured-body error. Do not create
|
||||
// implicit tags, in order to match the behavior of the buggy C++ parser. It just so happens this is also
|
||||
// good UX, since the subsequent validation errors caused by the implicit tags are unhelpful.
|
||||
switch ($this->region->getValue()) {
|
||||
// Fallthroughs intentional.
|
||||
case TagRegion::PRE_DOCTYPE:
|
||||
case TagRegion::PRE_HTML:
|
||||
case TagRegion::PRE_HEAD:
|
||||
case TagRegion::IN_HEAD:
|
||||
case TagRegion::PRE_BODY:
|
||||
$this->handler->markManufacturedBody();
|
||||
}
|
||||
} else {
|
||||
// Non-whitespace text; if this occurs outside <body>, output a manufactured-body error and create the
|
||||
// necessary implicit tags.
|
||||
switch ($this->region->getValue()) {
|
||||
case TagRegion::PRE_DOCTYPE:
|
||||
// Doctype is not manufactured, fallthrough intentional.
|
||||
case TagRegion::PRE_HTML:
|
||||
$this->startTag(new ParsedTag(Tag::HTML));
|
||||
// Fallthrough intentional.
|
||||
case TagRegion::PRE_HEAD:
|
||||
$this->startTag(new ParsedTag(Tag::HEAD));
|
||||
// Fallthrough intentional.
|
||||
case TagRegion::IN_HEAD:
|
||||
$this->endTag(new ParsedTag(Tag::HEAD));
|
||||
// Fallthrough intentional.
|
||||
case TagRegion::PRE_BODY:
|
||||
$this->handler->markManufacturedBody();
|
||||
$this->startTag(new ParsedTag(Tag::BODY));
|
||||
}
|
||||
}
|
||||
$this->handler->pcdata($text);
|
||||
}
|
||||
/**
|
||||
* Upon exiting a tag, validation for the current matcher is triggered, e.g. for checking that the tag had some
|
||||
* specified number of children.
|
||||
*
|
||||
* @param ParsedTag $tag Tag that is being exited.
|
||||
*/
|
||||
public function endTag($tag)
|
||||
{
|
||||
if ($this->region->equals(TagRegion::IN_HEAD()) && $tag->upperName() === Tag::HEAD) {
|
||||
$this->region = TagRegion::PRE_BODY();
|
||||
}
|
||||
/*
|
||||
* We ignore close body tags (</body) and instead insert them when their outer scope is closed (/html). This is
|
||||
* closer to how a browser parser works. The idea here is if other tags are found after the <body>, (ex: <div>)
|
||||
* which are only allowed in the <body>, we will effectively move them into the body section.
|
||||
*/
|
||||
if ($tag->upperName() === Tag::BODY) {
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* We look for tag.upperName() from the end. If we can find it, we pop everything from thereon off the stack. If
|
||||
* we can't find it, we don't bother with closing the tag, since it doesn't have a matching open tag, though in
|
||||
* practice the HtmlParser class will have already manufactured a start tag.
|
||||
*/
|
||||
for ($index = \count($this->stack) - 1; $index >= 0; $index--) {
|
||||
if ($this->stack[$index] === $tag->upperName()) {
|
||||
while (\count($this->stack) > $index) {
|
||||
if ($this->stack[\count($this->stack) - 1] === Tag::SVG) {
|
||||
$this->region = TagRegion::IN_BODY();
|
||||
}
|
||||
$this->handler->endTag(new ParsedTag(\array_pop($this->stack)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This method is called when we're done with the document.
|
||||
*
|
||||
* Normally, the parser should actually close the tags, but just in case it doesn't this easy-enough method will
|
||||
* take care of it.
|
||||
*/
|
||||
public function exitRemainingTags()
|
||||
{
|
||||
while (\count($this->stack) > 0) {
|
||||
$this->handler->endTag(new ParsedTag(\array_pop($this->stack)));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html\Parser;
|
||||
|
||||
use Google\Web_Stories_Dependencies\AmpProject\FakeEnum;
|
||||
/**
|
||||
* Enum for denoting to which structural region a tag belongs.
|
||||
*
|
||||
*
|
||||
* @method static TagRegion PRE_DOCTYPE()
|
||||
* @method static TagRegion PRE_HTML()
|
||||
* @method static TagRegion PRE_HEAD()
|
||||
* @method static TagRegion IN_HEAD()
|
||||
* @method static TagRegion PRE_BODY()
|
||||
* @method static TagRegion IN_BODY()
|
||||
* @method static TagRegion IN_SVG()
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
final class TagRegion extends FakeEnum
|
||||
{
|
||||
const PRE_DOCTYPE = 0;
|
||||
const PRE_HTML = 1;
|
||||
const PRE_HEAD = 2;
|
||||
const IN_HEAD = 3;
|
||||
const PRE_BODY = 4;
|
||||
// After closing <head> tag, but before open <body> tag.
|
||||
const IN_BODY = 5;
|
||||
const IN_SVG = 6;
|
||||
// We don't track the region after the closing body tag.
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html;
|
||||
|
||||
/**
|
||||
* Interface with constants for the different request destinations that are supported.
|
||||
*
|
||||
* For the purposes of the AMP implementation, we are only interested in the
|
||||
* request destinations that are valid values for the 'as' attribute in preloads.
|
||||
*
|
||||
* Full list of request destinations:
|
||||
* @see https://fetch.spec.whatwg.org/#concept-request-destination
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface RequestDestination
|
||||
{
|
||||
/**
|
||||
* Audio file, as typically used in <audio>.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUDIO = 'audio';
|
||||
/**
|
||||
* An HTML document intended to be embedded by a <frame> or <iframe>.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DOCUMENT = 'document';
|
||||
/**
|
||||
* A resource to be embedded inside an <embed> element.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const EMBED = 'embed';
|
||||
/**
|
||||
* Resource to be accessed by a fetch or XHR request, such as an ArrayBuffer or JSON file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FETCH = 'fetch';
|
||||
/**
|
||||
* Font file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FONT = 'font';
|
||||
/**
|
||||
* Image file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const IMAGE = 'image';
|
||||
/**
|
||||
* A resource to be embedded inside an <object> element.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const OBJECT = 'object';
|
||||
/**
|
||||
* JavaScript file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SCRIPT = 'script';
|
||||
/**
|
||||
* CSS stylesheet.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STYLE = 'style';
|
||||
/**
|
||||
* WebVTT file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TRACK = 'track';
|
||||
/**
|
||||
* A JavaScript web worker or shared worker.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const WORKER = 'worker';
|
||||
/**
|
||||
* Video file, as typically used in <video>.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VIDEO = 'video';
|
||||
}
|
454
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Html/Role.php
vendored
Normal file
454
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Html/Role.php
vendored
Normal file
@@ -0,0 +1,454 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html;
|
||||
|
||||
/**
|
||||
* Interface with constants for the different types of accessibility roles.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface Role
|
||||
{
|
||||
/**
|
||||
* A message with an alert or error information.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ALERT = 'alert';
|
||||
/**
|
||||
* A separate window with an alert or error information.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ALERTDIALOG = 'alertdialog';
|
||||
/**
|
||||
* A software unit executing a set of tasks for its users.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const APPLICATION = 'application';
|
||||
/**
|
||||
* A section of a page that could easily stand on its own on a page, in a document, or on a website.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ARTICLE = 'article';
|
||||
/**
|
||||
* A region that contains mostly site-oriented content, rather than page-specific content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BANNER = 'banner';
|
||||
/**
|
||||
* Allows for user-triggered actions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BUTTON = 'button';
|
||||
/**
|
||||
* An element as being a cell in a tabular container that does not contain column or row header information.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CELL = 'cell';
|
||||
/**
|
||||
* A control that has three possible values, (true, false, mixed).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CHECKBOX = 'checkbox';
|
||||
/**
|
||||
* A table cell containing header information for a column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COLUMNHEADER = 'columnheader';
|
||||
/**
|
||||
* Combobox is a presentation of a select, where users can type to locate a selected item.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COMBOBOX = 'combobox';
|
||||
/**
|
||||
* A supporting section of the document, designed to be complementary to the main content at a similar level in the
|
||||
* DOM hierarchy, but remains meaningful when separated from the main content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COMPLEMENTARY = 'complementary';
|
||||
/**
|
||||
* A large perceivable region that contains information about the parent document.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CONTENTINFO = 'contentinfo';
|
||||
/**
|
||||
* A definition of a term or concept.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DEFINITION = 'definition';
|
||||
/**
|
||||
* Descriptive content for a page element which references this element via describedby.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DESCRIPTION = 'description';
|
||||
/**
|
||||
* A dialog is a small application window that sits above the application and is designed to interrupt the current
|
||||
* processing of an application in order to prompt the user to enter information or require a response.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DIALOG = 'dialog';
|
||||
/**
|
||||
* A list of references to members of a single group.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DIRECTORY = 'directory';
|
||||
/**
|
||||
* Content that contains related information, such as a book.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DOCUMENT = 'document';
|
||||
/**
|
||||
* A scrollable list of articles where scrolling may cause articles to be added to or removed from either end of the
|
||||
* list.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FEED = 'feed';
|
||||
/**
|
||||
* A figure inside page content where appropriate semantics do not already exist.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FIGURE = 'figure';
|
||||
/**
|
||||
* A landmark region that contains a collection of items and objects that, as a whole, combine to create a form.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FORM = 'form';
|
||||
/**
|
||||
* A grid contains cells of tabular data arranged in rows and columns (e.g., a table).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const GRID = 'grid';
|
||||
/**
|
||||
* A gridcell is a table cell in a grid. Gridcells may be active, editable, and selectable. Cells may have
|
||||
* relationships such as controls to address the application of functional relationships.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const GRIDCELL = 'gridcell';
|
||||
/**
|
||||
* A group is a section of user interface objects which would not be included in a page summary or table of contents
|
||||
* by an assistive technology. See region for sections of user interface objects that should be included in a page
|
||||
* summary or table of contents.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const GROUP = 'group';
|
||||
/**
|
||||
* A heading for a section of the page.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HEADING = 'heading';
|
||||
/**
|
||||
* An img is a container for a collection elements that form an image.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const IMG = 'img';
|
||||
/**
|
||||
* Interactive reference to a resource (note, that in XHTML 2.0 any element can have an href attribute and thus be a
|
||||
* link)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LINK = 'link';
|
||||
/**
|
||||
* Group of non-interactive list items. Lists contain children whose role is listitem.
|
||||
*
|
||||
* Uses an underscore as "list" is a conflicting PHP keyword.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LIST_ = 'list';
|
||||
/**
|
||||
* A list box is a widget that allows the user to select one or more items from a list. Items within the list are
|
||||
* static and may contain images. List boxes contain children whose role is option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LISTBOX = 'listbox';
|
||||
/**
|
||||
* A single item in a list.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LISTITEM = 'listitem';
|
||||
/**
|
||||
* A region where new information is added and old information may disappear such as chat logs, messaging, game log
|
||||
* or an error log. In contrast to other regions, in this role there is a relationship between the arrival of new
|
||||
* items in the log and the reading order. The log contains a meaningful sequence and new information is added only
|
||||
* to the end of the log, not at arbitrary points.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LOG = 'log';
|
||||
/**
|
||||
* The main content of a document.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MAIN = 'main';
|
||||
/**
|
||||
* A marquee is used to scroll text across the page.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MARQUEE = 'marquee';
|
||||
/**
|
||||
* Content that represents a mathematical expression.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MATH = 'math';
|
||||
/**
|
||||
* Offers a list of choices to the user.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MENU = 'menu';
|
||||
/**
|
||||
* A menubar is a container of menu items. Each menu item may activate a new sub-menu. Navigation behavior should be
|
||||
* similar to the typical menu bar graphical user interface.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MENUBAR = 'menubar';
|
||||
/**
|
||||
* A link in a menu. This is an option in a group of choices contained in a menu.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MENUITEM = 'menuitem';
|
||||
/**
|
||||
* Defines a menuitem which is checkable (tri-state).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MENUITEMCHECKBOX = 'menuitemcheckbox';
|
||||
/**
|
||||
* Indicates a menu item which is part of a group of menuitemradio roles.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MENUITEMRADIO = 'menuitemradio';
|
||||
/**
|
||||
* A collection of navigational elements (usually links) for navigating the document or related documents.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NAVIGATION = 'navigation';
|
||||
/**
|
||||
* An element whose implicit native role semantics will not be mapped to the accessibility API.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NONE = 'none';
|
||||
/**
|
||||
* A section whose content is parenthetic or ancillary to the main content of the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NOTE = 'note';
|
||||
/**
|
||||
* A selectable item in a list represented by a select.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const OPTION = 'option';
|
||||
/**
|
||||
* An element whose role is presentational does not need to be mapped to the accessibility API.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PRESENTATION = 'presentation';
|
||||
/**
|
||||
* Used by applications for tasks that take a long time to execute, to show the execution progress.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PROGRESSBAR = 'progressbar';
|
||||
/**
|
||||
* A radio is an option in single-select list. Only one radio control in a radiogroup can be selected at the same
|
||||
* time.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const RADIO = 'radio';
|
||||
/**
|
||||
* A group of radio controls.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const RADIOGROUP = 'radiogroup';
|
||||
/**
|
||||
* Region is a large perceivable section on the web page.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const REGION = 'region';
|
||||
/**
|
||||
* A row of table cells.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ROW = 'row';
|
||||
/**
|
||||
* A structure containing one or more row elements in a tabular container.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ROWGROUP = 'rowgroup';
|
||||
/**
|
||||
* A table cell containing header information for a row.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ROWHEADER = 'rowheader';
|
||||
/**
|
||||
* Scroll bar to navigate the horizontal or vertical dimensions of the page.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SCROLLBAR = 'scrollbar';
|
||||
/**
|
||||
* A section of the page used to search the page, site, or collection of sites.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SEARCH = 'search';
|
||||
/**
|
||||
* An entry field to provide a query to search for.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SEARCHBOX = 'searchbox';
|
||||
/**
|
||||
* A line or bar that separates and distinguishes sections of content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SEPARATOR = 'separator';
|
||||
/**
|
||||
* A user input where the user selects an input in a given range. This form of range expects an analog keyboard
|
||||
* interface.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SLIDER = 'slider';
|
||||
/**
|
||||
* A form of Range that expects a user selecting from discrete choices.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SPINBUTTON = 'spinbutton';
|
||||
/**
|
||||
* This is a container for process advisory information to give feedback to the user.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS = 'status';
|
||||
/**
|
||||
* Functionally identical to a checkbox but represents the states "on"/"off" instead of "checked"/"unchecked".
|
||||
*
|
||||
* Uses an underscore as "list" is a conflicting PHP keyword.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SWITCH_ = 'switch';
|
||||
/**
|
||||
* A header for a tabpanel.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TAB = 'tab';
|
||||
/**
|
||||
* A non-interactive table structure containing data arranged in rows and columns.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'table';
|
||||
/**
|
||||
* A list of tabs, which are references to tabpanels.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLIST = 'tablist';
|
||||
/**
|
||||
* Tabpanel is a container for the resources associated with a tab.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABPANEL = 'tabpanel';
|
||||
/**
|
||||
* A word or phrase with a corresponding definition.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TERM = 'term';
|
||||
/**
|
||||
* Inputs that allow free-form text as their value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TEXTBOX = 'textbox';
|
||||
/**
|
||||
* A numerical counter which indicates an amount of elapsed time from a start point, or the time remaining until an
|
||||
* end point.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TIMER = 'timer';
|
||||
/**
|
||||
* A toolbar is a collection of commonly used functions represented in compact visual form.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TOOLBAR = 'toolbar';
|
||||
/**
|
||||
* A popup that displays a description for an element when a user passes over or rests on that element. Supplement
|
||||
* to the normal tooltip processing of the user agent.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TOOLTIP = 'tooltip';
|
||||
/**
|
||||
* A form of a list having groups inside groups, where sub trees can be collapsed and expanded.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TREE = 'tree';
|
||||
/**
|
||||
* A grid whose rows can be expanded and collapsed in the same manner as for a tree.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TREEGRID = 'treegrid';
|
||||
/**
|
||||
* An option item of a tree. This is an element within a tree that may be expanded or collapsed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TREEITEM = 'treeitem';
|
||||
}
|
233
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Html/Tag.php
vendored
Normal file
233
wp-content/plugins/web-stories/third-party/vendor/ampproject/amp-toolbox/src/Html/Tag.php
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace Google\Web_Stories_Dependencies\AmpProject\Html;
|
||||
|
||||
/**
|
||||
* Interface with constants for the different types of tags.
|
||||
*
|
||||
* @package ampproject/amp-toolbox
|
||||
*/
|
||||
interface Tag
|
||||
{
|
||||
const A = 'a';
|
||||
const ABBR = 'abbr';
|
||||
const ACRONYM = 'acronym';
|
||||
const ADDRESS = 'address';
|
||||
const APPLET = 'applet';
|
||||
const AREA = 'area';
|
||||
const ARTICLE = 'article';
|
||||
const ASIDE = 'aside';
|
||||
const AUDIO = 'audio';
|
||||
const B = 'b';
|
||||
const BASE = 'base';
|
||||
const BASEFONT = 'basefont';
|
||||
const BDI = 'bdi';
|
||||
const BDO = 'bdo';
|
||||
const BGSOUND = 'bgsound';
|
||||
const BIG = 'big';
|
||||
const BLOCKQUOTE = 'blockquote';
|
||||
const BODY = 'body';
|
||||
const BR = 'br';
|
||||
const BUTTON = 'button';
|
||||
const CANVAS = 'canvas';
|
||||
const CAPTION = 'caption';
|
||||
const CENTER = 'center';
|
||||
const CIRCLE = 'circle';
|
||||
const CITE = 'cite';
|
||||
const CLIPPATH = 'clippath';
|
||||
const CODE = 'code';
|
||||
const COL = 'col';
|
||||
const COLGROUP = 'colgroup';
|
||||
const DATA = 'data';
|
||||
const DATALIST = 'datalist';
|
||||
const DD = 'dd';
|
||||
const DEFS = 'defs';
|
||||
const DEL = 'del';
|
||||
const DESC = 'desc';
|
||||
const DETAILS = 'details';
|
||||
const DFN = 'dfn';
|
||||
const DIR = 'dir';
|
||||
const DIV = 'div';
|
||||
const DL = 'dl';
|
||||
const DT = 'dt';
|
||||
const ELLIPSE = 'ellipse';
|
||||
const EM = 'em';
|
||||
const EMBED = 'embed';
|
||||
const FEBLEND = 'feblend';
|
||||
const FECOLORMATRIX = 'fecolormatrix';
|
||||
const FECOMPONENTTRANSFER = 'fecomponenttransfer';
|
||||
const FECOMPOSITE = 'fecomposite';
|
||||
const FECONVOLVEMATRIX = 'feconvolvematrix';
|
||||
const FEDIFFUSELIGHTING = 'fediffuselighting';
|
||||
const FEDISPLACEMENTMAP = 'fedisplacementmap';
|
||||
const FEDISTANTLIGHT = 'fedistantlight';
|
||||
const FEDROPSHADOW = 'fedropshadow';
|
||||
const FEFLOOD = 'feflood';
|
||||
const FEFUNCA = 'fefunca';
|
||||
const FEFUNCB = 'fefuncb';
|
||||
const FEFUNCG = 'fefuncg';
|
||||
const FEFUNCR = 'fefuncr';
|
||||
const FEGAUSSIANBLUR = 'fegaussianblur';
|
||||
const FEMERGE = 'femerge';
|
||||
const FEMERGENODE = 'femergenode';
|
||||
const FEMORPHOLOGY = 'femorphology';
|
||||
const FEOFFSET = 'feoffset';
|
||||
const FEPOINTLIGHT = 'fepointlight';
|
||||
const FESPECULARLIGHTING = 'fespecularlighting';
|
||||
const FESPOTLIGHT = 'fespotlight';
|
||||
const FETILE = 'fetile';
|
||||
const FETURBULENCE = 'feturbulence';
|
||||
const FIELDSET = 'fieldset';
|
||||
const FIGCAPTION = 'figcaption';
|
||||
const FIGURE = 'figure';
|
||||
const FILTER = 'filter';
|
||||
const FONT = 'font';
|
||||
const FOOTER = 'footer';
|
||||
const FORM = 'form';
|
||||
const FRAME = 'frame';
|
||||
const FRAMESET = 'frameset';
|
||||
const G = 'g';
|
||||
const GLYPH = 'glyph';
|
||||
const GLYPHREF = 'glyphref';
|
||||
const H1 = 'h1';
|
||||
const H2 = 'h2';
|
||||
const H3 = 'h3';
|
||||
const H4 = 'h4';
|
||||
const H5 = 'h5';
|
||||
const H6 = 'h6';
|
||||
const HEAD = 'head';
|
||||
const HEADER = 'header';
|
||||
const HGROUP = 'hgroup';
|
||||
const HKERN = 'hkern';
|
||||
const HR = 'hr';
|
||||
const HTML = 'html';
|
||||
const I = 'i';
|
||||
const IFRAME = 'iframe';
|
||||
const IMAGE = 'image';
|
||||
const IMG = 'img';
|
||||
const INPUT = 'input';
|
||||
const INS = 'ins';
|
||||
const ISINDEX = 'isindex';
|
||||
const KBD = 'kbd';
|
||||
const KEYGEN = 'keygen';
|
||||
const LABEL = 'label';
|
||||
const LEGEND = 'legend';
|
||||
const LI = 'li';
|
||||
const LINE = 'line';
|
||||
const LINEARGRADIENT = 'lineargradient';
|
||||
const LINK = 'link';
|
||||
const LISTING = 'listing';
|
||||
const MAIN = 'main';
|
||||
const MAP = 'map';
|
||||
const MARK = 'mark';
|
||||
const MARKER = 'marker';
|
||||
const MASK = 'mask';
|
||||
const MENU = 'menu';
|
||||
const META = 'meta';
|
||||
const METADATA = 'metadata';
|
||||
const METER = 'meter';
|
||||
const MULTICOL = 'multicol';
|
||||
const NAV = 'nav';
|
||||
const NEXTID = 'nextid';
|
||||
const NOBR = 'nobr';
|
||||
const NOFRAMES = 'noframes';
|
||||
const NOSCRIPT = 'noscript';
|
||||
const O_P = 'o:p';
|
||||
// @todo Will this be usable at present given PHP DOM?
|
||||
const OBJECT = 'object';
|
||||
const OL = 'ol';
|
||||
const OPTGROUP = 'optgroup';
|
||||
const OPTION = 'option';
|
||||
const OUTPUT = 'output';
|
||||
const P = 'p';
|
||||
const PARAM = 'param';
|
||||
const PATH = 'path';
|
||||
const PATTERN = 'pattern';
|
||||
const PICTURE = 'picture';
|
||||
const POLYGON = 'polygon';
|
||||
const POLYLINE = 'polyline';
|
||||
const PRE = 'pre';
|
||||
const PROGRESS = 'progress';
|
||||
const Q = 'Q';
|
||||
const RADIALGRADIENT = 'radialgradient';
|
||||
const RB = 'rb';
|
||||
const RECT = 'rect';
|
||||
const RP = 'rp';
|
||||
const RT = 'rt';
|
||||
const RTC = 'rtc';
|
||||
const RUBY = 'ruby';
|
||||
const S = 's';
|
||||
const SAMP = 'samp';
|
||||
const SCRIPT = 'script';
|
||||
const SECTION = 'section';
|
||||
const SELECT = 'select';
|
||||
const SLOT = 'slot';
|
||||
const SMALL = 'small';
|
||||
const SOLIDCOLOR = 'solidcolor';
|
||||
const SOURCE = 'source';
|
||||
const SPACER = 'spacer';
|
||||
const SPAN = 'span';
|
||||
const STOP = 'stop';
|
||||
const STRIKE = 'strike';
|
||||
const STRONG = 'strong';
|
||||
const STYLE = 'style';
|
||||
const SUB = 'sub';
|
||||
const SUMMARY = 'summary';
|
||||
const SUP = 'sup';
|
||||
const SVG = 'svg';
|
||||
const SWITCH_ = 'switch';
|
||||
const SYMBOL = 'symbol';
|
||||
const TABLE = 'table';
|
||||
const TBODY = 'tbody';
|
||||
const TD = 'td';
|
||||
const TEMPLATE = 'template';
|
||||
const TEXT = 'text';
|
||||
const TEXTAREA = 'textarea';
|
||||
const TEXTPATH = 'textpath';
|
||||
const TFOOT = 'tfoot';
|
||||
const TH = 'th';
|
||||
const THEAD = 'thead';
|
||||
const TIME = 'time';
|
||||
const TITLE = 'title';
|
||||
const TR = 'tr';
|
||||
const TRACK = 'track';
|
||||
const TREF = 'tref';
|
||||
const TSPAN = 'tspan';
|
||||
const TT = 'tt';
|
||||
const U = 'u';
|
||||
const UL = 'ul';
|
||||
const USE_ = 'use';
|
||||
const VAR_ = 'var';
|
||||
const VIDEO = 'video';
|
||||
const VIEW = 'view';
|
||||
const VKERN = 'vkern';
|
||||
const WBR = 'wbr';
|
||||
const _DOCTYPE = '!doctype';
|
||||
/**
|
||||
* HTML elements that are self-closing.
|
||||
*
|
||||
* @link https://www.w3.org/TR/html5/syntax.html#serializing-html-fragments
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const SELF_CLOSING_TAGS = [self::AREA, self::BASE, self::BASEFONT, self::BGSOUND, self::BR, self::COL, self::EMBED, self::FRAME, self::HR, self::IMG, self::INPUT, self::KEYGEN, self::LINK, self::META, self::PARAM, self::SOURCE, self::TRACK, self::WBR];
|
||||
/**
|
||||
* List of elements allowed in head.
|
||||
*
|
||||
* @link https://github.com/ampproject/amphtml/blob/445d6e3be8a5063e2738c6f90fdcd57f2b6208be/validator/engine/htmlparser.js#L83-L100
|
||||
* @link https://www.w3.org/TR/html5/document-metadata.html
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const ELEMENTS_ALLOWED_IN_HEAD = [self::TITLE, self::BASE, self::LINK, self::META, self::STYLE, self::NOSCRIPT, self::SCRIPT];
|
||||
/**
|
||||
* Set of HTML tags which should never trigger an implied open of a <head> or <body> element.
|
||||
*/
|
||||
const STRUCTURE_TAGS = [self::_DOCTYPE, self::HTML, self::HEAD, self::BODY];
|
||||
/**
|
||||
* The set of HTML tags whose presence will implicitly close a <p> element.
|
||||
* For example '<p>foo<h1>bar</h1>' should parse the same as '<p>foo</p><h1>bar</h1>'.
|
||||
* @link https://www.w3.org/TR/html-markup/p.html
|
||||
*/
|
||||
const P_CLOSING_TAGS = [self::ADDRESS, self::ARTICLE, self::ASIDE, self::BLOCKQUOTE, self::DIR, self::DL, self::FIELDSET, self::FOOTER, self::FORM, self::H1, self::H2, self::H3, self::H4, self::H5, self::H6, self::HEADER, self::HR, self::MENU, self::NAV, self::OL, self::P, self::PRE, self::SECTION, self::TABLE, self::UL];
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user