mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00
Fix #44383: PHP DateTime not converted to xsd:datetime
Closes GH-12437. Closes GH-11725.
This commit is contained in:
parent
2318a81725
commit
b34b4d54c3
8 changed files with 154 additions and 14 deletions
1
NEWS
1
NEWS
|
@ -85,6 +85,7 @@ SOAP:
|
||||||
lost the setPersistence()). (nielsdos)
|
lost the setPersistence()). (nielsdos)
|
||||||
. Fixed bug #49278 (SoapClient::__getLastResponseHeaders returns NULL if
|
. Fixed bug #49278 (SoapClient::__getLastResponseHeaders returns NULL if
|
||||||
wsdl operation !has output). (nielsdos)
|
wsdl operation !has output). (nielsdos)
|
||||||
|
. Fixed bug #44383 (PHP DateTime not converted to xsd:datetime). (nielsdos)
|
||||||
|
|
||||||
Sockets:
|
Sockets:
|
||||||
. Removed the deprecated inet_ntoa call support. (David Carlier)
|
. Removed the deprecated inet_ntoa call support. (David Carlier)
|
||||||
|
|
|
@ -139,6 +139,9 @@ PHP 8.4 UPGRADE NOTES
|
||||||
It is now possible to specify entries in a class map with clark notation
|
It is now possible to specify entries in a class map with clark notation
|
||||||
to resolve a type with a specific namespace to a specific class.
|
to resolve a type with a specific namespace to a specific class.
|
||||||
For example: '{http://example.com}foo' => 'FooClass'.
|
For example: '{http://example.com}foo' => 'FooClass'.
|
||||||
|
. Instances of DateTimeInterface that are passed to xsd:datetime or similar
|
||||||
|
elements are now serialized as such instead of being serialized as an
|
||||||
|
empty string.
|
||||||
|
|
||||||
- XSL:
|
- XSL:
|
||||||
. It is now possible to use parameters that contain both single and double
|
. It is now possible to use parameters that contain both single and double
|
||||||
|
|
|
@ -56,6 +56,9 @@ PHP 8.4 INTERNALS UPGRADE NOTES
|
||||||
- Added php_libxml_pretend_ctx_error_ex() to emit errors as if they had come
|
- Added php_libxml_pretend_ctx_error_ex() to emit errors as if they had come
|
||||||
from libxml.
|
from libxml.
|
||||||
|
|
||||||
|
e. ext/date
|
||||||
|
- Added the php_format_date_ex() API to format instances of php_date_obj.
|
||||||
|
|
||||||
========================
|
========================
|
||||||
4. OpCode changes
|
4. OpCode changes
|
||||||
========================
|
========================
|
||||||
|
|
|
@ -841,6 +841,15 @@ static zend_string *date_format(const char *format, size_t format_len, timelib_t
|
||||||
return string.s;
|
return string.s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PHPAPI zend_string *php_format_date_obj(const char *format, size_t format_len, php_date_obj *date_obj)
|
||||||
|
{
|
||||||
|
if (!date_obj->time) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return date_format(format, format_len, date_obj->time, date_obj->time->is_localtime);
|
||||||
|
}
|
||||||
|
|
||||||
static void php_date(INTERNAL_FUNCTION_PARAMETERS, bool localtime)
|
static void php_date(INTERNAL_FUNCTION_PARAMETERS, bool localtime)
|
||||||
{
|
{
|
||||||
zend_string *format;
|
zend_string *format;
|
||||||
|
|
|
@ -123,6 +123,7 @@ PHPAPI int php_idate(char format, time_t ts, bool localtime);
|
||||||
|
|
||||||
PHPAPI void php_strftime(INTERNAL_FUNCTION_PARAMETERS, bool gm);
|
PHPAPI void php_strftime(INTERNAL_FUNCTION_PARAMETERS, bool gm);
|
||||||
PHPAPI zend_string *php_format_date(const char *format, size_t format_len, time_t ts, bool localtime);
|
PHPAPI zend_string *php_format_date(const char *format, size_t format_len, time_t ts, bool localtime);
|
||||||
|
PHPAPI zend_string *php_format_date_obj(const char *format, size_t format_len, php_date_obj *date_obj);
|
||||||
|
|
||||||
/* Mechanism to set new TZ database */
|
/* Mechanism to set new TZ database */
|
||||||
PHPAPI void php_date_set_tzdb(timelib_tzdb *tzdb);
|
PHPAPI void php_date_set_tzdb(timelib_tzdb *tzdb);
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "php_soap.h"
|
#include "php_soap.h"
|
||||||
#include "ext/libxml/php_libxml.h"
|
#include "ext/libxml/php_libxml.h"
|
||||||
#include "ext/standard/base64.h"
|
#include "ext/standard/base64.h"
|
||||||
|
#include "ext/date/php_date.h"
|
||||||
#include <libxml/parserInternals.h>
|
#include <libxml/parserInternals.h>
|
||||||
#include "zend_strtod.h"
|
#include "zend_strtod.h"
|
||||||
#include "zend_interfaces.h"
|
#include "zend_interfaces.h"
|
||||||
|
@ -57,7 +58,7 @@ static xmlNodePtr to_xml_list(encodeTypePtr enc, zval *data, int style, xmlNodeP
|
||||||
static xmlNodePtr to_xml_list1(encodeTypePtr enc, zval *data, int style, xmlNodePtr parent);
|
static xmlNodePtr to_xml_list1(encodeTypePtr enc, zval *data, int style, xmlNodePtr parent);
|
||||||
|
|
||||||
/* Datetime encode/decode */
|
/* Datetime encode/decode */
|
||||||
static xmlNodePtr to_xml_datetime_ex(encodeTypePtr type, zval *data, char *format, int style, xmlNodePtr parent);
|
static xmlNodePtr to_xml_datetime_ex(encodeTypePtr type, zval *data, char *format, const char *ext_date_format, size_t ext_date_format_len, int style, xmlNodePtr parent);
|
||||||
static xmlNodePtr to_xml_datetime(encodeTypePtr type, zval *data, int style, xmlNodePtr parent);
|
static xmlNodePtr to_xml_datetime(encodeTypePtr type, zval *data, int style, xmlNodePtr parent);
|
||||||
static xmlNodePtr to_xml_time(encodeTypePtr type, zval *data, int style, xmlNodePtr parent);
|
static xmlNodePtr to_xml_time(encodeTypePtr type, zval *data, int style, xmlNodePtr parent);
|
||||||
static xmlNodePtr to_xml_date(encodeTypePtr type, zval *data, int style, xmlNodePtr parent);
|
static xmlNodePtr to_xml_date(encodeTypePtr type, zval *data, int style, xmlNodePtr parent);
|
||||||
|
@ -2847,7 +2848,7 @@ static zval *guess_zval_convert(zval *ret, encodeTypePtr type, xmlNodePtr data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Time encode/decode */
|
/* Time encode/decode */
|
||||||
static xmlNodePtr to_xml_datetime_ex(encodeTypePtr type, zval *data, char *format, int style, xmlNodePtr parent)
|
static xmlNodePtr to_xml_datetime_ex(encodeTypePtr type, zval *data, char *format, const char *ext_date_format, size_t ext_date_format_len, int style, xmlNodePtr parent)
|
||||||
{
|
{
|
||||||
/* logic hacked from ext/standard/datetime.c */
|
/* logic hacked from ext/standard/datetime.c */
|
||||||
struct tm *ta, tmbuf;
|
struct tm *ta, tmbuf;
|
||||||
|
@ -2905,6 +2906,17 @@ static xmlNodePtr to_xml_datetime_ex(encodeTypePtr type, zval *data, char *forma
|
||||||
efree(buf);
|
efree(buf);
|
||||||
} else if (Z_TYPE_P(data) == IS_STRING) {
|
} else if (Z_TYPE_P(data) == IS_STRING) {
|
||||||
xmlNodeSetContentLen(xmlParam, BAD_CAST(Z_STRVAL_P(data)), Z_STRLEN_P(data));
|
xmlNodeSetContentLen(xmlParam, BAD_CAST(Z_STRVAL_P(data)), Z_STRLEN_P(data));
|
||||||
|
} else if (Z_TYPE_P(data) == IS_OBJECT) {
|
||||||
|
if (instanceof_function_slow(Z_OBJCE_P(data), php_date_get_interface_ce())) {
|
||||||
|
php_date_obj *date_obj = Z_PHPDATE_P(data);
|
||||||
|
zend_string *formatted_date_string = php_format_date_obj(ext_date_format, ext_date_format_len, date_obj);
|
||||||
|
if (formatted_date_string) {
|
||||||
|
xmlNodeSetContentLen(xmlParam, BAD_CAST(ZSTR_VAL(formatted_date_string)), ZSTR_LEN(formatted_date_string));
|
||||||
|
zend_string_release_ex(formatted_date_string, false);
|
||||||
|
} else {
|
||||||
|
soap_error0(E_ERROR, "Encoding: Invalid DateTimeInterface");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style == SOAP_ENCODED) {
|
if (style == SOAP_ENCODED) {
|
||||||
|
@ -2919,45 +2931,47 @@ static xmlNodePtr to_xml_duration(encodeTypePtr type, zval *data, int style, xml
|
||||||
return to_xml_string(type, data, style, parent);
|
return to_xml_string(type, data, style, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define TO_XML_DATETIME_EX_HELPER(type, date, format, ext_date_format, style, parent) \
|
||||||
|
to_xml_datetime_ex(type, data, format, ext_date_format, strlen(ext_date_format), style, parent)
|
||||||
|
|
||||||
static xmlNodePtr to_xml_datetime(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
static xmlNodePtr to_xml_datetime(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
||||||
{
|
{
|
||||||
return to_xml_datetime_ex(type, data, "%Y-%m-%dT%H:%M:%S", style, parent);
|
return TO_XML_DATETIME_EX_HELPER(type, data, "%Y-%m-%dT%H:%M:%S", "Y-m-d\\TH:i:s.up", style, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
static xmlNodePtr to_xml_time(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
static xmlNodePtr to_xml_time(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
||||||
{
|
{
|
||||||
/* TODO: microsecconds */
|
return TO_XML_DATETIME_EX_HELPER(type, data, "%H:%M:%S", "H:i:s.up", style, parent);
|
||||||
return to_xml_datetime_ex(type, data, "%H:%M:%S", style, parent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static xmlNodePtr to_xml_date(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
static xmlNodePtr to_xml_date(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
||||||
{
|
{
|
||||||
return to_xml_datetime_ex(type, data, "%Y-%m-%d", style, parent);
|
return TO_XML_DATETIME_EX_HELPER(type, data, "%Y-%m-%d", "Y-m-dp", style, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
static xmlNodePtr to_xml_gyearmonth(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
static xmlNodePtr to_xml_gyearmonth(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
||||||
{
|
{
|
||||||
return to_xml_datetime_ex(type, data, "%Y-%m", style, parent);
|
return TO_XML_DATETIME_EX_HELPER(type, data, "%Y-%m", "Y-mp", style, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
static xmlNodePtr to_xml_gyear(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
static xmlNodePtr to_xml_gyear(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
||||||
{
|
{
|
||||||
return to_xml_datetime_ex(type, data, "%Y", style, parent);
|
return TO_XML_DATETIME_EX_HELPER(type, data, "%Y", "Yp", style, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
static xmlNodePtr to_xml_gmonthday(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
static xmlNodePtr to_xml_gmonthday(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
||||||
{
|
{
|
||||||
return to_xml_datetime_ex(type, data, "--%m-%d", style, parent);
|
return TO_XML_DATETIME_EX_HELPER(type, data, "--%m-%d", "--m-dp", style, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
static xmlNodePtr to_xml_gday(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
static xmlNodePtr to_xml_gday(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
||||||
{
|
{
|
||||||
return to_xml_datetime_ex(type, data, "---%d", style, parent);
|
return TO_XML_DATETIME_EX_HELPER(type, data, "---%d", "---dp", style, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
static xmlNodePtr to_xml_gmonth(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
static xmlNodePtr to_xml_gmonth(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
|
||||||
{
|
{
|
||||||
return to_xml_datetime_ex(type, data, "--%m--", style, parent);
|
return TO_XML_DATETIME_EX_HELPER(type, data, "--%m--", "--m--p", style, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
static zval* to_zval_list(zval *ret, encodeTypePtr enc, xmlNodePtr data) {
|
static zval* to_zval_list(zval *ret, encodeTypePtr enc, xmlNodePtr data) {
|
||||||
|
|
|
@ -215,10 +215,14 @@ PHP_MINIT_FUNCTION(soap);
|
||||||
PHP_MSHUTDOWN_FUNCTION(soap);
|
PHP_MSHUTDOWN_FUNCTION(soap);
|
||||||
PHP_MINFO_FUNCTION(soap);
|
PHP_MINFO_FUNCTION(soap);
|
||||||
|
|
||||||
|
static const zend_module_dep soap_deps[] = {
|
||||||
|
ZEND_MOD_REQUIRED("date")
|
||||||
|
ZEND_MOD_END
|
||||||
|
};
|
||||||
|
|
||||||
zend_module_entry soap_module_entry = {
|
zend_module_entry soap_module_entry = {
|
||||||
#ifdef STANDARD_MODULE_HEADER
|
STANDARD_MODULE_HEADER_EX, NULL,
|
||||||
STANDARD_MODULE_HEADER,
|
soap_deps,
|
||||||
#endif
|
|
||||||
"soap",
|
"soap",
|
||||||
ext_functions,
|
ext_functions,
|
||||||
PHP_MINIT(soap),
|
PHP_MINIT(soap),
|
||||||
|
|
105
ext/soap/tests/schema/schema086.phpt
Normal file
105
ext/soap/tests/schema/schema086.phpt
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
--TEST--
|
||||||
|
SOAP XML Schema 86: DateTimeInterface date/time types
|
||||||
|
--EXTENSIONS--
|
||||||
|
soap
|
||||||
|
xml
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
include "test_schema.inc";
|
||||||
|
$schema = <<<EOF
|
||||||
|
<complexType name="testType">
|
||||||
|
<sequence>
|
||||||
|
<element name="dateTime" type="dateTime"/>
|
||||||
|
<element name="time" type="time"/>
|
||||||
|
<element name="date" type="date"/>
|
||||||
|
<element name="gYearMonth" type="gYearMonth"/>
|
||||||
|
<element name="gYear" type="gYear"/>
|
||||||
|
<element name="gMonthDay" type="gMonthDay"/>
|
||||||
|
<element name="gDay" type="gDay"/>
|
||||||
|
<element name="gMonth" type="gMonth"/>
|
||||||
|
</sequence>
|
||||||
|
</complexType>
|
||||||
|
EOF;
|
||||||
|
|
||||||
|
$test_dates = [
|
||||||
|
new DateTime("2023-10-14 13:37:42.1234+02:00"),
|
||||||
|
new DateTimeImmutable("2023-10-14 13:37:42.1234+02:00"),
|
||||||
|
new DateTime("2023-10-14 13:37:42.1234Z"),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($test_dates as $date) {
|
||||||
|
test_schema($schema,'type="tns:testType"',array(
|
||||||
|
'dateTime' => $date,
|
||||||
|
'time' => $date,
|
||||||
|
'date' => $date,
|
||||||
|
'gYearMonth' => $date,
|
||||||
|
'gYear' => $date,
|
||||||
|
'gMonthDay' => $date,
|
||||||
|
'gDay' => $date,
|
||||||
|
'gMonth' => $date
|
||||||
|
));
|
||||||
|
}
|
||||||
|
echo "ok";
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://test-uri/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:test><testParam xsi:type="ns1:testType"><dateTime xsi:type="xsd:dateTime">2023-10-14T13:37:42.123400+02:00</dateTime><time xsi:type="xsd:time">13:37:42.123400+02:00</time><date xsi:type="xsd:date">2023-10-14+02:00</date><gYearMonth xsi:type="xsd:gYearMonth">2023-10+02:00</gYearMonth><gYear xsi:type="xsd:gYear">2023+02:00</gYear><gMonthDay xsi:type="xsd:gMonthDay">--10-14+02:00</gMonthDay><gDay xsi:type="xsd:gDay">---14+02:00</gDay><gMonth xsi:type="xsd:gMonth">--10--+02:00</gMonth></testParam></ns1:test></SOAP-ENV:Body></SOAP-ENV:Envelope>
|
||||||
|
object(stdClass)#%d (8) {
|
||||||
|
["dateTime"]=>
|
||||||
|
string(32) "2023-10-14T13:37:42.123400+02:00"
|
||||||
|
["time"]=>
|
||||||
|
string(21) "13:37:42.123400+02:00"
|
||||||
|
["date"]=>
|
||||||
|
string(16) "2023-10-14+02:00"
|
||||||
|
["gYearMonth"]=>
|
||||||
|
string(13) "2023-10+02:00"
|
||||||
|
["gYear"]=>
|
||||||
|
string(10) "2023+02:00"
|
||||||
|
["gMonthDay"]=>
|
||||||
|
string(13) "--10-14+02:00"
|
||||||
|
["gDay"]=>
|
||||||
|
string(11) "---14+02:00"
|
||||||
|
["gMonth"]=>
|
||||||
|
string(12) "--10--+02:00"
|
||||||
|
}
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://test-uri/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:test><testParam xsi:type="ns1:testType"><dateTime xsi:type="xsd:dateTime">2023-10-14T13:37:42.123400+02:00</dateTime><time xsi:type="xsd:time">13:37:42.123400+02:00</time><date xsi:type="xsd:date">2023-10-14+02:00</date><gYearMonth xsi:type="xsd:gYearMonth">2023-10+02:00</gYearMonth><gYear xsi:type="xsd:gYear">2023+02:00</gYear><gMonthDay xsi:type="xsd:gMonthDay">--10-14+02:00</gMonthDay><gDay xsi:type="xsd:gDay">---14+02:00</gDay><gMonth xsi:type="xsd:gMonth">--10--+02:00</gMonth></testParam></ns1:test></SOAP-ENV:Body></SOAP-ENV:Envelope>
|
||||||
|
object(stdClass)#9 (8) {
|
||||||
|
["dateTime"]=>
|
||||||
|
string(32) "2023-10-14T13:37:42.123400+02:00"
|
||||||
|
["time"]=>
|
||||||
|
string(21) "13:37:42.123400+02:00"
|
||||||
|
["date"]=>
|
||||||
|
string(16) "2023-10-14+02:00"
|
||||||
|
["gYearMonth"]=>
|
||||||
|
string(13) "2023-10+02:00"
|
||||||
|
["gYear"]=>
|
||||||
|
string(10) "2023+02:00"
|
||||||
|
["gMonthDay"]=>
|
||||||
|
string(13) "--10-14+02:00"
|
||||||
|
["gDay"]=>
|
||||||
|
string(11) "---14+02:00"
|
||||||
|
["gMonth"]=>
|
||||||
|
string(12) "--10--+02:00"
|
||||||
|
}
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://test-uri/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:test><testParam xsi:type="ns1:testType"><dateTime xsi:type="xsd:dateTime">2023-10-14T13:37:42.123400Z</dateTime><time xsi:type="xsd:time">13:37:42.123400Z</time><date xsi:type="xsd:date">2023-10-14Z</date><gYearMonth xsi:type="xsd:gYearMonth">2023-10Z</gYearMonth><gYear xsi:type="xsd:gYear">2023Z</gYear><gMonthDay xsi:type="xsd:gMonthDay">--10-14Z</gMonthDay><gDay xsi:type="xsd:gDay">---14Z</gDay><gMonth xsi:type="xsd:gMonth">--10--Z</gMonth></testParam></ns1:test></SOAP-ENV:Body></SOAP-ENV:Envelope>
|
||||||
|
object(stdClass)#8 (8) {
|
||||||
|
["dateTime"]=>
|
||||||
|
string(27) "2023-10-14T13:37:42.123400Z"
|
||||||
|
["time"]=>
|
||||||
|
string(16) "13:37:42.123400Z"
|
||||||
|
["date"]=>
|
||||||
|
string(11) "2023-10-14Z"
|
||||||
|
["gYearMonth"]=>
|
||||||
|
string(8) "2023-10Z"
|
||||||
|
["gYear"]=>
|
||||||
|
string(5) "2023Z"
|
||||||
|
["gMonthDay"]=>
|
||||||
|
string(8) "--10-14Z"
|
||||||
|
["gDay"]=>
|
||||||
|
string(6) "---14Z"
|
||||||
|
["gMonth"]=>
|
||||||
|
string(7) "--10--Z"
|
||||||
|
}
|
||||||
|
ok
|
Loading…
Add table
Add a link
Reference in a new issue