Mainframe98 has uploaded a new change for review. (
https://gerrit.wikimedia.org/r/387110 )
Change subject: Rework the internals of SpecialNewVideos
......................................................................
Rework the internals of SpecialNewVideos
This was the easiest way to add support for expiring user groups.
Additionally, this brings many more improvements, free of charge!
Special:NewVideos now has a OOUI interface and many more options
to sort by. SQL queries now use a wrapper, improving database
compatibility. All while still maintaining backwards query
parameter compatibility with previous versions.
Bug: T160027
Change-Id: I693e0d0cb583aa95f3614497b34c9ad8e6eb5cc8
---
M extension.json
M i18n/en.json
M includes/specials/SpecialNewVideos.php
A includes/specials/pagers/NewVideosPager.php
4 files changed, 320 insertions(+), 228 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Video
refs/changes/10/387110/1
diff --git a/extension.json b/extension.json
index fb6c31d..d562611 100644
--- a/extension.json
+++ b/extension.json
@@ -1,6 +1,6 @@
{
"name": "Video",
- "version": "1.6.1",
+ "version": "1.7.1",
"author": [
"David Pean",
"Jack Phoenix",
@@ -67,7 +67,8 @@
"AddVideo": "includes/specials/SpecialAddVideo.php",
"NewVideos": "includes/specials/SpecialNewVideos.php",
"SpecialUndeleteWithVideoSupport":
"includes/specials/SpecialUndeleteWithVideoSupport.php",
- "VideoHooks": "includes/Video.hooks.php"
+ "VideoHooks": "includes/Video.hooks.php",
+ "NewVideosPager": "includes/specials/pagers/NewVideosPager.php"
},
"Hooks": {
"ArticleFromTitle": [
diff --git a/i18n/en.json b/i18n/en.json
index dbbdea0..88838aa 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -9,7 +9,7 @@
"newvideos": "New videos",
"video-upload-new-version": "Upload a new version of this video",
"video-newvideos-showfrom": "Show new videos starting from $2, $1",
- "video-newvideos-list-text": "Below is a list of <strong>$1</strong>
{{PLURAL:$1|video|videos}} sorted $2.",
+ "video-newvideos-list-text": "Below is a list of <strong>$1</strong>
{{PLURAL:$1|video|videos}}.",
"video-addvideo-watchlist": "Add this video to my watchlist",
"video-addvideo-title": "Add video",
"video-addvideo-dest": "Add video for $1",
@@ -43,5 +43,6 @@
"video-more-links-to-video": "View [[Special:WhatLinksHere/$1|more
links]] to this video.",
"video-showhidebots": "($1 bots)",
"action-addvideo": "add videos from external services into the site",
- "right-addvideo": "Add videos from external services into the site"
+ "right-addvideo": "Add videos from external services into the site",
+ "video-hidebots": "Hide uploads by bots"
}
diff --git a/includes/specials/SpecialNewVideos.php
b/includes/specials/SpecialNewVideos.php
index f0b9415..2374f37 100644
--- a/includes/specials/SpecialNewVideos.php
+++ b/includes/specials/SpecialNewVideos.php
@@ -8,6 +8,8 @@
*/
class NewVideos extends IncludableSpecialPage {
+ /** @var FormOptions */
+ protected $opts;
/**
* Constructor
@@ -21,7 +23,7 @@
*
* @return string
*/
- function getGroupName() {
+ public function getGroupName() {
return 'changes';
}
@@ -31,256 +33,169 @@
* @param mixed|null $par Parameter passed to the page or null
*/
public function execute( $par ) {
- global $wgGroupPermissions;
+ $context = new DerivativeContext( $this->getContext() );
$out = $this->getOutput();
- $request = $this->getRequest();
$lang = $this->getLanguage();
$out->setPageTitle( $this->msg( 'newvideos' ) );
- $wpIlMatch = $request->getText( 'wpIlMatch' );
- $dbr = wfGetDB( DB_REPLICA );
- $shownav = !$this->including();
- $hidebots = $request->getBool( 'hidebots', 1 );
+ $opts = new FormOptions();
- $hidebotsql = '';
- if ( $hidebots ) {
- /*
- * Make a list of group names which have the 'bot' flag
- * set.
- */
- $botconds = array();
- foreach ( $wgGroupPermissions as $groupname => $perms )
{
- if ( array_key_exists( 'bot', $perms ) &&
$perms['bot'] ) {
- $botconds[] = "ug_group='$groupname'";
- }
- }
+ $opts->add( 'wpIlMatch', '' ); // Known as 'like', but uses old
name for back-compat
+ $opts->add( 'user', '' );
+ $opts->add( 'hidebots', true );
+ $opts->add( 'newbies', false );
+ $opts->add( 'hidepatrolled', false );
+ $opts->add( 'limit', 48 ); // Back-compat, old value has always
been 48
+ $opts->add( 'offset', '' );
+ $opts->add( 'start', '' );
+ $opts->add( 'end', '' );
- /* If not bot groups, do not set $hidebotsql */
- if ( $botconds ) {
- $isbotmember = $dbr->makeList( $botconds,
LIST_OR );
+ $opts->fetchValuesFromRequest( $this->getRequest() );
- /*
- * This join, in conjunction with WHERE ug_group
- * IS NULL, returns only those rows from IMAGE
- * where the uploading user is not a member of
- * a group which has the 'bot' permission set.
- */
- $ug = $dbr->tableName( 'user_groups' );
- $hidebotsql = " LEFT OUTER JOIN $ug ON
video_user_name=ug_user AND ($isbotmember)";
- }
+ if ( $par !== null ) {
+ $opts->setValue( is_numeric( $par ) ? 'limit' :
'wpIlMatch', $par );
}
- $video = $dbr->tableName( 'video' );
+ $data = [ 'hidebots' => 1 ];
- $sql = "SELECT video_timestamp FROM $video";
- if ( $hidebotsql ) {
- $sql .= "$hidebotsql WHERE ug_group IS NULL";
- }
- $sql .= ' ORDER BY video_timestamp DESC LIMIT 1';
- $res = $dbr->query( $sql, __METHOD__ );
- $row = $dbr->fetchRow( $res );
- if ( $row !== false ) {
- $ts = $row[0];
- } else {
- $ts = false;
- }
- $sql = '';
+ // If start date comes after end date chronologically, swap
them.
+ // They are swapped in the interface by JS.
+ $start = $opts->getValue( 'start' );
+ $end = $opts->getValue( 'end' );
+ if ( $start !== '' && $end !== '' && $start > $end ) {
+ $temp = $end;
+ $end = $start;
+ $start = $temp;
- /** If we were clever, we'd use this to cache. */
- $latestTimestamp = wfTimestamp( TS_MW, $ts );
+ $opts->setValue( 'start', $start, true );
+ $opts->setValue( 'end', $end, true );
- /** Hardcode this for now. */
- $limit = 48;
-
- $parval = intval( $par );
- if ( $parval ) {
- if ( $parval <= $limit && $parval > 0 ) {
- $limit = $parval;
- }
+ // Since the request values must always be adjusted for
backwards compatibility with the
+ // previous parameter mix, parameter values are added
to $data here and the request
+ // re-creation is moved outside this block
+ $data['start'] = $start;
+ $data['end'] = $end;
}
- $where = array();
- $searchpar = array();
- if ( $wpIlMatch != '' ) {
- $nt = Title::newFromText( $wpIlMatch );
- if ( $nt ) {
- // LOWER() & friends don't work as-is on
varbinary fields
- // @see
https://phabricator.wikimedia.org/T157197
- $where[] = 'LOWER(CONVERT(video_name USING
utf8))' . $dbr->buildLike(
- $dbr->anyString(), strtolower(
$nt->getDBkey() ), $dbr->anyString() );
- $searchpar['wpIlMatch'] = $wpIlMatch;
- }
+ // Swap values in request object, which is used by HTMLForm to
pre-populate the fields with
+ // the previous input. Done every request to maintain backwards
parameter compatibility
+ $request = $context->getRequest();
+ $context->setRequest( new DerivativeRequest(
+ $request,
+ $data + $request->getValues(),
+ $request->wasPosted()
+ ) );
+
+ $opts->validateIntBounds( 'limit', 0, 500 );
+
+ $this->opts = $opts;
+
+ $pager = new NewVideosPager( $context, $opts );
+ // Store html output for a moment so the toptext can be shown
first.
+ // A workaround, as the number of videos isn't available before
calling getBody(),
+ // but the toptext must be shown before the gallery
+ $pagerBody = $pager->getBody();
+
+ if ( !$this->including() ) {
+ $lt = $lang->formatNum( min(
$pager->getShownVideosCount(), $opts->getValue( 'limit' ) ) );
+ $this->setTopText( $lt );
+ $this->buildForm( $context );
}
- $invertSort = false;
- if( $until = $request->getVal( 'until' ) ) {
- $where[] = 'video_timestamp < ' . $dbr->timestamp(
$until );
+ $out->addHTML( $pagerBody );
+ if ( !$this->including() ) {
+ $out->addHTML( $pager->getNavigationBar() );
}
- if( $from = $request->getVal( 'from' ) ) {
- $where[] = 'video_timestamp >= ' . $dbr->timestamp(
$from );
- $invertSort = true;
- }
- $sql = 'SELECT video_name, video_url, video_user_name,
video_user_id, '.
- " video_timestamp FROM $video";
+ }
- if ( $hidebotsql ) {
- $sql .= $hidebotsql;
- $where[] = 'ug_group IS NULL';
- }
- if ( count( $where ) ) {
- $sql.= ' WHERE ' . $dbr->makeList( $where, LIST_AND );
- }
- $sql.= ' ORDER BY video_timestamp '. ( $invertSort ? '' : '
DESC' );
- $sql.= ' LIMIT ' . ( $limit + 1 );
- $res = $dbr->query( $sql, __METHOD__ );
+ protected function buildForm( IContextSource $context ) {
+ $formDescriptor = [
+ 'wpIlMatch' => [
+ 'type' => 'text',
+ 'label-message' => 'newimages-label',
+ 'name' => 'wpIlMatch',
+ ],
+ 'user' => [
+ 'type' => 'text',
+ 'label-message' => 'newimages-user',
+ 'name' => 'user',
+ ],
+ 'newbies' => [
+ 'type' => 'check',
+ 'label-message' => 'newimages-newbies',
+ 'name' => 'newbies',
+ ],
+ 'hidebots' => [
+ 'type' => 'check',
+ 'label-message' => 'video-hidebots',
+ 'default' => $this->opts->getValue( 'hidebots'
),
+ 'name' => 'hidebots',
+ ],
+ 'hidepatrolled' => [
+ 'type' => 'check',
+ 'label-message' => 'newimages-hidepatrolled',
+ 'name' => 'hidepatrolled',
+ ],
+ 'limit' => [
+ 'type' => 'hidden',
+ 'default' => $this->opts->getValue( 'limit' ),
+ 'name' => 'limit',
+ ],
+ 'offset' => [
+ 'type' => 'hidden',
+ 'default' => $this->opts->getValue( 'offset' ),
+ 'name' => 'offset',
+ ],
+ 'start' => [
+ 'type' => 'date',
+ 'label-message' => 'date-range-from',
+ 'name' => 'start',
+ ],
+ 'end' => [
+ 'type' => 'date',
+ 'label-message' => 'date-range-to',
+ 'name' => 'end',
+ ],
+ ];
- // We have to flip things around to get the last N after a
certain date
- $videos = array();
- foreach ( $res as $s ) {
- if ( $invertSort ) {
- array_unshift( $videos, $s );
- } else {
- array_push( $videos, $s );
- }
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
+ unset( $formDescriptor['wpIlMatch'] );
}
- $gallery = new VideoGallery();
- $firstTimestamp = null;
- $lastTimestamp = null;
- $shownVideos = 0;
- foreach ( $videos as $s ) {
- if ( ++$shownVideos > $limit ) {
- // One extra just to test for whether to show a
page link;
- // don't actually show it.
- break;
- }
+ if ( !$this->getUser()->useFilePatrol() ) {
+ unset( $formDescriptor['hidepatrolled'] );
+ }
- $name = $s->video_name;
- $ut = $s->video_user_name;
+ HTMLForm::factory( 'ooui', $formDescriptor, $context )
+ // For the 'multiselect' field values to be preserved
on submit
+ ->setFormIdentifier( 'specialnewvideos' )
+ ->setWrapperLegendMsg( 'newimages-legend' )
+ ->setSubmitTextMsg( 'ilsubmit' )
+ ->setMethod( 'get' )
+ ->prepareForm()
+ ->displayForm( false );
+ }
- $nt = Title::newFromText( $name, NS_VIDEO );
- $vid = new Video( $nt, $this->getContext() );
- $ul = Linker::linkKnown( Title::makeTitle( NS_USER, $ut
), $ut );
+ /**
+ * Send the text to be displayed above the options
+ *
+ * @param string $lt number of videos displayed
+ */
+ public function setTopText( $lt ) {
+ global $wgContLang;
- $gallery->add(
- $vid,
- "$ul<br />\n<i>" .
- $lang->timeanddate(
$s->video_timestamp, true ) .
- "</i><br />\n"
+ $message = $this->msg( 'video-newvideos-list-text', $lt
)->inContentLanguage();
+ if ( !$message->isDisabled() ) {
+ $this->getOutput()->addWikiText(
+ Html::rawElement( 'p',
+ [ 'lang' => $wgContLang->getHtmlCode(),
'dir' => $wgContLang->getDir() ],
+ "\n" . $message->parse() . "\n"
+ ),
+ /* $lineStart */ false,
+ /* $interface */ false
);
-
- $timestamp = wfTimestamp( TS_MW, $s->video_timestamp );
- if ( empty( $firstTimestamp ) ) {
- $firstTimestamp = $timestamp;
- }
- $lastTimestamp = $timestamp;
- }
-
- $bydate = $this->msg( 'bydate' )->escaped();
- $lt = $lang->formatNum( min( $shownVideos, $limit ) );
- if ( $shownav ) {
- $text = $this->msg( 'video-newvideos-list-text', $lt,
$bydate )->parse();
- $out->addHTML( $text . "\n" );
- }
-
- $sub = $this->msg( 'ilsubmit' )->escaped();
- $titleObj = SpecialPage::getTitleFor( 'NewVideos' );
- $action = htmlspecialchars( $titleObj->getLocalURL( $hidebots ?
'' : 'hidebots=0' ) );
- if( $shownav ) {
- $out->addHTML(
- "<form id=\"imagesearch\" method=\"post\"
action=\"{$action}\">" .
- Xml::input( 'wpIlMatch', 20, $wpIlMatch ) . ' '
.
- Xml::submitButton( $sub, array( 'name' =>
'wpIlSubmit' ) ) .
- '</form>'
- );
- }
-
- // Paging controls...
-
- # If we change bot visibility, this needs to be carried along.
- if ( !$hidebots ) {
- $botpar = array( 'hidebots' => 0 );
- } else {
- $botpar = array();
- }
- $now = wfTimestampNow();
- $date = $lang->date( $now, true );
- $time = $lang->time( $now, true );
- $query = array_merge(
- array( 'from' => $now ),
- $botpar,
- $searchpar
- );
-
- $dateLink = Linker::linkKnown(
- $titleObj,
- htmlspecialchars( $this->msg(
'video-newvideos-showfrom', $date, $time )->escaped() ),
- array(),
- $query
- );
-
- $query = array_merge(
- array( 'hidebots' => ( $hidebots ? 0 : 1 ) ),
- $searchpar
- );
-
- $showhide = $hidebots ? $this->msg( 'show' )->escaped() :
$this->msg( 'hide' )->escaped();
-
- $botLink = Linker::linkKnown(
- $titleObj,
- htmlspecialchars( $this->msg( 'video-showhidebots',
$showhide )->escaped() ),
- array(),
- $query
- );
-
- $prevLink = $this->msg( 'pager-newer-n', $lang->formatNum(
$limit ) )->parse();
- if ( $firstTimestamp && $firstTimestamp != $latestTimestamp ) {
- $query = array_merge(
- array( 'from' => $firstTimestamp ),
- $botpar,
- $searchpar
- );
- $prevLink = Linker::linkKnown(
- $titleObj,
- $prevLink,
- array(),
- $query
- );
- }
-
- $nextLink = $this->msg( 'pager-older-n', $lang->formatNum(
$limit ) )->parse();
- if ( $shownVideos > $limit && $lastTimestamp ) {
- $query = array_merge(
- array( 'until' => $lastTimestamp ),
- $botpar,
- $searchpar
- );
-
- $nextLink = Linker::linkKnown(
- $titleObj,
- $nextLink,
- array(),
- $query
- );
- }
-
- $prevnext = '<p>' . $botLink . ' ' .
- $this->msg( 'viewprevnext', $prevLink, $nextLink,
$dateLink )->text() .
- '</p>';
-
- if ( $shownav ) {
- $out->addHTML( $prevnext );
- }
-
- if ( count( $videos ) ) {
- $out->addHTML( $gallery->toHTML() );
- if ( $shownav ) {
- $out->addHTML( $prevnext );
- }
- } else {
- $out->addWikiMsg( 'video-no-videos' );
}
}
}
diff --git a/includes/specials/pagers/NewVideosPager.php
b/includes/specials/pagers/NewVideosPager.php
new file mode 100644
index 0000000..a231346
--- /dev/null
+++ b/includes/specials/pagers/NewVideosPager.php
@@ -0,0 +1,175 @@
+<?php
+/**
+ * @ingroup Pager
+ */
+use MediaWiki\MediaWikiServices;
+
+class NewVideosPager extends RangeChronologicalPager {
+
+ /**
+ * @var VideoGallery
+ */
+ protected $gallery;
+
+ /**
+ * @var FormOptions
+ */
+ protected $opts;
+
+ /**
+ * @param IContextSource $context
+ * @param FormOptions $opts
+ */
+ function __construct( IContextSource $context, FormOptions $opts ) {
+ parent::__construct( $context );
+
+ $this->opts = $opts;
+ $this->setLimit( $opts->getValue( 'limit' ) );
+
+ $startTimestamp = '';
+ $endTimestamp = '';
+ if ( $opts->getValue( 'start' ) ) {
+ $startTimestamp = $opts->getValue( 'start' ) . '
00:00:00';
+ }
+ if ( $opts->getValue( 'end' ) ) {
+ $endTimestamp = $opts->getValue( 'end' ) . ' 23:59:59';
+ }
+ $this->getDateRangeCond( $startTimestamp, $endTimestamp );
+ }
+
+ function formatRow( $row ) {
+ $name = $row->video_name;
+ $user = User::newFromId( $row->video_user_id );
+
+ $title = Title::makeTitle( NS_VIDEO, $name );
+ $video = new Video( $title, $this->getContext() );
+ $ul =
MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
+ $user->getUserPage(),
+ $user->getName()
+ );
+ $time = $this->getLanguage()->userTimeAndDate(
$row->video_timestamp, $this->getUser() );
+
+ $this->gallery->add(
+ $video,
+ "$ul<br />\n<i>"
+ . htmlspecialchars( $time )
+ . "</i><br />\n"
+ );
+ }
+
+ function getQueryInfo() {
+ $opts = $this->opts;
+ $conds = $jconds = [];
+ $tables = [ 'video' ];
+ $fields = [ 'video_name', 'video_url', 'video_user_name',
'video_user_id', 'video_timestamp' ];
+ $options = [];
+
+ $user = $opts->getValue( 'user' );
+ if ( $user !== '' ) {
+ $userId = User::idFromName( $user );
+ if ( $userId ) {
+ $conds['video_user_id'] = $userId;
+ } else {
+ $conds['video_user_name'] = $user;
+ }
+ }
+
+ if ( $opts->getValue( 'newbies' ) ) {
+ // newbie = most recent 1% of users
+ $dbr = wfGetDB( DB_REPLICA );
+ $max = $dbr->selectField( 'user', 'max(user_id)',
false, __METHOD__ );
+ $conds[] = 'video_user_id >' . (int)( $max - $max / 100
);
+
+ // there's no point in looking for new user activity in
a far past;
+ // beyond a certain point, we'd just end up scanning
the rest of the
+ // table even though the users we're looking for didn't
yet exist...
+ // see T140537, (for ContribsPages, but similar to this)
+ $conds[] = 'video_timestamp > ' .
+ $dbr->addQuotes( $dbr->timestamp(
wfTimestamp() - 30 * 24 * 60 * 60 ) );
+ }
+
+ if ( $opts->getValue( 'hidebots' ) ) {
+ $groupsWithBotPermission =
User::getGroupsWithPermission( 'bot' );
+
+ if ( count( $groupsWithBotPermission ) ) {
+ $dbr = wfGetDB( DB_REPLICA );
+ $tables[] = 'user_groups';
+ $conds[] = 'ug_group IS NULL';
+ $jconds['user_groups'] = [
+ 'LEFT JOIN',
+ [
+ 'ug_group' =>
$groupsWithBotPermission,
+ 'ug_user = video_user_id',
+ 'ug_expiry IS NULL OR ug_expiry
>= ' . $dbr->addQuotes( $dbr->timestamp() )
+ ]
+ ];
+ }
+ }
+
+ if ( $opts->getValue( 'hidepatrolled' ) ) {
+ $tables[] = 'recentchanges';
+ $conds['rc_type'] = RC_LOG;
+ $conds['rc_log_type'] = 'upload';
+ $conds['rc_patrolled'] = 0;
+ $conds['rc_namespace'] = NS_FILE;
+ $jconds['recentchanges'] = [
+ 'INNER JOIN',
+ [
+ 'rc_title = video_name',
+ 'rc_user = video_user_id',
+ 'rc_timestamp = video_timestamp'
+ ]
+ ];
+ // We're ordering by video_timestamp, so we have to
make sure MariaDB queries `video` first.
+ // It sometimes decides to query `recentchanges` first
and filesort the result set later
+ // to get the right ordering. T124205 /
https://mariadb.atlassian.net/browse/MDEV-8880
+ $options[] = 'STRAIGHT_JOIN';
+ }
+
+ $likeVal = $opts->getValue( 'wpIlMatch' );
+ if ( $likeVal !== '' && !$this->getConfig()->get( 'MiserMode' )
) {
+ $dbr = wfGetDB( DB_REPLICA );
+ $likeObj = Title::newFromText( $likeVal );
+ if ( $likeObj instanceof Title ) {
+ $like = $dbr->buildLike(
+ $dbr->anyString(),
+ strtolower( $likeObj->getDBkey() ),
+ $dbr->anyString()
+ );
+ // LOWER() & friends don't work as-is on
varbinary fields
+ // @see
https://phabricator.wikimedia.org/T157197
+ $conds[] = "LOWER(CONVERT(video_name USING
utf8)) $like";
+ }
+ }
+
+ $query = [
+ 'tables' => $tables,
+ 'fields' => $fields,
+ 'join_conds' => $jconds,
+ 'conds' => $conds,
+ 'options' => $options,
+ ];
+
+ return $query;
+ }
+
+ function getIndexField() {
+ return 'video_timestamp';
+ }
+
+ function getStartBody() {
+ if ( !$this->gallery ) {
+ $this->gallery = new VideoGallery();
+ }
+
+ return '';
+ }
+
+ function getEndBody() {
+ return $this->gallery->toHTML();
+ }
+
+ function getShownVideosCount() {
+ return $this->gallery->count();
+ }
+}
--
To view, visit https://gerrit.wikimedia.org/r/387110
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I693e0d0cb583aa95f3614497b34c9ad8e6eb5cc8
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Video
Gerrit-Branch: master
Gerrit-Owner: Mainframe98 <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits