Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forgiveness #79

Merged
merged 25 commits into from
Nov 16, 2018
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
35 changes: 27 additions & 8 deletions i18n/en.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
{
"@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-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-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 +128,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').change(
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");
}
);
});
} );
233 changes: 233 additions & 0 deletions specials/SpecialClearPendingReviews.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
<?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();

$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('YmdHis'),
],
'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();
}

$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;

if ( empty( $categoryField ) && empty ( $allData['page'] ) ) {
return wfMessage( 'clearpendingreviews-missing-date-category' )->inContentLanguage();
}
if ( empty ( $categoryField ) ) {
return true;
} else {
$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 );

$output->addHTML( wfMessage( 'clearpendingreviews-success' )->numParams( $pageCount )->plain() );

//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>";
$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>";
$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();
//Display preview of pages to be cleared
$form->setPostText( $table );

return false;
}
}
}