Special:EditTags, generally accessed via the revision-deletion-like interface
on history pages and Special:Log is likely to be more useful.
* Added 'applychangetags' and 'changetags' user rights.
+* (T35235) LogFormatter subclasses are now responsible for formatting the
+ parameters for API log event output. Extensions should implement the new
+ getParametersForApi() method in their log formatters.
==== External libraries ====
* MediaWiki now requires certain external libraries to be installed. In the past
* Default type param for query list=watchlist and list=recentchanges has
been changed from all types (e.g. including 'external') to 'edit|new|log'.
* Added formatversion to format=json, still experimental.
+* (T73020) Log event details are now always under a 'params' subkey for
+ list=logevents, and a 'logparams' subkey for list=watchlist and
+ list=recentchanges.
+* Log event details are changing formatting:
+ * block events now report flags as an array rather than as a comma-separated
+ list.
+ * patrol events now report the 'auto' flag as a boolean (absent/empty string
+ for BC formats) rather than as an integer.
+ * rights events now report the old and new group lists as arrays rather than
+ as comma-separated lists.
+ * merge events use new-style formatting.
+ * delete/event and delete/revision events use new-style formatting.
=== Action API internal changes in 1.25 ===
* ApiHelp has been rewritten to support i18n and paginated HTML output.
* ApiResult/ApiFormatBase "raw mode" is deprecated.
* ApiFormatXml now assumes defaults and so on instead of throwing errors when
metadata isn't set.
+* (T35235) LogFormatter subclasses are now responsible for formatting log event
+ parameters for the API.
* The following methods have been deprecated and may be removed in a future
release:
* ApiBase::getDescription
* ApiResult::size
* ApiResult::convertStatusToArray
* ApiQueryImageInfo::getPropertyDescriptions
+ * ApiQueryLogEvents::addLogParams
* The following classes have been deprecated and may be removed in a future
release:
* ApiQueryDeletedrevs
'UploadFromStash' => __DIR__ . '/includes/upload/UploadFromStash.php',
'UploadFromUrl' => __DIR__ . '/includes/upload/UploadFromUrl.php',
'UploadFromUrlJob' => __DIR__ . '/includes/jobqueue/jobs/UploadFromUrlJob.php',
+ 'UploadLogFormatter' => __DIR__ . '/includes/logging/UploadLogFormatter.php',
'UploadSourceAdapter' => __DIR__ . '/includes/Import.php',
'UploadSourceField' => __DIR__ . '/includes/specials/SpecialUpload.php',
'UploadStash' => __DIR__ . '/includes/upload/UploadStash.php',
);
/**
- * The same as above, but here values are names of functions,
+ * The same as above, but here values are names of classes,
* not messages.
* @see LogPage::actionText
* @see LogFormatter
'patrol/patrol' => 'PatrolLogFormatter',
'rights/rights' => 'RightsLogFormatter',
'rights/autopromote' => 'RightsLogFormatter',
- 'upload/upload' => 'LogFormatter',
- 'upload/overwrite' => 'LogFormatter',
- 'upload/revert' => 'LogFormatter',
+ 'upload/upload' => 'UploadLogFormatter',
+ 'upload/overwrite' => 'UploadLogFormatter',
+ 'upload/revert' => 'UploadLogFormatter',
'merge/merge' => 'MergeLogFormatter',
'tag/update' => 'TagLogFormatter',
'managetags/create' => 'LogFormatter',
}
/**
+ * @deprecated since 1.25 Use LogFormatter::formatParametersForApi instead
* @param ApiResult $result
* @param array $vals
* @param string $params
public static function addLogParams( $result, &$vals, $params, $type,
$action, $ts, $legacy = false
) {
- switch ( $type ) {
- case 'move':
- if ( $legacy ) {
- $targetKey = 0;
- $noredirKey = 1;
- } else {
- $targetKey = '4::target';
- $noredirKey = '5::noredir';
- }
+ wfDeprecated( __METHOD__, '1.25' );
- if ( isset( $params[$targetKey] ) ) {
- $title = Title::newFromText( $params[$targetKey] );
- if ( $title ) {
- $vals2 = array();
- ApiQueryBase::addTitleInfo( $vals2, $title, 'new_' );
- $vals[$type] = $vals2;
- }
- }
- if ( isset( $params[$noredirKey] ) && $params[$noredirKey] ) {
- $vals[$type]['suppressedredirect'] = '';
- }
- $params = null;
- break;
- case 'patrol':
- if ( $legacy ) {
- $cur = 0;
- $prev = 1;
- $auto = 2;
- } else {
- $cur = '4::curid';
- $prev = '5::previd';
- $auto = '6::auto';
- }
- $vals2 = array();
- $vals2['cur'] = $params[$cur];
- $vals2['prev'] = $params[$prev];
- $vals2['auto'] = $params[$auto];
- $vals[$type] = $vals2;
- $params = null;
- break;
- case 'rights':
- $vals2 = array();
- if ( $legacy ) {
- list( $vals2['old'], $vals2['new'] ) = $params;
- } else {
- $vals2['new'] = implode( ', ', $params['5::newgroups'] );
- $vals2['old'] = implode( ', ', $params['4::oldgroups'] );
- }
- $vals[$type] = $vals2;
- $params = null;
- break;
- case 'block':
- if ( $action == 'unblock' ) {
- break;
- }
- if ( $legacy ) {
- $durationKey = 0;
- $flagsKey = 1;
- } else {
- $durationKey = '5::duration';
- $flagsKey = '6::flags';
- }
- $vals2 = array();
- $vals2['duration'] = $params[$durationKey];
- $vals2['flags'] = isset( $params[$flagsKey] ) ? $params[$flagsKey] : '';
-
- // Indefinite blocks have no expiry time
- if ( SpecialBlock::parseExpiryInput( $params[$durationKey] ) !== 'infinity' ) {
- $vals2['expiry'] = wfTimestamp( TS_ISO_8601,
- strtotime( $params[$durationKey], wfTimestamp( TS_UNIX, $ts ) ) );
- }
- $vals[$type] = $vals2;
- $params = null;
- break;
- case 'upload':
- if ( isset( $params['img_timestamp'] ) ) {
- $params['img_timestamp'] = wfTimestamp( TS_ISO_8601, $params['img_timestamp'] );
- }
- break;
- case 'merge':
- // replace the named parameter with numbered for backward compatibility
- if ( isset( $params['4::dest'] ) ) {
- $params[] = $params['4::dest'];
- unset( $params['4::dest'] );
- }
- if ( isset( $params['5::mergepoint'] ) ) {
- $params[] = $params['5::mergepoint'];
- unset( $params['5::mergepoint'] );
- }
- break;
- case 'delete':
- if ( $action === 'event' || $action === 'revision' ) {
- // replace the named parameter with numbered for backward compatibility
- if ( $action === 'event' ) {
- $idsKey = '4::ids';
- $ofieldKey = '5::ofield';
- $nfieldKey = '6::nfield';
- } else {
- if ( isset( $params['4::type'] ) ) {
- $params[] = $params['4::type'];
- unset( $params['4::type'] );
- }
- $idsKey = '5::ids';
- $ofieldKey = '6::ofield';
- $nfieldKey = '7::nfield';
- }
- if ( isset( $params[$idsKey] ) ) {
- $params[] = implode( ',', $params[$idsKey] );
- unset( $params[$idsKey] );
- }
- if ( isset( $params[$ofieldKey] ) ) {
- $params[] = 'ofield=' . $params[$ofieldKey];
- unset( $params[$ofieldKey] );
- }
- if ( isset( $params[$nfieldKey] ) ) {
- $params[] = 'nfield=' . $params[$nfieldKey];
- unset( $params[$nfieldKey] );
- }
- }
- break;
- }
- if ( !is_null( $params ) ) {
- $logParams = array();
- // Keys like "4::paramname" can't be used for output so we change them to "paramname"
- foreach ( $params as $key => $value ) {
- if ( strpos( $key, ':' ) === false ) {
- $logParams[$key] = $value;
- continue;
- }
- $logParam = explode( ':', $key, 3 );
- $logParams[$logParam[2]] = $value;
- }
- ApiResult::setIndexedTagName( $logParams, 'param' );
- ApiResult::setIndexedTagNameOnSubarrays( $logParams, 'param' );
- $vals = array_merge( $vals, $logParams );
- }
+ $entry = new ManualLogEntry( $type, $action );
+ $entry->setParameters( $params );
+ $entry->setTimestamp( $ts );
+ $entry->setLegacy( $legacy );
+ $formatter = LogFormatter::newFromEntry( $entry );
+ $vals['params'] = $formatter->formatParametersForApi();
return $vals;
}
$vals['logpage'] = intval( $row->log_page );
}
if ( $this->fld_details && $row->log_params !== '' ) {
- self::addLogParams(
- $this->getResult(),
- $vals,
- $logEntry->getParameters(),
- $logEntry->getType(),
- $logEntry->getSubtype(),
- $logEntry->getTimestamp(),
- $logEntry->isLegacy()
- );
+ $vals['params'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
}
}
}
$vals['logid'] = intval( $row->rc_logid );
$vals['logtype'] = $row->rc_log_type;
$vals['logaction'] = $row->rc_log_action;
- $logEntry = DatabaseLogEntry::newFromRow( $row );
- ApiQueryLogEvents::addLogParams(
- $this->getResult(),
- $vals,
- $logEntry->getParameters(),
- $logEntry->getType(),
- $logEntry->getSubtype(),
- $logEntry->getTimestamp(),
- $logEntry->isLegacy()
- );
+ $vals['logparams'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
}
}
$vals['logid'] = intval( $row->rc_logid );
$vals['logtype'] = $row->rc_log_type;
$vals['logaction'] = $row->rc_log_action;
- $logEntry = DatabaseLogEntry::newFromRow( $row );
- ApiQueryLogEvents::addLogParams(
- $this->getResult(),
- $vals,
- $logEntry->getParameters(),
- $logEntry->getType(),
- $logEntry->getSubtype(),
- $logEntry->getTimestamp(),
- $logEntry->isLegacy()
- );
+ $vals['logparams'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
}
}
return $messages[$flag];
}
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = $entry->getParameters();
+
+ static $map = array(
+ // While this looks wrong to be starting at 5 rather than 4, it's
+ // because getMessageParameters uses $4 for its own purposes.
+ '5::duration',
+ '6:array:flags',
+ '6::flags' => '6:array:flags',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $params[$index] ) ) {
+ $params[$key] = $params[$index];
+ unset( $params[$index] );
+ }
+ }
+
+ if ( isset( $params['6:array:flags'] ) && !is_array( $params['6:array:flags'] ) ) {
+ $params['6:array:flags'] = $params['6:array:flags'] === ''
+ ? array()
+ : explode( ',', $params['6:array:flags'] );
+ }
+
+ if ( isset( $params['5::duration'] ) &&
+ SpecialBlock::parseExpiryInput( $params['5::duration'] ) !== wfGetDB( DB_SLAVE )->getInfinity()
+ ) {
+ $ts = wfTimestamp( TS_UNIX, $entry->getTimestamp() );
+ $params[':timestamp:expiry'] = strtotime( $params['5::duration'], $ts );
+ }
+
+ return $params;
+ }
+
+ public function formatParametersForApi() {
+ $ret = parent::formatParametersForApi();
+ if ( isset( $ret['flags'] ) ) {
+ ApiResult::setIndexedTagName( $ret['flags'], 'f' );
+ }
+ return $ret;
+ }
+
}
return '';
}
}
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = array();
+
+ $subtype = $this->entry->getSubtype();
+ if ( in_array( $subtype, array( 'event', 'revision' ) ) ) {
+ $rawParams = $entry->getParameters();
+ if ( $subtype === 'event' ) {
+ array_unshift( $rawParams, 'logging' );
+ }
+
+ static $map = array(
+ '4::type',
+ '5::ids',
+ '6::ofield',
+ '7::nfield',
+ '4::ids' => '5::ids',
+ '5::ofield' => '6::ofield',
+ '6::nfield' => '7::nfield',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $rawParams[$index] ) ) {
+ $rawParams[$key] = $rawParams[$index];
+ unset( $rawParams[$index] );
+ }
+ }
+
+ $old = $this->parseBitField( $rawParams['6::ofield'] );
+ $new = $this->parseBitField( $rawParams['7::nfield'] );
+ if ( !is_array( $rawParams['5::ids'] ) ) {
+ $rawParams['5::ids'] = explode( ',', $rawParams['5::ids'] );
+ }
+
+ $params = array(
+ '::type' => $rawParams['4::type'],
+ ':array:ids' => $rawParams['5::ids'],
+ ':assoc:old' => array( 'bitmask' => $old ),
+ ':assoc:new' => array( 'bitmask' => $new ),
+ );
+
+ static $fields = array(
+ Revision::DELETED_TEXT => 'content',
+ Revision::DELETED_COMMENT => 'comment',
+ Revision::DELETED_USER => 'user',
+ Revision::DELETED_RESTRICTED => 'restricted',
+ );
+ foreach ( $fields as $bit => $key ) {
+ $params[':assoc:old'][$key] = (bool)( $old & $bit );
+ $params[':assoc:new'][$key] = (bool)( $new & $bit );
+ }
+ }
+
+ return $params;
+ }
+
+ public function formatParametersForApi() {
+ $ret = parent::formatParametersForApi();
+ if ( isset( $ret['ids'] ) ) {
+ ApiResult::setIndexedTagName( $ret['ids'], 'id' );
+ }
+ return $ret;
+ }
}
continue;
}
list( $index, $type, ) = explode( ':', $key, 3 );
- $params[$index - 1] = $this->formatParameterValue( $type, $value );
+ if ( ctype_digit( $index ) ) {
+ $params[$index - 1] = $this->formatParameterValue( $type, $value );
+ }
}
/* Message class doesn't like non consecutive numbering.
// problems with extensions
return $this->getMessageParameters();
}
+
+ /**
+ * Get the array of parameters, converted from legacy format if necessary.
+ * @since 1.25
+ * @return array
+ */
+ protected function getParametersForApi() {
+ return $this->entry->getParameters();
+ }
+
+ /**
+ * Format parameters for API output
+ *
+ * The result array should generally map named keys to values. Index and
+ * type should be omitted, e.g. "4::foo" should be returned as "foo" in the
+ * output. Values should generally be unformatted.
+ *
+ * Renames or removals of keys besides from the legacy numeric format to
+ * modern named style should be avoided. Any renames should be announced to
+ * the mediawiki-api-announce mailing list.
+ *
+ * @since 1.25
+ * @return array
+ */
+ public function formatParametersForApi() {
+ $logParams = array();
+ foreach ( $this->getParametersForApi() as $key => $value ) {
+ $vals = explode( ':', $key, 3 );
+ if ( count( $vals ) !== 3 ) {
+ $logParams[$key] = $value;
+ continue;
+ }
+ $logParams += $this->formatParameterValueForApi( $vals[2], $vals[1], $value );
+ }
+ ApiResult::setIndexedTagName( $logParams, 'param' );
+ ApiResult::setArrayType( $logParams, 'assoc' );
+
+ return $logParams;
+ }
+
+ /**
+ * Format a single parameter value for API output
+ *
+ * @since 1.25
+ * @param string $name
+ * @param string $type
+ * @param string $value
+ * @return array
+ */
+ protected function formatParameterValueForApi( $name, $type, $value ) {
+ $type = strtolower( trim( $type ) );
+ switch ( $type ) {
+ case 'bool':
+ $value = (bool)$value;
+ break;
+
+ case 'number':
+ if ( ctype_digit( $value ) ) {
+ $value = (int)$value;
+ } else {
+ $value = (float)$value;
+ }
+ break;
+
+ case 'array':
+ case 'assoc':
+ case 'kvp':
+ if ( is_array( $value ) ) {
+ ApiResult::setArrayType( $value, $type );
+ }
+ break;
+
+ case 'timestamp':
+ $value = wfTimestamp( TS_ISO_8601, $value );
+ break;
+
+ case 'msg':
+ case 'msg-content':
+ $msg = $this->msg( $value );
+ if ( $type === 'msg-content' ) {
+ $msg->inContentLanguage();
+ }
+ $value = array();
+ $value["{$name}_key"] = $msg->getKey();
+ if ( $msg->getParams() ) {
+ $value["{$name}_params"] = $msg->getParams();
+ }
+ $value["{$name}_text"] = $msg->text();
+ return $value;
+
+ case 'title':
+ case 'title-link':
+ $title = Title::newFromText( $value );
+ if ( $title ) {
+ $value = array();
+ ApiQueryBase::addTitleInfo( $value, $title, "{$name}_" );
+ }
+ return $value;
+
+ case 'user':
+ case 'user-link':
+ $user = User::newFromName( $value );
+ if ( $user ) {
+ $value = $user->getName();
+ }
+ break;
+
+ default:
+ // do nothing
+ break;
+ }
+
+ return array( $name => $value );
+ }
}
/**
return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
}
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = $entry->getParameters();
+
+ static $map = array(
+ '4:title:dest',
+ '5:timestamp:mergepoint',
+ '4::dest' => '4:title:dest',
+ '5::mergepoint' => '5:timestamp:mergepoint',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $params[$index] ) ) {
+ $params[$key] = $params[$index];
+ unset( $params[$index] );
+ }
+ }
+
+ return $params;
+ }
}
return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
}
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = $entry->getParameters();
+
+ static $map = array(
+ '4:title:target',
+ '5:bool:suppressredirect',
+ '4::target' => '4:title:target',
+ '5::noredir' => '5:bool:suppressredirect',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $params[$index] ) ) {
+ $params[$key] = $params[$index];
+ unset( $params[$index] );
+ }
+ }
+
+ return $params;
+ }
+
}
return $params;
}
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = $entry->getParameters();
+
+ static $map = array(
+ '4::curid',
+ '5::previd',
+ '6:bool:auto',
+ '6::auto' => '6:bool:auto',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $params[$index] ) ) {
+ $params[$key] = $params[$index];
+ unset( $params[$index] );
+ }
+ }
+
+ return $params;
+ }
}
return $params;
}
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = $entry->getParameters();
+
+ static $map = array(
+ '4:array:oldgroups',
+ '5:array:newgroups',
+ '4::oldgroups' => '4:array:oldgroups',
+ '5::newgroups' => '5:array:newgroups',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $params[$index] ) ) {
+ $params[$key] = $params[$index];
+ unset( $params[$index] );
+ }
+ }
+
+ return $params;
+ }
+
+ public function formatParametersForApi() {
+ $ret = parent::formatParametersForApi();
+ if ( isset( $ret['oldgroups'] ) ) {
+ ApiResult::setIndexedTagName( $ret['oldgroups'], 'g' );
+ }
+ if ( isset( $ret['newgroups'] ) ) {
+ ApiResult::setIndexedTagName( $ret['newgroups'], 'g' );
+ }
+ return $ret;
+ }
}
--- /dev/null
+<?php
+/**
+ * Formatter for upload log entries.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.25
+ */
+
+/**
+ * This class formats upload log entries.
+ *
+ * @since 1.25
+ */
+class UploadLogFormatter extends LogFormatter {
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = $entry->getParameters();
+
+ static $map = array(
+ 'img_timestamp' => ':timestamp:img_timestamp',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $params[$index] ) ) {
+ $params[$key] = $params[$index];
+ unset( $params[$index] );
+ }
+ }
+
+ return $params;
+ }
+
+}
: array();
if ( LogEventsList::userCan( $this->row, LogPage::DELETED_ACTION, $user ) ) {
- ApiQueryLogEvents::addLogParams(
- $result,
- $ret,
- $logEntry->getParameters(),
- $logEntry->getType(),
- $logEntry->getSubtype(),
- $logEntry->getTimestamp(),
- $logEntry->isLegacy()
- );
+ $ret['params'] = LogFormatter::newFromEntry( $logEntry )->formatParametersForApi();
}
if ( LogEventsList::userCan( $this->row, LogPage::DELETED_USER, $user ) ) {
$ret += array(
$this->assertEquals( $comment, $formatter->getComment() );
}
+
+ /**
+ * @dataProvider provideApiParamFormatting
+ * @covers LogFormatter::formatParametersForApi
+ * @covers LogFormatter::formatParameterValueForApi
+ */
+ public function testApiParamFormatting( $key, $value, $expected ) {
+ $entry = $this->newLogEntry( 'param', array( $key => $value ) );
+ $formatter = LogFormatter::newFromEntry( $entry );
+ $formatter->setContext( $this->context );
+
+ ApiResult::setIndexedTagName( $expected, 'param' );
+ ApiResult::setArrayType( $expected, 'assoc' );
+
+ $this->assertEquals( $expected, $formatter->formatParametersForApi() );
+ }
+
+ public static function provideApiParamFormatting() {
+ return array(
+ array( 0, 'value', array( 'value' ) ),
+ array( 'named', 'value', array( 'named' => 'value' ) ),
+ array( '::key', 'value', array( 'key' => 'value' ) ),
+ array( '4::key', 'value', array( 'key' => 'value' ) ),
+ array( '4:raw:key', 'value', array( 'key' => 'value' ) ),
+ array( '4:plain:key', 'value', array( 'key' => 'value' ) ),
+ array( '4:bool:key', '1', array( 'key' => true ) ),
+ array( '4:bool:key', '0', array( 'key' => false ) ),
+ array( '4:number:key', '123', array( 'key' => 123 ) ),
+ array( '4:number:key', '123.5', array( 'key' => 123.5 ) ),
+ array( '4:array:key', array(), array( 'key' => array( ApiResult::META_TYPE => 'array' ) ) ),
+ array( '4:assoc:key', array(), array( 'key' => array( ApiResult::META_TYPE => 'assoc' ) ) ),
+ array( '4:kvp:key', array(), array( 'key' => array( ApiResult::META_TYPE => 'kvp' ) ) ),
+ array( '4:timestamp:key', '20150102030405', array( 'key' => '2015-01-02T03:04:05Z' ) ),
+ array( '4:msg:key', 'parentheses', array(
+ 'key_key' => 'parentheses',
+ 'key_text' => wfMessage( 'parentheses' )->text(),
+ ) ),
+ array( '4:msg-content:key', 'parentheses', array(
+ 'key_key' => 'parentheses',
+ 'key_text' => wfMessage( 'parentheses' )->inContentLanguage()->text(),
+ ) ),
+ array( '4:title:key', 'project:foo', array(
+ 'key_ns' => NS_PROJECT,
+ 'key_title' => Title::newFromText( 'project:foo' )->getFullText(),
+ ) ),
+ array( '4:title-link:key', 'project:foo', array(
+ 'key_ns' => NS_PROJECT,
+ 'key_title' => Title::newFromText( 'project:foo' )->getFullText(),
+ ) ),
+ array( '4:user:key', 'foo', array( 'key' => 'Foo' ) ),
+ array( '4:user-link:key', 'foo', array( 'key' => 'Foo' ) ),
+ );
+ }
}