Skip to content
Snippets Groups Projects
Commit 67e14f27 authored by Luhur Abdi Rizal's avatar Luhur Abdi Rizal Committed by Mark Dorison
Browse files

Issue #3265416 by MustangGB: Error when using markdown module with PHP 8.1 on Drupal 9.3

parent b63aa95c
No related branches found
No related tags found
1 merge request!17Issue #3265416: Error when using markdown module with PHP 8.1 on Drupal 9.3
......@@ -244,7 +244,7 @@ abstract class AnnotationObject extends AnnotationBase implements \ArrayAccess,
/**
* {@inheritdoc}
*/
public function getIterator() {
public function getIterator(): \Traversable {
$iterator = new \ArrayIterator($this);
foreach ($this->_deprecated as $key => $value) {
$iterator->offsetSet($key, $value);
......@@ -303,7 +303,7 @@ abstract class AnnotationObject extends AnnotationBase implements \ArrayAccess,
/**
* {@inheritdoc}
*/
public function offsetExists($offset) {
public function offsetExists($offset): bool {
if (array_key_exists($offset, $this->_deprecatedProperties)) {
return isset($this->_deprecated[$offset]);
}
......@@ -312,7 +312,10 @@ abstract class AnnotationObject extends AnnotationBase implements \ArrayAccess,
/**
* {@inheritdoc}
*
* @todo add "mixed" return type as soon as Drupal 9.5 is no longer supported.
*/
#[\ReturnTypeWillChange]
public function &offsetGet($offset) {
$value = NULL;
if (array_key_exists($offset, $this->_deprecatedProperties)) {
......@@ -330,7 +333,7 @@ abstract class AnnotationObject extends AnnotationBase implements \ArrayAccess,
/**
* {@inheritdoc}
*/
public function offsetSet($offset, $value = NULL) {
public function offsetSet($offset, $value = NULL): void {
if (array_key_exists($offset, $this->_deprecatedProperties)) {
$this->_deprecated[$offset] = $this->normalizeValue($value);
$this->triggerDeprecation($offset);
......@@ -343,7 +346,7 @@ abstract class AnnotationObject extends AnnotationBase implements \ArrayAccess,
/**
* {@inheritdoc}
*/
public function offsetUnset($offset) {
public function offsetUnset($offset): void {
if (array_key_exists($offset, $this->_deprecatedProperties)) {
unset($this->_deprecated[$offset]);
}
......
......@@ -207,7 +207,7 @@ class Identifier implements MarkupInterface {
/**
* {@inheritdoc}
*/
public function jsonSerialize() {
public function jsonSerialize(): string {
return $this->value;
}
......
......@@ -32,6 +32,7 @@ use Drupal\markdown\Traits\ParserAwareTrait;
use Drupal\markdown\Util\FilterAwareInterface;
use Drupal\markdown\Util\FilterFormatAwareInterface;
use Drupal\markdown\Util\FilterHtml;
use Drupal\markdown\Util\FormHelper;
use Drupal\markdown\Util\ParserAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -644,7 +645,7 @@ class ParserConfigurationForm extends FormBase implements FilterAwareInterface,
],
];
$renderStrategySubform['custom_allowed_html']['#description'] = $this->moreInfo($renderStrategySubform['custom_allowed_html']['#description'], RenderStrategyInterface::DOCUMENTATION_URL);
FormTrait::resetToDefault($renderStrategySubform['custom_allowed_html'], 'custom_allowed_html', '', $renderStrategySubformState);
FormHelper::resetToDefault($renderStrategySubform['custom_allowed_html'], 'custom_allowed_html', '', $renderStrategySubformState);
$renderStrategySubformState->addElementState($renderStrategySubform['custom_allowed_html'], 'visible', 'type', ['value' => RenderStrategyInterface::FILTER_OUTPUT]);
return $element;
......
......@@ -12,6 +12,7 @@ use Drupal\markdown\Plugin\Markdown\ParserInterface;
use Drupal\markdown\Plugin\Markdown\SettingsInterface;
use Drupal\markdown\Traits\FormTrait;
use Drupal\markdown\Traits\SettingsTrait;
use Drupal\markdown\Util\FormHelper;
use Drupal\markdown\Util\KeyValuePipeConverter;
/**
......@@ -107,7 +108,7 @@ class ExternalLinkExtension extends BaseExtension implements AllowedHtmlInterfac
'#description' => $this->t('Defines a whitelist of hosts which are considered non-external and should not receive the external link treatment. This can be a single host name, like <code>example.com</code>, which must match exactly. Wildcard matching is also supported using regular expression like <code>/(^|\.)example\.com$/</code>. Note that you must use <code>/</code> characters to delimit your regex. By default, if no internal hosts are provided, all links will be considered external. One host per line.'),
], $form_state, '\Drupal\markdown\Util\KeyValuePipeConverter::denormalizeNoKeys');
$element['token'] = FormTrait::createTokenBrowser();
$element['token'] = FormHelper::createTokenBrowser();
$element += $this->createSettingElement('html_class', [
'#type' => 'textfield',
......
......@@ -96,7 +96,7 @@ class ParsedMarkdown implements ParsedMarkdownInterface {
/**
* {@inheritdoc}
*/
public function count() {
public function count(): int {
return $this->getSize();
}
......@@ -158,7 +158,7 @@ class ParsedMarkdown implements ParsedMarkdownInterface {
/**
* {@inheritdoc}
*/
public function jsonSerialize() {
public function jsonSerialize(): string {
return $this->__toString();
}
......@@ -183,18 +183,7 @@ class ParsedMarkdown implements ParsedMarkdownInterface {
* {@inheritdoc}
*/
public function serialize() {
$data['object'] = serialize(get_object_vars($this));
// Determine if PHP has gzip capabilities.
$data['gzip'] = extension_loaded('zlib');
// Compress and encode the markdown and html output.
if ($data['gzip']) {
/* @noinspection PhpComposerExtensionStubsInspection */
$data['object'] = base64_encode(gzencode($data['object'], 9));
}
return serialize($data);
return serialize($this->__serialize());
}
/**
......@@ -226,7 +215,25 @@ class ParsedMarkdown implements ParsedMarkdownInterface {
*/
public function unserialize($serialized) {
$data = unserialize($serialized);
$this->__unserialize($data);
}
public function __serialize(): array {
$data['object'] = serialize(get_object_vars($this));
// Determine if PHP has gzip capabilities.
$data['gzip'] = extension_loaded('zlib');
// Compress and encode the markdown and html output.
if ($data['gzip']) {
/* @noinspection PhpComposerExtensionStubsInspection */
$data['object'] = base64_encode(gzencode($data['object'], 9));
}
return $data;
}
public function __unserialize(array $data): void {
// Data was gzipped.
if ($data['gzip']) {
// Decompress data if PHP has gzip capabilities.
......
......@@ -2,27 +2,14 @@
namespace Drupal\markdown\Traits;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
use Drupal\markdown\Util\FormHelper;
/**
* Trait providing helpful methods when dealing with forms.
*/
trait FormTrait {
/**
* Flag indicating whether the token module exists.
*
* @var bool
*/
protected static $tokenModuleExists;
/**
* Adds a #states selector to an element.
*
......@@ -38,18 +25,7 @@ trait FormTrait {
* An array of parents.
*/
public static function addElementState(array &$element, $state, $name, array $conditions, array $parents = NULL) {
// If parents weren't provided, attempt to extract it from the element.
if (!isset($parents)) {
$parents = isset($element['#parents']) ? $element['#parents'] : [];
}
// Add the state.
if ($selector = static::getElementSelector($name, $parents)) {
if (!isset($element['#states'][$state][$selector])) {
$element['#states'][$state][$selector] = [];
}
$element['#states'][$state][$selector] = NestedArray::mergeDeep($element['#states'][$state][$selector], $conditions);
}
FormHelper::addElementState($element, $state, $name, $conditions, $parents);
}
/**
......@@ -63,7 +39,7 @@ trait FormTrait {
* The value of the data attribute. Note: do not JSON encode this value.
*/
public static function addDataAttribute(array &$element, $name, $value) {
static::addDataAttributes($element, [$name => $value]);
FormHelper::addDataAttribute($element, $name, $value);
}
/**
......@@ -75,11 +51,7 @@ trait FormTrait {
* The data attributes to add.
*/
public static function addDataAttributes(array &$element, array $data) {
$converter = new CamelCaseToSnakeCaseNameConverter();
foreach ($data as $name => $value) {
$name = str_replace('_', '-', $converter->normalize(preg_replace('/^data-?/', '', $name)));
$element['#attributes']['data-' . $name] = is_string($value) || $value instanceof MarkupInterface ? (string) $value : Json::encode($value);
}
FormHelper::addDataAttributes($element, $data);
}
/**
......@@ -92,11 +64,7 @@ trait FormTrait {
* The modified $element.
*/
public static function createElement(array $element) {
if (isset($element['#attributes']['data']) && is_array($element['#attributes']['data'])) {
static::addDataAttributes($element, $element['#attributes']['data']);
unset($element['#attributes']['data']);
}
return $element;
return FormHelper::createElement($element);
}
/**
......@@ -112,22 +80,7 @@ trait FormTrait {
* The messages converted into a render array to be used inline.
*/
public static function createInlineMessage(array $messages, $weight = -10) {
static $headings;
if (!$headings) {
$headings = [
'error' => t('Error message'),
'info' => t('Info message'),
'status' => t('Status message'),
'warning' => t('Warning message'),
];
}
return [
'#type' => 'item',
'#weight' => $weight,
'#theme' => 'status_messages',
'#message_list' => $messages,
'#status_headings' => $headings,
];
return FormHelper::createInlineMessage($messages, $weight);
}
/**
......@@ -142,23 +95,7 @@ trait FormTrait {
* The selector for an element.
*/
public static function getElementSelector($name, array $parents) {
// Immediately return if name is already an input selector.
if (strpos($name, ':input[name="') === 0) {
return $name;
}
// Add the name of the element that will be used for the condition.
$parents[] = $name;
// Remove the first parent as the base selector.
$selector = array_shift($parents);
// Join remaining parents with [].
if ($parents) {
$selector .= '[' . implode('][', $parents) . ']';
}
return $selector ? ':input[name="' . $selector . '"]' : '';
return FormHelper::getElementSelector($name, $parents);
}
/**
......@@ -174,33 +111,7 @@ trait FormTrait {
* The form state.
*/
public static function resetToDefault(array &$element, $name, $defaultValue, FormStateInterface $form_state) {
/** @var \Drupal\markdown\Form\SubformStateInterface $form_state */
$selector = static::getElementSelector($name, $form_state->createParents());
$reset = FormTrait::createElement([
'#type' => 'link',
'#title' => '↩️',
'#url' => Url::fromUserInput('#reset-default-value', ['external' => TRUE]),
'#attributes' => [
'data' => [
'markdownElement' => 'reset',
'markdownId' => 'reset_' . $name,
'markdownTarget' => $selector,
'markdownDefaultValue' => $defaultValue,
],
'title' => t('Reset to default value'),
'style' => 'display: none; text-decoration: none;',
],
]);
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$element['#attached']['library'][] = 'markdown/reset';
$element['#title'] = new FormattableMarkup('@title @reset', [
'@title' => $element['#title'],
'@reset' => $renderer->renderPlain($reset),
]);
FormHelper::resetToDefault($element, $name, $defaultValue, $form_state);
}
/**
......@@ -217,28 +128,7 @@ trait FormTrait {
* A new render array element.
*/
public static function createTokenBrowser(array $tokenTypes = [], $globalTypes = TRUE, $dialog = TRUE) {
if (!isset(static::$tokenModuleExists)) {
static::$tokenModuleExists = \Drupal::moduleHandler()->moduleExists('token');
}
if (static::$tokenModuleExists) {
return [
'#type' => 'item',
'#input' => FALSE,
'#theme' => 'token_tree_link',
'#token_types' => $tokenTypes,
'#global_types' => $globalTypes,
'#dialog' => $dialog,
];
}
return [
'#type' => 'item',
'#input' => FALSE,
'#markup' => t('To browse available tokens, install the @token module.', [
'@token' => Link::fromTextAndUrl('Token', Url::fromUri('https://www.drupal.org/project/token', ['attributes' => ['target' => '_blank']]))->toString(),
]),
];
return FormHelper::createTokenBrowser($tokenTypes, $globalTypes, $dialog);
}
}
......@@ -5,6 +5,7 @@ namespace Drupal\markdown\Traits;
use Drupal\Component\Utility\DiffArray;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\markdown\Util\FormHelper;
use Drupal\markdown\Util\SortArray;
/**
......@@ -70,10 +71,10 @@ trait SettingsTrait {
$defaultValue = $return;
}
}
FormTrait::resetToDefault($element, $name, $defaultValue, $form_state);
FormHelper::resetToDefault($element, $name, $defaultValue, $form_state);
}
return [$name => FormTrait::createElement($element)];
return [$name => FormHelper::createElement($element)];
}
/**
......
<?php
namespace Drupal\markdown\Util;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
class FormHelper {
/**
* Flag indicating whether the token module exists.
*
* @var bool
*/
protected static $tokenModuleExists;
/**
* Adds a #states selector to an element.
*
* @param array $element
* An element to add the state to, passed by reference.
* @param string $state
* The state that will be triggered.
* @param string $name
* The name of the element used for conditions.
* @param array $conditions
* The conditions of $name that trigger $state.
* @param array|null $parents
* An array of parents.
*/
public static function addElementState(array &$element, string $state, string $name, array $conditions, array $parents = NULL): void {
// If parents weren't provided, attempt to extract it from the element.
if (!isset($parents)) {
$parents = isset($element['#parents']) ? $element['#parents'] : [];
}
// Add the state.
if ($selector = self::getElementSelector($name, $parents)) {
if (!isset($element['#states'][$state][$selector])) {
$element['#states'][$state][$selector] = [];
}
$element['#states'][$state][$selector] = NestedArray::mergeDeep($element['#states'][$state][$selector], $conditions);
}
}
/**
* Creates an element, adding data attributes to it if necessary.
*
* @param array $element
* An element.
*
* @return array
* The modified $element.
*/
public static function createElement(array $element): array {
if (isset($element['#attributes']['data']) && is_array($element['#attributes']['data'])) {
self::addDataAttributes($element, $element['#attributes']['data']);
unset($element['#attributes']['data']);
}
return $element;
}
/**
* Creates a Token browser element for use when dealing with tokens.
*
* @param array $tokenTypes
* An array of token types.
* @param bool $globalTypes
* Flag indicating whether to display global tokens.
* @param bool $dialog
* Flag indicating whether to show the browser in a dialog.
*
* @return array
* A new render array element.
*/
public static function createTokenBrowser(array $tokenTypes = [], bool $globalTypes = TRUE, bool $dialog = TRUE): array {
if (!isset(static::$tokenModuleExists)) {
static::$tokenModuleExists = \Drupal::moduleHandler()->moduleExists('token');
}
if (static::$tokenModuleExists) {
return [
'#type' => 'item',
'#input' => FALSE,
'#theme' => 'token_tree_link',
'#token_types' => $tokenTypes,
'#global_types' => $globalTypes,
'#dialog' => $dialog,
];
}
return [
'#type' => 'item',
'#input' => FALSE,
'#markup' => t('To browse available tokens, install the @token module.', [
'@token' => Link::fromTextAndUrl('Token', Url::fromUri('https://www.drupal.org/project/token', ['attributes' => ['target' => '_blank']]))->toString(),
]),
];
}
/**
* Allows a form element to be reset to its default value.
*
* @param array $element
* The render array element to modify, passed by reference.
* @param string $name
* The name.
* @param mixed $defaultValue
* The default value.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public static function resetToDefault(array &$element, string $name, $defaultValue, FormStateInterface $form_state): void {
/** @var \Drupal\markdown\Form\SubformStateInterface $form_state */
$selector = self::getElementSelector($name, $form_state->createParents());
$reset = self::createElement([
'#type' => 'link',
'#title' => '↩️',
'#url' => Url::fromUserInput('#reset-default-value', ['external' => TRUE]),
'#attributes' => [
'data' => [
'markdownElement' => 'reset',
'markdownId' => 'reset_' . $name,
'markdownTarget' => $selector,
'markdownDefaultValue' => $defaultValue,
],
'title' => t('Reset to default value'),
'style' => 'display: none; text-decoration: none;',
],
]);
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$element['#attached']['library'][] = 'markdown/reset';
$element['#title'] = new FormattableMarkup('@title @reset', [
'@title' => $element['#title'],
'@reset' => $renderer->renderPlain($reset),
]);
}
/**
* Adds a data attribute to an element.
*
* @param array $element
* An element, passed by reference.
* @param string $name
* The name of the data attribute.
* @param mixed $value
* The value of the data attribute. Note: do not JSON encode this value.
*/
public static function addDataAttribute(array &$element, string $name, $value): void {
self::addDataAttributes($element, [$name => $value]);
}
/**
* Retrieves the selector for an element.
*
* @param string $name
* The name of the element.
* @param array $parents
* An array of parents.
*
* @return string
* The selector for an element.
*/
public static function getElementSelector(string $name, array $parents): string {
// Immediately return if name is already an input selector.
if (strpos($name, ':input[name="') === 0) {
return $name;
}
// Add the name of the element that will be used for the condition.
$parents[] = $name;
// Remove the first parent as the base selector.
$selector = array_shift($parents);
// Join remaining parents with [].
if ($parents) {
$selector .= '[' . implode('][', $parents) . ']';
}
return $selector ? ':input[name="' . $selector . '"]' : '';
}
/**
* Creates an inline status message to be used in a render array.
*
* @param array $messages
* An array of messages, grouped by message type (i.e.
* ['status' => ['message']]).
* @param int $weight
* The weight of the message.
*
* @return array
* The messages converted into a render array to be used inline.
*/
public static function createInlineMessage(array $messages, $weight = -10): array {
static $headings;
if (!$headings) {
$headings = [
'error' => t('Error message'),
'info' => t('Info message'),
'status' => t('Status message'),
'warning' => t('Warning message'),
];
}
return [
'#type' => 'item',
'#weight' => $weight,
'#theme' => 'status_messages',
'#message_list' => $messages,
'#status_headings' => $headings,
];
}
/**
* Adds multiple data attributes to an element.
*
* @param array $element
* An element, passed by reference.
* @param array $data
* The data attributes to add.
*/
public static function addDataAttributes(array &$element, array $data): void {
$converter = new CamelCaseToSnakeCaseNameConverter();
foreach ($data as $name => $value) {
$name = str_replace('_', '-', $converter->normalize(preg_replace('/^data-?/', '', $name)));
$element['#attributes']['data-' . $name] = is_string($value) || $value instanceof MarkupInterface ? (string) $value : Json::encode($value);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment