Skip to content

Commit

Permalink
Add Special:ClearPendingReviews (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
krisfield authored and jamesmontalvo3 committed Nov 16, 2018
1 parent 7abea2c commit df3874d
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 12 deletions.
1 change: 1 addition & 0 deletions WatchAnalytics.alias.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
$specialPageAliases['en'] = array(
'WatchAnalytics' => array( 'WatchAnalytics' ),
'PendingReviews' => array( 'PendingReviews' ),
'ClearPendingReviews' => array( 'ClearPendingReviews' ),
'PageStatistics' => array( 'PageStatistics' ),
);
29 changes: 25 additions & 4 deletions extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,23 @@
"type": "specialpage",
"GroupPermissions": {
"sysop": {
"viewpagescore": true
"viewpagescore": true,
"clearreviews": true
},
"user": {
"pendingreviewslink": true
}
},
"AvailableRights": [
"viewpagescore",
"pendingreviewslink"
"pendingreviewslink",
"clearreviews"
],
"SpecialPages": {
"WatchAnalytics": "SpecialWatchAnalytics",
"PendingReviews": "SpecialPendingReviews",
"PageStatistics": "SpecialPageStatistics"
"PageStatistics": "SpecialPageStatistics",
"ClearPendingReviews": "SpecialClearPendingReviews"
},
"MessagesDirs": {
"WatchAnalytics": [
Expand Down Expand Up @@ -54,7 +57,8 @@
"WatchStateRecorder": "includes/WatchStateRecorder.php",
"SpecialWatchAnalytics": "specials/SpecialWatchAnalytics.php",
"SpecialPendingReviews": "specials/SpecialPendingReviews.php",
"SpecialPageStatistics": "specials/SpecialPageStatistics.php"
"SpecialPageStatistics": "specials/SpecialPageStatistics.php",
"SpecialClearPendingReviews": "specials/SpecialClearPendingReviews.php"
},
"ResourceModules": {
"ext.watchanalytics.base": {
Expand Down Expand Up @@ -91,6 +95,13 @@
"mediawiki.Title"
]
},
"ext.watchanalytics.clearpendingreviews.scripts": {
"position": "bottom",
"scripts": "clearpendingreviews/ext.watchanalytics.clearpendingreviews.js",
"dependencies": [
"mediawiki.Title"
]
},
"ext.watchanalytics.pendingreviews.styles": {
"position": "bottom",
"styles": "pendingreviews/ext.watchanalytics.pendingreviews.css"
Expand Down Expand Up @@ -181,6 +192,16 @@
"WatchAnalyticsUpdaterHooks::onParserTestTables"
]
},
"LogTypes": [ "pendingreviews" ],
"LogNames": {
"pendingreviews": "pendingreviews"
},
"LogHeaders": {
"pendingreviews": "pendingreviews-header"
},
"LogActionsHandlers": {
"pendingreviews/*": "LogFormatter"
},
"config": {
"_prefix": "eg",
"WatchAnalyticsPageCounter": false,
Expand Down
37 changes: 29 additions & 8 deletions i18n/en.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
{
"@metadata": {
"authors": [
"James Montalvo"
]
},
"watchanalytics-desc": "Encouraging good distribution of watchers",
"@metadata": {
"authors": [
"James Montalvo"
]
},
"watchanalytics-desc": "Encouraging good distribution of watchers",
"watchanalytics-personal-url": "Pending reviews ($1)",
"watchanalytics-personal-url-old": "{{PLURAL:$1|1 review|$1 reviews}} (oldest: {{PLURAL:$2|1 day|$2 days}})",
"watchanalytics-view": "View:",
"pendingreviews": "Pending reviews",
"watchanalytics": "Watch analytics",
"pagestatistics": "Page statistics",
"clearpendingreviews": "Clear pending reviews",
"clearpendingreview-category": "Select page category:",
"clearpendingreview-start-time": "Start time:",
"clearpendingreview-end-time": "End time:",
"clearpendingreview-page-title": "Page title like:",
"clearpendingreview-preview": "Preview pages",
"clearpendingreview-clear": "Clear reviews",
"clearpendingreviews-legend": "Search criteria",
"clearpendingreviews-date-invalid": "Not a valid date",
"clearpendingreviews-date-order-invalid": "Start date must be before end date",
"clearpendingreviews-missing-date-category":"Must input either category or title.",
"clearpendingreviews-category-invalid": "Category does not exist.",
"clearpendingreviews-success": "Successfully cleared pending reviews from $1 pages.",
"clearpendingreviews-success-return": "Return to: ",
"clearpendingreviews-pages-cleared": "The following pages will be cleared:",
"clearpendingreviews-people-impacted": "The following people will be impacted:",
"action-clearreviews": "clear pending reviews",
"clearform-submit": "Clear Pages",
"clearform-section1": "Select time period:",
"clearform-section2": "Select category and/or title:",
"pendingreviews-user-page": "Pending reviews for User:$1",
"watchanalytics-pages-specialpage": "Page watch statistics",
"watchanalytics-users-specialpage": "User watch statistics",
Expand Down Expand Up @@ -110,10 +130,11 @@
"pendingreviews-log-upload-new": "[[$1]] uploaded the initial version of the file",
"pendingreviews-log-upload-overwrite": "[[$1]] uploaded a new version of the file",
"pendingreviews-log-unknown-change": "[[$1]] made an unknown change",

"log-name-pendingreviews": "Pending Reviews",
"log-description-pendingreviews": "Tracks actions taken using tools from the Watch Analytics extension related to Pending Reviews.",
"logentry-pendingreviews-clearreivews": "$1 {{GENDER:$2|cleared $4 pending reviews}} with criteria -category$5 -title like$6 using $3",
"pendingreviews-watch-suggestion-title": "This wiki needs your help watching pages",
"pendingreviews-watch-suggestion-description": "Few (if any) people are watching the pages below. They are related to other pages in your watchlist, and it'd be real swell if you could help by watching some of them.",
"pendingreviews-watch-suggestion-watchlink": "Watch"

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
$( function () {
$(document).ready( function () {
$('.oo-ui-inputWidget-input').keydown(
function() {
$(".oo-ui-buttonElement-button[name=clearpages]").css({ "value": "Preview", "name":"Preview", "background-color": "#36c", "border-color": "#36c" });
$(".oo-ui-buttonElement-button[name=clearpages]").attr( "name", "preview");
$(".oo-ui-buttonElement-button[name=preview]").html("Preview");
}
);
});
} );
252 changes: 252 additions & 0 deletions specials/SpecialClearPendingReviews.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
<?php
/**
* ClearPendingReviews SpecialPage
*
* @file
* @ingroup Extensions
*/

class SpecialClearPendingReviews extends SpecialPage {
public function __construct() {
parent::__construct( 'ClearPendingReviews', 'clearreviews' );
}

public function execute( $par ) {
global $wgOut;

if ( !$this->getUser()->isAllowed( 'clearreviews' ) ) {
throw new PermissionsError( 'clearreviews' );
}

$this->setHeaders();
$wgOut->addModules( 'ext.watchanalytics.clearpendingreviews.scripts' );
$output = $this->getOutput();

//Defines input form
$formDescriptor = [
'start' => [
'section' => 'section1',
'label-message' => 'clearpendingreview-start-time',
'type' => 'text',
'required' => 'true',
'validation-callback' => [ $this, 'validateTime' ],
],
'end' => [
'section' => 'section1',
'label-message' => 'clearpendingreview-end-time',
'type' => 'text',
'required' => 'true',
'validation-callback' => [ $this, 'validateTime' ],
'help' => '<b>Current time:</b> '.date('YmdHi').'00',
],
'category' => [
'section' => 'section2',
'label-message' => 'clearpendingreview-category',
'type' => 'text',
'validation-callback' => [ $this, 'validateCategory' ],
],
'page' => [
'section' => 'section2',
'label-message' => 'clearpendingreview-page-title',
'type' => 'text',
],
];

$form = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext(), 'clearform' );

$form->setSubmitText( 'Preview' );
$form->setSubmitName( 'preview' );
$form->setSubmitCallback( [ $this, 'trySubmit' ] );
$form->show();

}

public function validateTime( $dateField, $allData ) {
if ( !is_string( $dateField ) ) {
return wfMessage( 'clearpendingreviews-date-invalid' )->inContentLanguage();
}

//Validates start time is before end time
if ( $allData['start'] > $allData['end'] ) {
return wfMessage( 'clearpendingreviews-date-order-invalid' )->inContentLanguage();
}

//Verifys input format is ISO
$dateTime = DateTime::createFromFormat( 'YmdHis', $dateField );
if ( $dateTime ) {
return $dateTime->format( 'YmdHis' ) === $dateField;
}

return wfMessage( 'clearpendingreviews-date-invalid' )->inContentLanguage();
}

public function validateCategory( $categoryField, $allData ) {
$bad_cat_name = false;

//Validates either Category or Title field is used
if ( empty( $categoryField ) && empty ( $allData['page'] ) ) {
return wfMessage( 'clearpendingreviews-missing-date-category' )->inContentLanguage();
}
if ( empty ( $categoryField ) ) {
return true;
} else {
//Verifys category exists in wiki
$category_title = Title::makeTitleSafe( NS_CATEGORY, $categoryField );
if ( !$category_title->exists() ) {
return wfMessage( 'clearpendingreviews-category-invalid' )->inContentLanguage();
}
}
return true;
}

/**
* @param array $data
* @param bool $clearPages
* @return $results
*/

public static function doSearchQuery( $data, $clearPages ) {
$dbw = wfGetDB( DB_REPLICA );
$category = preg_replace('/\s+/', '_', $data['category']);
$page = preg_replace('/\s+/', '_', $data['page']);
$start = preg_replace('/\s+/', '', $data['start']);
$end = preg_replace('/\s+/', '', $data['end']);
$conditions = '';

if ($category) {
$conditions .= "c.cl_to='$category' AND ";
}
if ($page) {
$conditions .= "w.wl_title LIKE '$page%' AND ";
}

$tables = array( 'w' => 'watchlist', 'p' => 'page', 'c' => 'categorylinks' );
$vars = array( 'w.*' );
$conditions .= "w.wl_notificationtimestamp IS NOT NULL AND w.wl_notificationtimestamp < $end AND w.wl_notificationtimestamp > $start";
$join_conds = array(
'p' => array(
'LEFT JOIN', 'w.wl_title=p.page_title'
),
'c' => array(
'LEFT JOIN', 'c.cl_from=p.page_id'
)
);

$results = $dbw->select( $tables, $vars, $conditions, __METHOD__, 'DISTINCT', $join_conds );

if ( $clearPages == True ) {
$dbw = wfGetDB( DB_MASTER );

foreach ($results as $result ) {
$values = array('wl_notificationtimestamp' => null );
$conds = array( 'wl_id' => $result->wl_id );
$options = array();
$dbw->update( 'watchlist', $values, $conds, __METHOD__, $options );
}
}

return $results;
}

/**
* @param array $data
* @param object $form
* @return Status
*/

public function trySubmit( $data, $form ) {
$request = $this->getRequest();
$output = $this->getOutput();
$this->setHeaders();

if (isset($_POST['clearpages'])) {
//Clears pending reviews
$results = $this->doSearchQuery( $data, True );

//Count how many pages were cleared
$pageCount = 0;
foreach ( $results as $result ) {
$pageCount = $pageCount + 1;
}

//Log when pages are cleared in Special:Log
$logEntry = new ManualLogEntry( 'pendingreviews', 'clearreivews' );
$logEntry->setPerformer( $this->getUser() );
$logEntry->setTarget( $this->getPageTitle() );
$logEntry->setParameters( [
'4::paramname' => '('.$pageCount.')',
'5::paramname' => '('.$data['category'].')',
'6::paramname' => '('.$data['page'].')',
] );
$logid = $logEntry->insert();
$logEntry->publish( $logid );
Hooks::run( 'PendingReviewsCleared', [&$data, &$results, &$pageCount] );

//Create link back to Special:ClearPendingReviews
$pageLinkHtml = Linker::link( $this->getPageTitle() );
$output->addHTML( "<b>" );
$output->addHTML( wfMessage( 'clearpendingreviews-success' )->numParams( $pageCount )->plain() );
$output->addHTML( "</b>" );
$output->addHTML( "<br>" );
$output->addHTML( wfMessage( 'clearpendingreviews-success-return' ) );
$output->addHTML( $pageLinkHtml );

//Don't reload the form after clearing pages.
return true;

} else {
$results = $this->doSearchQuery( $data, False );
$table = '';
$table .= "<table class='wikitable' style='width:100%'>";
$table .= "<tr>";
$table .= "<td style='vertical-align:top;'>";
$table .= "<h3>".wfMessage( 'clearpendingreviews-pages-cleared' )."</h3>";
$table .= "<ul>";
$impactedPages = array();
foreach ($results as $result) {
$page = Title::makeTitle( $result->wl_namespace, $result->wl_title );
$pageLinkHtml = Linker::link( $page );
$impactedPages[] = $pageLinkHtml;
}

$impactedPages = array_unique( $impactedPages );
foreach ($impactedPages as $impactedPage ) {
$table .= "<li>".$impactedPage."</li>";
}

$table .= "</ul>";
$table .= "</td>";
$table .= "<td style='vertical-align:top;'>";
$table .= "<h3>".wfMessage( 'clearpendingreviews-people-impacted' )."</h3>";
$table .= "<ul>";
$impactedUsers = array();
foreach ($results as $result ) {
$user = User::newFromId( $result->wl_user );
$userTitleObj = $user->getUserPage();
$userLinkHtml = Linker::link( $userTitleObj );
$impactedUsers[] = $userLinkHtml;
}

$impactedUsers = array_unique( $impactedUsers );
foreach ($impactedUsers as $impactedUser ) {
$table .= "<li>".$impactedUser."</li>";
}

$table .= "</ul>";
$table .= "</td>";
$table .= "</tr>";
$table .= "</table>";

$form->setSubmitText( 'Clear pages' );
$form->setSubmitName( 'clearpages' );
$form->setSubmitDestructive();
$form->setCancelTarget( $this->getPageTitle());
$form->showCancel();
Hooks::run( 'PendingReviewsPreview', [&$data, &$results] );
//Display preview of pages to be cleared
$form->setPostText( $table );

return false;
}
}
}

0 comments on commit df3874d

Please sign in to comment.