Commit 6b93ac73 authored by catch's avatar catch

Issue #1285726 by kim.pepper, InternetDevels, alexpott, RobLoach: Remove XML-RPC.

parent c2e66f9e
......@@ -45,6 +45,7 @@ Drupal 8.0, xxxx-xx-xx (development version)
- Removed the Overlay module from core.
- Removed the Garland theme from core.
- Removed the Statistics module's accesslog functionality and reports from core.
- Removed XML-RPC functionality from core.
- Removed backwards-compatibility with 'magic_quotes_gpc'/'magic_quotes_runtime'
PHP configuration settings. Both are required to be disabled.
- Universally Unique IDentifier (UUID):
......
......@@ -433,9 +433,6 @@ Views UI module
- Tim Plunkett 'tim.plunkett' http://drupal.org/user/241634
- Damian Lee 'damiankloip' http://drupal.org/user/1037976
XML-RPC module
- Frederic G. Marand 'fgm' http://drupal.org/user/27985
Theme maintainers
-----------------
......
......@@ -144,19 +144,6 @@
* providing default configuration or configuration import, as outlined in
* @ref sec_rest above.
*
* @section sec_xmlrpc Using XML-RPC
* In XML-RPC, a web site can set up one or more XML-RPC methods, which it
* will usually service at a central XML-RPC URL. Drupal Core's XML-RPC module
* provides both client and server XML-RPC functionality.
*
* On the server side, the XML-RPC module sets up URL path xmlrpc.php, which
* responds to XML-RPC requests. Individual XML-RPC request methods are defined
* by modules by implementing hook_xmlrpc(); Drupal Core does not define any
* XML-RPC web service requests by default.
*
* On the client side, XML-RPC requests to other web sites that provide XML-RPC
* web services can be performed in Drupal by using the xmlrpc() function.
*
* @section sec_integrate Integrating data from other sites into Drupal
* If you want to integrate data from other web sites into Drupal, here are
* some notes:
......@@ -176,7 +163,6 @@
* - \Drupal\Component\Serialization\Json (JSON encoding and decoding).
* - PHP has functions and classes for parsing XML; see
* http://php.net/manual/refs.xml.php
* - As mentioned above, for XML-RPC requests, use function xmlrpc().
* @}
*/
......
......@@ -29,7 +29,7 @@ function testDrupalGetFilename() {
// does not exist.
$this->assertFalse(\Drupal::hasService('keyvalue'), 'The container has no keyvalue service.');
// Retrieving the location of a module.
$this->assertIdentical(drupal_get_filename('module', 'xmlrpc'), 'core/modules/xmlrpc/xmlrpc.info.yml');
$this->assertIdentical(drupal_get_filename('module', 'system'), 'core/modules/system/system.info.yml');
// Retrieving the location of a theme.
$this->assertIdentical(drupal_get_filename('theme', 'stark'), 'core/themes/stark/stark.info.yml');
......
......@@ -116,7 +116,7 @@ function testDependencyResolution() {
$this->assertFalse($this->moduleHandler()->moduleExists('color'), 'ModuleHandler::install() aborts if dependencies are missing.');
// Fix the missing dependency.
// Forum module depends on Ban. Ban depends on XML-RPC module.
// Color module depends on Config. Config depends on Help module.
\Drupal::state()->set('module_test.dependency', 'dependency');
drupal_static_reset('system_rebuild_module_data');
......@@ -145,9 +145,9 @@ function testDependencyResolution() {
$uninstalled_modules = \Drupal::state()->get('module_test.uninstall_order') ?: array();
$this->assertEqual($uninstalled_modules, array('color', 'config', 'help'), 'Modules were uninstalled in the correct order.');
// Enable forum module again, which should enable both the ban module and
// XML-RPC module. But, this time do it with ban module declaring a
// dependency on a specific version of XML-RPC module in its info file. Make
// Enable Color module again, which should enable both the Config module and
// Help module. But, this time do it with Config module declaring a
// dependency on a specific version of Help module in its info file. Make
// sure that Drupal\Core\Extension\ModuleHandler::install() still works.
\Drupal::state()->set('module_test.dependency', 'version dependency');
drupal_static_reset('system_rebuild_module_data');
......@@ -163,12 +163,7 @@ function testDependencyResolution() {
// Finally, verify that the modules were enabled in the correct order.
$enable_order = \Drupal::state()->get('module_test.install_order') ?: array();
$help_position = array_search('help', $enable_order);
$config_position = array_search('config', $enable_order);
$color_position = array_search('color', $enable_order);
$xmlrpc_before_ban = $help_position !== FALSE && $config_position !== FALSE && $help_position < $config_position;
$ban_before_forum = $config_position !== FALSE && $color_position !== FALSE && $config_position < $color_position;
$this->assertTrue($xmlrpc_before_ban && $ban_before_forum, 'Modules were enabled in the correct order.');
$this->assertIdentical($enable_order, array('help', 'config', 'color'));
}
/**
......
<?php
/**
* @file
* Contains \Drupal\xmlrpc\Controller\XmlrpcController.
*/
namespace Drupal\xmlrpc\Controller;
/**
* Contains controller methods for the XML-RPC module.
*/
class XmlrpcController {
/**
* @todo Remove xmlrpc_server_page().
*/
public function php() {
module_load_include('server.inc', 'xmlrpc');
return xmlrpc_server_page();
}
}
<?php
/**
* @file
* Definition of Drupal\xmlrpc\Tests\XmlRpcBasicTest.
*/
namespace Drupal\xmlrpc\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Perform basic XML-RPC tests that do not require addition callbacks.
*
* @group xmlrpc
*/
class XmlRpcBasicTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('xmlrpc');
/**
* Ensure that a basic XML-RPC call with no parameters works.
*/
protected function testListMethods() {
// Minimum list of methods that should be included.
$minimum = array(
'system.multicall',
'system.methodSignature',
'system.getCapabilities',
'system.listMethods',
'system.methodHelp',
);
// Invoke XML-RPC call to get list of methods.
$url = url('xmlrpc.php', array('absolute' => TRUE));
$methods = xmlrpc($url, array('system.listMethods' => array()));
// Ensure that the minimum methods were found.
$count = 0;
foreach ($methods as $method) {
if (in_array($method, $minimum)) {
$count++;
}
}
$this->assertEqual($count, count($minimum), 'system.listMethods returned at least the minimum listing');
}
/**
* Ensure that system.methodSignature returns an array of signatures.
*/
protected function testMethodSignature() {
$url = url('xmlrpc.php', array('absolute' => TRUE));
$signature = xmlrpc($url, array('system.methodSignature' => array('system.listMethods')));
$this->assert(is_array($signature) && !empty($signature) && is_array($signature[0]),
'system.methodSignature returns an array of signature arrays.');
}
/**
* Ensure that XML-RPC correctly handles invalid messages when parsing.
*/
protected function testInvalidMessageParsing() {
$invalid_messages = array(
array(
'message' => xmlrpc_message(''),
'assertion' => 'Empty message correctly rejected during parsing.',
),
array(
'message' => xmlrpc_message('<?xml version="1.0" encoding="ISO-8859-1"?>'),
'assertion' => 'Empty message with XML declaration correctly rejected during parsing.',
),
array(
'message' => xmlrpc_message('<?xml version="1.0"?><params><param><value><string>value</string></value></param></params>'),
'assertion' => 'Non-empty message without a valid message type is rejected during parsing.',
),
array(
'message' => xmlrpc_message('<methodResponse><params><param><value><string>value</string></value></param></methodResponse>'),
'assertion' => 'Non-empty malformed message is rejected during parsing.',
),
);
foreach ($invalid_messages as $assertion) {
$this->assertFalse(xmlrpc_message_parse($assertion['message']), $assertion['assertion']);
}
}
}
<?php
/**
* @file
* Definition of Drupal\xmlrpc\Tests\XmlRpcMessagesTest.
*/
namespace Drupal\xmlrpc\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests large messages and method alterations.
*
* @group xmlrpc
*/
class XmlRpcMessagesTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('xmlrpc', 'xmlrpc_test');
/**
* Make sure that XML-RPC can transfer large messages.
*/
function testSizedMessages() {
$xml_url = url('xmlrpc.php', array('absolute' => TRUE));
$sizes = array(8, 80, 160);
foreach ($sizes as $size) {
$xml_message_l = xmlrpc_test_message_sized_in_kb($size);
$xml_message_r = xmlrpc($xml_url, array('messages.messageSizedInKB' => array($size)));
$this->assertEqual($xml_message_l, $xml_message_r, format_string('XML-RPC messages.messageSizedInKB of %s Kb size received', array('%s' => $size)));
}
}
/**
* Ensure that hook_xmlrpc_alter() can hide even builtin methods.
*/
protected function testAlterListMethods() {
// Ensure xmlrpc_test.alter() is disabled and retrieve regular list of methods.
\Drupal::state()->set('xmlrpc_test.alter', FALSE);
$url = url('xmlrpc.php', array('absolute' => TRUE));
$methods1 = xmlrpc($url, array('system.listMethods' => array()));
// Enable the alter hook and retrieve the list of methods again.
\Drupal::state()->set('xmlrpc_test.alter', TRUE);
$methods2 = xmlrpc($url, array('system.listMethods' => array()));
$diff = array_diff($methods1, $methods2);
$this->assertTrue(is_array($diff) && !empty($diff), 'Method list is altered by hook_xmlrpc_alter');
$removed = reset($diff);
$this->assertEqual($removed, 'system.methodSignature', 'Hiding builting system.methodSignature with hook_xmlrpc_alter works');
}
}
<?php
/**
* @file
* Definition of Drupal\xmlrpc\Tests\XmlRpcValidatorTest.
*/
namespace Drupal\xmlrpc\Tests;
use Drupal\simpletest\WebTestBase;
/**
* See <a href="http://www.xmlrpc.com/validator1Docs">the xmlrpc validator1
* specification</a>.
*
* @group xmlrpc
*/
class XmlRpcValidatorTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('xmlrpc', 'xmlrpc_test');
/**
* Run validator1 tests.
*/
function testValidator() {
$xml_url = url('xmlrpc.php', array('absolute' => TRUE));
srand();
mt_srand();
$array_1 = array(array('curly' => mt_rand(-100, 100)),
array('curly' => mt_rand(-100, 100)),
array('larry' => mt_rand(-100, 100)),
array('larry' => mt_rand(-100, 100)),
array('moe' => mt_rand(-100, 100)),
array('moe' => mt_rand(-100, 100)),
array('larry' => mt_rand(-100, 100)));
shuffle($array_1);
$l_res_1 = xmlrpc_test_arrayOfStructsTest($array_1);
$r_res_1 = xmlrpc($xml_url, array('validator1.arrayOfStructsTest' => array($array_1)));
$this->assertIdentical($l_res_1, $r_res_1);
$string_2 = 't\'&>>zf"md>yr>xlcev<h<"k&j<og"w&&>">>uai"np&s>>q\'&b<>"&&&';
$l_res_2 = xmlrpc_test_countTheEntities($string_2);
$r_res_2 = xmlrpc($xml_url, array('validator1.countTheEntities' => array($string_2)));
$this->assertIdentical($l_res_2, $r_res_2);
$struct_3 = array('moe' => mt_rand(-100, 100), 'larry' => mt_rand(-100, 100), 'curly' => mt_rand(-100, 100), 'homer' => mt_rand(-100, 100));
$l_res_3 = xmlrpc_test_easyStructTest($struct_3);
$r_res_3 = xmlrpc($xml_url, array('validator1.easyStructTest' => array($struct_3)));
$this->assertIdentical($l_res_3, $r_res_3);
$struct_4 = array('sub1' => array('bar' => 13),
'sub2' => 14,
'sub3' => array('foo' => 1, 'baz' => 2),
'sub4' => array('ss' => array('sss' => array('ssss' => 'sssss'))));
$l_res_4 = xmlrpc_test_echoStructTest($struct_4);
$r_res_4 = xmlrpc($xml_url, array('validator1.echoStructTest' => array($struct_4)));
$this->assertIdentical($l_res_4, $r_res_4);
$int_5 = mt_rand(-100, 100);
$bool_5 = (($int_5 % 2) == 0);
$string_5 = $this->randomName();
$double_5 = (double)(mt_rand(-1000, 1000) / 100);
$time_5 = REQUEST_TIME;
$base64_5 = $this->randomName(100);
$l_res_5 = xmlrpc_test_manyTypesTest($int_5, $bool_5, $string_5, $double_5, xmlrpc_date($time_5), $base64_5);
// See http://drupal.org/node/37766 why this currently fails
$l_res_5[5] = $l_res_5[5]->data;
$r_res_5 = xmlrpc($xml_url, array('validator1.manyTypesTest' => array($int_5, $bool_5, $string_5, $double_5, xmlrpc_date($time_5), xmlrpc_base64($base64_5))));
// @todo Contains objects, objects are not equal.
$this->assertEqual($l_res_5, $r_res_5);
$size = mt_rand(100, 200);
$array_6 = array();
for ($i = 0; $i < $size; $i++) {
$array_6[] = $this->randomName(mt_rand(8, 12));
}
$l_res_6 = xmlrpc_test_moderateSizeArrayCheck($array_6);
$r_res_6 = xmlrpc($xml_url, array('validator1.moderateSizeArrayCheck' => array($array_6)));
$this->assertIdentical($l_res_6, $r_res_6);
$struct_7 = array();
for ($y = 2000; $y < 2002; $y++) {
for ($m = 3; $m < 5; $m++) {
for ($d = 1; $d < 6; $d++) {
$ys = (string) $y;
$ms = sprintf('%02d', $m);
$ds = sprintf('%02d', $d);
$struct_7[$ys][$ms][$ds]['moe'] = mt_rand(-100, 100);
$struct_7[$ys][$ms][$ds]['larry'] = mt_rand(-100, 100);
$struct_7[$ys][$ms][$ds]['curly'] = mt_rand(-100, 100);
}
}
}
$l_res_7 = xmlrpc_test_nestedStructTest($struct_7);
$r_res_7 = xmlrpc($xml_url, array('validator1.nestedStructTest' => array($struct_7)));
$this->assertIdentical($l_res_7, $r_res_7);
$int_8 = mt_rand(-100, 100);
$l_res_8 = xmlrpc_test_simpleStructReturnTest($int_8);
$r_res_8 = xmlrpc($xml_url, array('validator1.simpleStructReturnTest' => array($int_8)));
$this->assertIdentical($l_res_8, $r_res_8);
/* Now test multicall */
$x = array();
$x['validator1.arrayOfStructsTest'] = array($array_1);
$x['validator1.countTheEntities'] = array($string_2);
$x['validator1.easyStructTest'] = array($struct_3);
$x['validator1.echoStructTest'] = array($struct_4);
$x['validator1.manyTypesTest'] = array($int_5, $bool_5, $string_5, $double_5, xmlrpc_date($time_5), xmlrpc_base64($base64_5));
$x['validator1.moderateSizeArrayCheck'] = array($array_6);
$x['validator1.nestedStructTest'] = array($struct_7);
$x['validator1.simpleStructReturnTest'] = array($int_8);
$a_l_res = array($l_res_1, $l_res_2, $l_res_3, $l_res_4, $l_res_5, $l_res_6, $l_res_7, $l_res_8);
$a_r_res = xmlrpc($xml_url, $x);
$this->assertEqual($a_l_res, $a_r_res);
}
}
name: 'XML-RPC Test'
type: module
description: 'Support module for XML-RPC tests according to the validator1 specification.'
package: Testing
version: VERSION
core: 8.x
<?php
function xmlrpc_test_arrayOfStructsTest($array) {
$sum = 0;
foreach ($array as $struct) {
if (isset($struct['curly'])) {
$sum += $struct['curly'];
}
}
return $sum;
}
function xmlrpc_test_countTheEntities($string) {
return array(
'ctLeftAngleBrackets' => substr_count($string, '<'),
'ctRightAngleBrackets' => substr_count($string, '>'),
'ctAmpersands' => substr_count($string, '&'),
'ctApostrophes' => substr_count($string, "'"),
'ctQuotes' => substr_count($string, '"'),
);
}
function xmlrpc_test_easyStructTest($array) {
return $array["curly"] + $array["moe"] + $array["larry"];
}
function xmlrpc_test_echoStructTest($array) {
return $array;
}
function xmlrpc_test_manyTypesTest($number, $boolean, $string, $double, $dateTime, $base64) {
$timestamp = gmmktime($dateTime->hour, $dateTime->minute, $dateTime->second, $dateTime->month, $dateTime->day, $dateTime->year);
return array($number, $boolean, $string, $double, xmlrpc_date($timestamp), xmlrpc_Base64($base64));
}
function xmlrpc_test_moderateSizeArrayCheck($array) {
return array_shift($array) . array_pop($array);
}
function xmlrpc_test_nestedStructTest($array) {
return $array["2000"]["04"]["01"]["larry"] + $array["2000"]["04"]["01"]["moe"] + $array["2000"]["04"]["01"]["curly"];
}
function xmlrpc_test_simpleStructReturnTest($number) {
return array("times10" => ($number*10), "times100" => ($number*100), "times1000" => ($number*1000));
}
/**
* Implements hook_xmlrpc().
*/
function xmlrpc_test_xmlrpc() {
return array(
'validator1.arrayOfStructsTest' => 'xmlrpc_test_arrayOfStructsTest',
'validator1.countTheEntities' => 'xmlrpc_test_countTheEntities',
'validator1.easyStructTest' => 'xmlrpc_test_easyStructTest',
'validator1.echoStructTest' => 'xmlrpc_test_echoStructTest',
'validator1.manyTypesTest' => 'xmlrpc_test_manyTypesTest',
'validator1.moderateSizeArrayCheck' => 'xmlrpc_test_moderateSizeArrayCheck',
'validator1.nestedStructTest' => 'xmlrpc_test_nestedStructTest',
'validator1.simpleStructReturnTest' => 'xmlrpc_test_simpleStructReturnTest',
'messages.messageSizedInKB' => 'xmlrpc_test_message_sized_in_kb',
);
}
/**
* Implements hook_xmlrpc_alter().
*
* Hide (or not) the system.methodSignature() service depending on a variable.
*/
function xmlrpc_test_xmlrpc_alter(&$services) {
$xmlprc_alter = \Drupal::state()->get('xmlrpc_test.alter') ?: FALSE;
if ($xmlprc_alter) {
$remove = NULL;
foreach ($services as $key => $value) {
if (!is_array($value)) {
continue;
}
if ($value[0] == 'system.methodSignature') {
$remove = $key;
break;
}
}
if (isset($remove)) {
unset($services[$remove]);
}
}
}
/**
* Created a message of the desired size in KB.
*
* @param $size
* Message size in KB.
* @return array
* Generated message structure.
*/
function xmlrpc_test_message_sized_in_kb($size) {
$message = array();
$word = 'abcdefg';
// Create a ~1KB sized struct.
for ($i = 0 ; $i < 128; $i++) {
$line['word_' . $i] = $word;
}
for ($i = 0; $i < $size; $i++) {
$message['line_' . $i] = $line;
}
return $message;
}
<?php
/**
* @file
* Hooks provided by the XML-RPC module.
*/
/**
* Register XML-RPC callbacks.
*
* This hook lets a module register callback functions to be called when
* particular XML-RPC methods are invoked by a client.
*
* @return
* An array which maps XML-RPC methods to Drupal functions. Each array
* element is either a pair of method => function or an array with four
* entries:
* - The XML-RPC method name (for example, module.function).
* - The Drupal callback function (for example, module_function).
* - The method signature is an array of XML-RPC types. The first element
* of this array is the type of return value and then you should write a
* list of the types of the parameters. XML-RPC types are the following
* (See the types at http://www.xmlrpc.com/spec):
* - "boolean": 0 (false) or 1 (true).
* - "double": a floating point number (for example, -12.214).
* - "int": a integer number (for example, -12).
* - "array": an array without keys (for example, array(1, 2, 3)).
* - "struct": an associative array or an object (for example,
* array('one' => 1, 'two' => 2)).
* - "date": when you return a date, then you may either return a
* timestamp (time(), mktime() etc.) or an ISO8601 timestamp. When
* date is specified as an input parameter, then you get an object,
* which is described in the function xmlrpc_date
* - "base64": a string containing binary data, automatically
* encoded/decoded automatically.
* - "string": anything else, typically a string.
* - A descriptive help string, enclosed in a t() function for translation
* purposes.
* Both forms are shown in the example.
*
* @ingroup third_party
*/
function hook_xmlrpc() {
return array(
'drupal.login' => 'drupal_login',
array(
'drupal.site.ping',
'drupal_directory_ping',
array('boolean', 'string', 'string', 'string', 'string', 'string'),
t('Handling ping request'))
);
}
/**
* Alters the definition of XML-RPC methods before they are called.
*
* This hook allows modules to modify the callback definition of declared
* XML-RPC methods, right before they are invoked by a client. Methods may be
* added, or existing methods may be altered.
*
* Note that hook_xmlrpc() supports two distinct and incompatible formats to
* define a callback, so care must be taken when altering other methods.
*
* @param $methods
* An asssociative array of method callback definitions, as returned from
* hook_xmlrpc() implementations.
*
* @see hook_xmlrpc()
* @see xmlrpc_server()
*/
function hook_xmlrpc_alter(&$methods) {
// Directly change a simple method.
$methods['drupal.login'] = 'mymodule_login';
// Alter complex definitions.
foreach ($methods as $key => &$method) {
// Skip simple method definitions.
if (!is_int($key)) {
continue;
}
// Perform the wanted manipulation.
if ($method[0] == 'drupal.site.ping') {
$method[1] = 'mymodule_directory_ping';
}
}
}
This diff is collapsed.
name: XML-RPC
type: module
description: 'Provides XML-RPC functionality.'
package: Core
version: VERSION
core: 8.x
<?php
/**
* @file
* Enables XML-RPC functionality.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function xmlrpc_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.xmlrpc':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The XML-RPC module gives external systems the opportunity to communicate with the site through the <a href="http://en.wikipedia.org/wiki/XML-RPC">XML-RPC protocol</a>. An XML-RPC client can communicate with the site by making a request to <a href="!xrphp">xmlrpc.php</a>. For more information, see <a href="!xrdocs">the online documentation for the XML-RPC module</a>.', array('!xrphp' => \Drupal::url('xmlrpc.php'),'!xrdocs' => 'https://drupal.org/documentation/modules/xmlrpc')) . '</p>';
return $output;
}
}
/**
* Performs one or more XML-RPC request(s).
*
* Usage example:
* @code
* $result = xmlrpc('http://example.com/xmlrpc.php', array(
* 'service.methodName' => array($parameter, $second, $third),
* ));
* @endcode
*
* @param string $url
* An absolute URL of the XML-RPC endpoint.
* @param array $args
* An associative array whose keys are the methods to call and whose values
* are the arguments to pass to the respective method. If multiple methods
* are specified, a system.multicall is performed.
* @param array $headers
* (optional) An array of headers to pass along.
*
* @return
* For one request:
* Either the return value of the method on success, or FALSE.
* If FALSE is returned, see xmlrpc_errno() and xmlrpc_error_msg().
* For multiple requests:
* An array of results. Each result will either be the result
* returned by the method called, or an xmlrpc_error object if the call
* failed. See xmlrpc_error().
*
* @ingroup third_party
*/
function xmlrpc($url, array $args, array $headers = array()) {
module_load_include('inc', 'xmlrpc');
return _xmlrpc($url, $args, $headers);
}
xmlrpc.php:
path: '/xmlrpc.php'
defaults:
_title: 'XML-RPC'
_content: '\Drupal\xmlrpc\Controller\XmlrpcController::php'
requirements:
_access: 'TRUE'
This diff is collapsed.
......@@ -190,7 +190,7 @@ function simpletest_script_help() {
as the names of test groups as shown at
admin/config/development/testing.
These group names typically correspond to module names like "User"
or "Profile" or "System", but there is also a group "XML-RPC".
or "Profile" or "System", but there is also a group "Database".
If --class is specified then these are interpreted as the names of
specific test classes whose test methods will be run. Tests must
be separated by commas. Ignored if --all is specified.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment