From 38756eae46a87c7315b375b6ca6e484c9aacb1e9 Mon Sep 17 00:00:00 2001 From: Prateek Saxena Date: Wed, 25 Apr 2018 10:52:19 +0530 Subject: [PATCH] Special:Log: Convert to HTMLForm * All Xml generation code has been removed. * The LogEventsListGetExtraInputs hook now needs a form descriptor array and not plain HTML. See I37e0d3e81a46239750465b9299279fbbd7c7f87a. * LogPager and LogEventList also take the $day parameter for 'From date (and earlier)' and pass it to getDateCond as well. * Since FormOptions can't automatically extract the date from the request this is being done manually in the execute method of Special:Log using MWTimestamp. Bug: T117737 Change-Id: Iba3c6aa5ac40dcdee0792c2d045b238b02d76521 --- RELEASE-NOTES-1.32 | 2 + docs/hooks.txt | 2 +- includes/logging/LogEventsList.php | 236 +++++++++++++---------------- includes/logging/LogPager.php | 18 ++- includes/specials/SpecialLog.php | 14 ++ languages/i18n/en.json | 4 + languages/i18n/qqq.json | 4 + 7 files changed, 148 insertions(+), 132 deletions(-) diff --git a/RELEASE-NOTES-1.32 b/RELEASE-NOTES-1.32 index c41e5b5dc8..00f10a9c6c 100644 --- a/RELEASE-NOTES-1.32 +++ b/RELEASE-NOTES-1.32 @@ -179,6 +179,8 @@ because of Phabricator reports. CapsuleMultiselectWidget. The following methods may no longer be used: * setItemsFromData: Use setValue instead * getItemsData: Use getItems instead and get the data property +* The hook 'LogEventsListGetExtraInputs' now needs a form descriptor array + and not plain HTML. === Deprecations in 1.32 === * Use of a StartProfiler.php file is deprecated in favour of placing diff --git a/docs/hooks.txt b/docs/hooks.txt index 708456c926..ff465033ad 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -2182,7 +2182,7 @@ $autocreated: Boolean, whether this was an auto-creation Special:Log for a specific log type $type: String of log type being displayed $logEventsList: LogEventsList object for context and access to the WebRequest -&$input: string HTML of an input element +&$formDescriptor: array HTMLForm's form descriptor 'LogEventsListShowLogExtract': Called before the string is added to OutputPage. Returning false will prevent the string from being added to the OutputPage. diff --git a/includes/logging/LogEventsList.php b/includes/logging/LogEventsList.php index 17d15d683a..fdcaa1b43f 100644 --- a/includes/logging/LogEventsList.php +++ b/includes/logging/LogEventsList.php @@ -101,99 +101,94 @@ class LogEventsList extends ContextSource { * @param int|string $year Use 0 to start with no year preselected. * @param int|string $month A month in the 1..12 range. Use 0 to start with no month * preselected. + * @param int|string $day A day in the 1..31 range. Use 0 to start with no month + * preselected. * @param array|null $filter * @param string $tagFilter Tag to select by default * @param string|null $action */ public function showOptions( $types = [], $user = '', $page = '', $pattern = false, $year = 0, - $month = 0, $filter = null, $tagFilter = '', $action = null + $month = 0, $day = 0, $filter = null, $tagFilter = '', $action = null ) { - global $wgScript, $wgMiserMode; - $title = SpecialPage::getTitleFor( 'Log' ); // For B/C, we take strings, but make sure they are converted... $types = ( $types === '' ) ? [] : (array)$types; - $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter, false, $this->getContext() ); - - $html = Html::hidden( 'title', $title->getPrefixedDBkey() ); + $formDescriptor = []; // Basic selectors - $html .= $this->getTypeMenu( $types ) . "\n"; - $html .= $this->getUserInput( $user ) . "\n"; - $html .= $this->getTitleInput( $page ) . "\n"; - $html .= $this->getExtraInputs( $types ) . "\n"; + $formDescriptor['type'] = $this->getTypeMenuDesc( $types ); + $formDescriptor['user'] = $this->getUserInputDesc( $user ); + $formDescriptor['page'] = $this->getTitleInputDesc( $title ); + + // Add extra inputs if any + $extraInputsDescriptor = $this->getExtraInputsDesc( $types ); + if ( !empty( $extraInputsDescriptor ) ) { + $formDescriptor[ 'extra' ] = $extraInputsDescriptor; + } // Title pattern, if allowed - if ( !$wgMiserMode ) { - $html .= $this->getTitlePattern( $pattern ) . "\n"; + if ( !$this->getConfig()->get( 'MiserMode' ) ) { + $formDescriptor['pattern'] = $this->getTitlePatternDesc( $pattern ); } - // date menu - $html .= Xml::tags( 'p', null, Xml::dateMenu( (int)$year, (int)$month ) ); + // Date menu + $formDescriptor['date'] = [ + 'type' => 'date', + 'label-message' => 'date' + ]; // Tag filter - if ( $tagSelector ) { - $html .= Xml::tags( 'p', null, implode( "\u{00A0}", $tagSelector ) ); - } + $formDescriptor['tagfilter'] = [ + 'type' => 'tagfilter', + 'name' => 'tagfilter', + 'label-raw' => $this->msg( 'tag-filter' )->parse(), + ]; // Filter links if ( $filter ) { - $html .= Xml::tags( 'p', null, $this->getFilterLinks( $filter ) ); + $formDescriptor['filters'] = $this->getFiltersDesc( $filter ); } // Action filter - if ( $action !== null ) { - $html .= Xml::tags( 'p', null, $this->getActionSelector( $types, $action ) ); + if ( + $action !== null && + $this->allowedActions !== null && + count( $this->allowedActions ) > 0 + ) { + $formDescriptor['subtype'] = $this->getActionSelectorDesc( $types, $action ); } - // Submit button - $html .= Xml::submitButton( $this->msg( 'logeventslist-submit' )->text() ); - - // Fieldset - $html = Xml::fieldset( $this->msg( 'log' )->text(), $html ); - - // Form wrapping - $html = Xml::tags( 'form', [ 'action' => $wgScript, 'method' => 'get' ], $html ); + $htmlForm = new HTMLForm( $formDescriptor, $this->getContext() ); + $htmlForm + ->setSubmitText( $this->msg( 'logeventslist-submit' )->text() ) + ->setWrapperLegendMsg( 'log' ); - $this->getOutput()->addHTML( $html ); + $htmlForm->prepareForm()->displayForm( false ); } /** * @param array $filter - * @return string Formatted HTML + * @return array Form descriptor */ - private function getFilterLinks( $filter ) { - // show/hide links - $messages = [ $this->msg( 'show' )->text(), $this->msg( 'hide' )->text() ]; - // Option value -> message mapping - $links = []; - $hiddens = ''; // keep track for "go" button - $linkRenderer = $this->getLinkRenderer(); + private function getFiltersDesc( $filter ) { + $options = []; + $default = []; foreach ( $filter as $type => $val ) { - // Should the below assignment be outside the foreach? - // Then it would have to be copied. Not certain what is more expensive. - $query = $this->getDefaultQuery(); - $queryKey = "hide_{$type}_log"; + $options[ $this->msg( "logeventslist-{$type}-log" )->text() ] = $type; - $hideVal = $val ? 0 : 1; - $query[$queryKey] = $hideVal; - - $link = $linkRenderer->makeKnownLink( - $this->getTitle(), - $messages[$hideVal], - [], - $query - ); - - // Message: log-show-hide-patrol - $links[$type] = $this->msg( "log-show-hide-{$type}" )->rawParams( $link )->escaped(); - $hiddens .= Html::hidden( "hide_{$type}_log", $val ) . "\n"; + if ( $val === 0 ) { + $default[] = $type; + } } - - // Build links - return '' . $this->getLanguage()->pipeList( $links ) . '' . $hiddens; + return [ + 'class' => 'HTMLMultiSelectField', + 'label-message' => 'logeventslist-more-filters', + 'flatlist' => true, + 'options' => $options, + 'default' => $default, + ]; } private function getDefaultQuery() { @@ -213,22 +208,11 @@ class LogEventsList extends ContextSource { /** * @param array $queryTypes - * @return string Formatted HTML + * @return array Form descriptor */ - private function getTypeMenu( $queryTypes ) { + private function getTypeMenuDesc( $queryTypes ) { $queryType = count( $queryTypes ) == 1 ? $queryTypes[0] : ''; - $selector = $this->getTypeSelector(); - $selector->setDefault( $queryType ); - return $selector->getHTML(); - } - - /** - * Returns log page selector. - * @return XmlSelect - * @since 1.19 - */ - public function getTypeSelector() { $typesByName = []; // Temporary array // First pass to load the log names foreach ( LogPage::validTypes() as $type ) { @@ -247,62 +231,57 @@ class LogEventsList extends ContextSource { unset( $typesByName[''] ); $typesByName = [ '' => $public ] + $typesByName; - $select = new XmlSelect( 'type' ); - foreach ( $typesByName as $type => $name ) { - $select->addOption( $name, $type ); - } - - return $select; + return [ + 'class' => 'HTMLSelectField', + 'name' => 'type', + 'options' => array_flip( $typesByName ), + 'default' => $queryType, + ]; } /** * @param string $user - * @return string Formatted HTML + * @return array Form descriptor */ - private function getUserInput( $user ) { - $label = Xml::inputLabel( - $this->msg( 'specialloguserlabel' )->text(), - 'user', - 'mw-log-user', - 15, - $user, - [ 'class' => 'mw-autocomplete-user' ] - ); - - return '' . $label . ''; + private function getUserInputDesc( $user ) { + return [ + 'class' => 'HTMLUserTextField', + 'label-message' => 'specialloguserlabel', + 'name' => 'user', + ]; } /** * @param string $title - * @return string Formatted HTML + * @return array Form descriptor */ - private function getTitleInput( $title ) { - $label = Xml::inputLabel( - $this->msg( 'speciallogtitlelabel' )->text(), - 'page', - 'mw-log-page', - 20, - $title - ); - - return '' . $label . ''; + private function getTitleInputDesc( $title ) { + return [ + 'class' => 'HTMLTitleTextField', + 'label-message' => 'speciallogtitlelabel', + 'name' => 'page', + 'value' => $title, + 'required' => false + ]; } /** * @param bool $pattern - * @return string Checkbox + * @return array Form descriptor */ - private function getTitlePattern( $pattern ) { - return '' . - Xml::checkLabel( $this->msg( 'log-title-wildcard' )->text(), 'pattern', 'pattern', $pattern ) . - ''; + private function getTitlePatternDesc( $pattern ) { + return [ + 'type' => 'check', + 'label-message' => 'log-title-wildcard', + 'name' => 'pattern', + ]; } /** * @param array $types - * @return string + * @return array Form descriptor */ - private function getExtraInputs( $types ) { + private function getExtraInputsDesc( $types ) { if ( count( $types ) == 1 ) { if ( $types[0] == 'suppress' ) { $offender = $this->getRequest()->getVal( 'offender' ); @@ -310,42 +289,45 @@ class LogEventsList extends ContextSource { if ( !$user || ( $user->getId() == 0 && !IP::isIPAddress( $offender ) ) ) { $offender = ''; // Blank field if invalid } - return Xml::inputLabel( $this->msg( 'revdelete-offender' )->text(), 'offender', - 'mw-log-offender', 20, $offender ); + return [ + 'type' => 'text', + 'label-message' => 'revdelete-offender', + 'name' => 'offender', + 'value' => $offender, + ]; } else { // Allow extensions to add their own extra inputs - $input = ''; - Hooks::run( 'LogEventsListGetExtraInputs', [ $types[0], $this, &$input ] ); - return $input; + $formDescriptor = []; + Hooks::run( 'LogEventsListGetExtraInputs', [ $types[0], $this, &$formDescriptor ] ); + return $formDescriptor; } } - return ''; + return []; } /** * Drop down menu for selection of actions that can be used to filter the log * @param array $types * @param string $action - * @return string - * @since 1.27 + * @return array Form descriptor */ - private function getActionSelector( $types, $action ) { - if ( $this->allowedActions === null || !count( $this->allowedActions ) ) { - return ''; - } - $html = ''; - $html .= Xml::label( wfMessage( 'log-action-filter-' . $types[0] )->text(), - 'action-filter-' .$types[0] ) . "\n"; - $select = new XmlSelect( 'subtype' ); - $select->addOption( wfMessage( 'log-action-filter-all' )->text(), '' ); + private function getActionSelectorDesc( $types, $action ) { + $actionOptions = []; + $actionOptions[ 'log-action-filter-all' ] = ''; + foreach ( $this->allowedActions as $value ) { $msgKey = 'log-action-filter-' . $types[0] . '-' . $value; - $select->addOption( wfMessage( $msgKey )->text(), $value ); + $actionOptions[ $msgKey ] = $value; } - $select->setDefault( $action ); - $html .= $select->getHTML(); - return $html; + + return [ + 'class' => 'HTMLSelectField', + 'name' => 'subtype', + 'options-messages' => $actionOptions, + 'default' => $action, + 'label' => $this->msg( 'log-action-filter-' . $types[0] )->text(), + ]; } /** diff --git a/includes/logging/LogPager.php b/includes/logging/LogPager.php index 6b177aef9a..2efb462b90 100644 --- a/includes/logging/LogPager.php +++ b/includes/logging/LogPager.php @@ -63,13 +63,14 @@ class LogPager extends ReverseChronologicalPager { * @param array $conds Extra conditions for the query * @param int|bool $year The year to start from. Default: false * @param int|bool $month The month to start from. Default: false + * @param int|bool $day The day to start from. Default: false * @param string $tagFilter Tag * @param string $action Specific action (subtype) requested * @param int $logId Log entry ID, to limit to a single log entry. */ public function __construct( $list, $types = [], $performer = '', $title = '', - $pattern = false, $conds = [], $year = false, $month = false, $tagFilter = '', - $action = '', $logId = false + $pattern = false, $conds = [], $year = false, $month = false, $day = false, + $tagFilter = '', $action = '', $logId = false ) { parent::__construct( $list->getContext() ); $this->mConds = $conds; @@ -80,7 +81,7 @@ class LogPager extends ReverseChronologicalPager { $this->limitPerformer( $performer ); $this->limitTitle( $title, $pattern ); $this->limitAction( $action ); - $this->getDateCond( $year, $month ); + $this->getDateCond( $year, $month, $day ); $this->mTagFilter = $tagFilter; $this->limitLogId( $logId ); @@ -91,6 +92,7 @@ class LogPager extends ReverseChronologicalPager { $query = parent::getDefaultQuery(); $query['type'] = $this->typeCGI; // arrays won't work here $query['user'] = $this->performer; + $query['day'] = $this->mDay; $query['month'] = $this->mMonth; $query['year'] = $this->mYear; @@ -104,8 +106,12 @@ class LogPager extends ReverseChronologicalPager { if ( count( $this->types ) ) { return $filters; } + + $request_filters = $this->getRequest()->getArray( "wpfilters" ); + $request_filters = $request_filters === null ? [] : $request_filters; + foreach ( $wgFilterLogTypes as $type => $default ) { - $hide = $this->getRequest()->getBool( "hide_{$type}_log", $default ); + $hide = !in_array( $type, $request_filters ); $filters[$type] = $hide; if ( $hide ) { @@ -413,6 +419,10 @@ class LogPager extends ReverseChronologicalPager { return $this->mMonth; } + public function getDay() { + return $this->mDay; + } + public function getTagFilter() { return $this->mTagFilter; } diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php index bad17466b5..359eedea78 100644 --- a/includes/specials/SpecialLog.php +++ b/includes/specials/SpecialLog.php @@ -46,6 +46,7 @@ class SpecialLog extends SpecialPage { $opts->add( 'pattern', false ); $opts->add( 'year', null, FormOptions::INTNULL ); $opts->add( 'month', null, FormOptions::INTNULL ); + $opts->add( 'day', null, FormOptions::INTNULL ); $opts->add( 'tagfilter', '' ); $opts->add( 'offset', '' ); $opts->add( 'dir', '' ); @@ -59,6 +60,17 @@ class SpecialLog extends SpecialPage { $this->parseParams( $opts, (string)$par ); } + // Set date values + $dateString = $this->getRequest()->getVal( 'wpdate' ); + if ( !empty( $dateString ) ) { + $dateStamp = MWTimestamp::getInstance( $dateString . ' 00:00:00' ); + $dateStamp->setTimezone( $this->getConfig()->get( 'Localtimezone' ) ); + + $opts->setValue( 'year', (int)$dateStamp->format( 'Y' ) ); + $opts->setValue( 'month', (int)$dateStamp->format( 'm' ) ); + $opts->setValue( 'day', (int)$dateStamp->format( 'd' ) ); + } + # Don't let the user get stuck with a certain date if ( $opts->getValue( 'offset' ) || $opts->getValue( 'dir' ) == 'prev' ) { $opts->setValue( 'year', '' ); @@ -214,6 +226,7 @@ class SpecialLog extends SpecialPage { $extraConds, $opts->getValue( 'year' ), $opts->getValue( 'month' ), + $opts->getValue( 'day' ), $opts->getValue( 'tagfilter' ), $opts->getValue( 'subtype' ), $opts->getValue( 'logid' ) @@ -235,6 +248,7 @@ class SpecialLog extends SpecialPage { $pager->getPattern(), $pager->getYear(), $pager->getMonth(), + $pager->getDay(), $pager->getFilterParams(), $pager->getTagFilter(), $pager->getAction() diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 715f275df1..8d5bc7200c 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -2136,6 +2136,9 @@ "speciallogtitlelabel": "Target (title or {{ns:user}}:username for user):", "log": "Logs", "logeventslist-submit": "Show", + "logeventslist-more-filters": "More filters:", + "logeventslist-patrol-log": "Patrol log", + "logeventslist-tag-log": "Tag log", "all-logs-page": "All public logs", "alllogstext": "Combined display of all available logs of {{SITENAME}}.\nYou can narrow down the view by selecting a log type, the username (case-sensitive), or the affected page (also case-sensitive).", "logempty": "No matching items in log.", @@ -2506,6 +2509,7 @@ "uctop": "(current)", "month": "From month (and earlier):", "year": "From year (and earlier):", + "date": "From date (and earlier):", "sp-contributions-newbies": "Show contributions of new accounts only", "sp-contributions-newbies-sub": "For new accounts", "sp-contributions-newbies-title": "User contributions for new accounts", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index d9fef837ab..7732a92956 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -2336,6 +2336,9 @@ "speciallogtitlelabel": "Used in [[Special:Log]] as a label for an input field with which the log can be filtered. This filter selects for pages or users on which a log action was performed.", "log": "{{doc-special|Log}}\n{{Identical|Log}}", "logeventslist-submit": "Submit button on [[Special:Log]]\n{{Identical|Show}}", + "logeventslist-more-filters": "More filters label on [[Special:Log]]", + "logeventslist-patrol-log": "Patrol log option label on [[Special:Log]]", + "logeventslist-tag-log": "Patrol log option label on [[Special:Log]]", "all-logs-page": "{{doc-logpage}}\nTitle of [[Special:Log]].", "alllogstext": "Header of [[Special:Log]]", "logempty": "Used as warning when there are no items to show.", @@ -2706,6 +2709,7 @@ "uctop": "This message is used in [[Special:Contributions]]. It is used to show that a particular edit was the last made to a page. Example: 09:57, 11 February 2008 (hist) (diff) Pagename‎ (edit summary) (current)\n{{Identical|Current}}", "month": "Used in [[Special:Contributions]] and history pages ([{{fullurl:Sandbox|action=history}} example]), as label for a dropdown box to select a specific month to view the edits made in that month, and the earlier months. See also {{msg-mw|year}}.", "year": "Used in [[Special:Contributions]] and history pages ([{{fullurl:Sandbox|action=history}} example]), as label for an input box to select a specific year to view the edits made in that year, and the earlier years.\n\nSee also:\n* {{msg-mw|month}}", + "date": "Used in [[Special:Contributions]] and history pages ([{{fullurl:Sandbox|action=history}} example]), as label for an input box to select a specific date to view the edits made on that date, and earlier.", "sp-contributions-newbies": "Text of radio button on special page [[Special:Contributions]].", "sp-contributions-newbies-sub": "Note at the top of the page of results for a search on [[Special:Contributions]] where 'Show contributions for new accounts only' has been selected.", "sp-contributions-newbies-title": "The page title in your browser bar, but not the page title.\n\nSee also:\n* {{msg-mw|Sp-contributions-newbies-sub}}", -- 2.20.1