https://www.mediawiki.org/wiki/Special:Code/MediaWiki/114710

Revision: 114710
Author:   catrope
Date:     2012-04-04 18:09:18 +0000 (Wed, 04 Apr 2012)
Log Message:
-----------
Add forgotten file

Added Paths:
-----------
    
branches/wmf/1.19wmf1/extensions/ArticleFeedbackv5/ArticleFeedbackv5.flagging.php

Added: 
branches/wmf/1.19wmf1/extensions/ArticleFeedbackv5/ArticleFeedbackv5.flagging.php
===================================================================
--- 
branches/wmf/1.19wmf1/extensions/ArticleFeedbackv5/ArticleFeedbackv5.flagging.php
                           (rev 0)
+++ 
branches/wmf/1.19wmf1/extensions/ArticleFeedbackv5/ArticleFeedbackv5.flagging.php
   2012-04-04 18:09:18 UTC (rev 114710)
@@ -0,0 +1,627 @@
+<?php
+/**
+ * ArticleFeedbackv5Flagging class
+ *
+ * @package    ArticleFeedback
+ * @author     Elizabeth M Smith <[email protected]>
+ * @author     Reha Sterbin <[email protected]>
+ * @version    $Id$
+ */
+
+/**
+ * Handles flagging of feedback
+ *
+ * Known flags are: 'delete', 'hide', 'resetoversight', 'abuse', 'oversight',
+ * 'unhelpful', and 'helpful'
+ *
+ * @package    ArticleFeedback
+ */
+class ArticleFeedbackv5Flagging {
+
+       /**
+        * The user performing the action
+        *
+        * Either zero for a system call, or $wgUser for a user-directed one
+        *
+        * @var mixed
+        */
+       private $user;
+
+       /**
+        * The page ID
+        *
+        * @var int
+        */
+       private $pageId;
+
+       /**
+        * The feedback ID
+        *
+        * @var int
+        */
+       private $feedbackId;
+
+       /**
+        * Constructor
+        *
+        * @param mixed $user       the user performing the action ($wgUser), or
+        *                          zero if it's a system call
+        * @param int   $pageId     the page ID
+        * @param int   $feedbackId the feedback ID
+        */
+       public function __construct( $user, $pageId, $feedbackId ) {
+               $this->user       = $user;
+               $this->pageId     = $pageId;
+               $this->feedbackId = $feedbackId;
+       }
+
+       /**
+        * Run a flagging action
+        *
+        * @param  $flag      string the flag
+        * @param  $notes     string [optional] any notes to send to the 
activity log
+        * @param  $direction string [optional] the direction of the request 
('increase' / 'decrease')
+        * @param  $toggle    bool   [optional] whether to toggle the flag
+        * @return array      information about the run, containing at least the
+        *                    keys 'result' ('Error' / 'Success') and 'reason' 
(a
+        *                    message key)
+        */
+       public function run( $flag, $notes = '', $direction = 'increase', 
$toggle = false ) {
+               $flag       = $flag;
+               $notes      = $notes;
+               $direction  = $direction == 'increase' ? 'increase' : 
'decrease';
+               $toggle     = $toggle ? true : false;
+
+               // default values for information to be filled in
+               $filters   = array();
+               $update    = array();
+               $results   = array();
+
+               // start
+               $where = array( 'af_id' => $this->feedbackId );
+
+               // we may not actually use this, but don't want to repeat this 
a million times
+               $default_user = wfMessage( 'articlefeedbackv5-default-user' 
)->text();
+
+               // we use ONE db connection that talks to master
+               $dbw     = wfGetDB( DB_MASTER );
+               $dbw->begin();
+               $timestamp = $dbw->timestamp();
+
+               // load feedback record, bail if we don't have one
+               $record = $this->fetchRecord( $dbw, $this->feedbackId );
+
+               if ( $record === false || !$record->af_id ) {
+                       // no-op, because this is already broken
+                       $error = 'articlefeedbackv5-invalid-feedback-id';
+
+               } elseif ( 'delete' == $flag && $this->isAllowed( 
'aftv5-delete-feedback' ) ) {
+
+                       // deleting means to "mark as oversighted" and "delete" 
it
+                       // oversighting also auto-hides the item
+
+                       // increase means "oversight this"
+                       if ( $direction == 'increase' ) {
+                               $activity = 'oversight';
+
+                               // delete
+                               $update['af_is_deleted'] = true;
+                               $update['af_is_undeleted'] = false;
+                               // only store the oversighter on 
delete/oversight
+                               $update['af_oversight_user_id'] = 
$this->getUserId();
+                               $update['af_oversight_timestamp'] = $timestamp;
+                               // delete specific filters
+                               $filters['deleted'] = 1;
+                               $filters['notdeleted'] = -1;
+                               if ( true == $record->af_is_undeleted ) {
+                                       $filters['undeleted'] = -1;
+                               }
+
+                               // This is data for the "hidden by, oversighted 
by" red line
+                               $results['oversight-user'] = 
$this->getUserLink();
+                               $results['oversight-timestamp'] = wfTimestamp( 
TS_RFC2822, $timestamp );
+
+                               // autohide if not hidden
+                               if ( false == $record->af_is_hidden ) {
+                                       $update['af_is_hidden'] = true;
+                                       $update['af_is_unhidden'] = false;
+                                       $filters = $this->changeFilterCounts( 
$record, $filters, 'hide' );
+                                       // 0 is used for "autohidden" purposes, 
we'll explicitly set it to overwrite last hider
+                                       $update['af_hide_user_id'] = 0;
+                                       $update['af_hide_timestamp'] = 
$timestamp;
+                                       $implicit_hide = true; // for logging
+                                       // tell front-end autohiding was done
+                                       $results['autohidden'] = 1;
+                                       // This is data for the "hidden by, 
oversighted by" red line
+                                       $results['hide-user'] = 
ApiArticleFeedbackv5Utils::getUserLink( null, $default_user );
+                                       $results['hide-timestamp'] = 
wfTimestamp( TS_RFC2822, $timestamp );
+                               }
+
+                       } else {
+                       // decrease means "unoversight this" but does NOT 
auto-unhide
+                               $activity = 'unoversight';
+                               $update['af_is_deleted'] = false;
+                               $update['af_is_undeleted'] = true;
+                               // increment "undeleted", decrement "deleted"
+                               // NOTE: we do not touch visible, since hidden 
controls visiblity
+                               $filters['deleted'] = -1;
+                               $filters['undeleted'] = 1;
+                               // increment "notdeleted" for count of 
everything but oversighted
+                               $filters['notdeleted'] = 1;
+                       }
+
+               } elseif ( 'hide' == $flag && $this->isAllowed( 
'aftv5-hide-feedback' ) ) {
+
+                       // increase means "hide this"
+                       if ( $direction == 'increase' ) {
+                               $activity = 'hidden';
+
+                               // hide
+                               $update['af_is_hidden'] = true;
+                               $update['af_is_unhidden'] = false;
+                               // only store the hider on hide not show
+                               $update['af_hide_user_id'] = $this->getUserId();
+                               $update['af_hide_timestamp'] = $timestamp;
+                               $filters = $this->changeFilterCounts( $record, 
$filters, 'hide' );
+
+                               // This is data for the "hidden by, oversighted 
by" red line
+                               $results['hide-user'] = $this->getUserLink();
+                               $results['hide-timestamp'] = wfTimestamp( 
TS_RFC2822, $timestamp );
+
+                       } else {
+                       // decrease means "unhide this"
+                               $activity = 'unhidden';
+
+                               $update['af_is_hidden'] = false;
+                               $update['af_is_unhidden'] = true;
+
+                               $filters = $this->changeFilterCounts( $record, 
$filters, 'show' );
+                       }
+
+               } elseif ( 'resetoversight' === $flag && $this->isAllowed( 
'aftv5-delete-feedback' ) ) {
+
+                       $activity = 'decline';
+                       // oversight request count becomes 0
+                       $update['af_oversight_count'] = 0;
+                       // declined oversight is flagged
+                       $update['af_is_declined'] = true;
+                       $filters['declined'] = 1;
+                       // if the oversight count was greater then 1
+                       if ( 0 < $record->af_oversight_count ) {
+                               $filters['needsoversight'] = -1;
+                       }
+
+               } elseif ( 'abuse' === $flag ) {
+
+                       // Conditional formatting for abuse flag
+                       global $wgArticleFeedbackv5AbusiveThreshold,
+                               $wgArticleFeedbackv5HideAbuseThreshold;
+
+                       $results['abuse_count'] = $record->af_abuse_count;
+
+                       // Make the abuse count in the result reflect this vote.
+                       if ( $direction == 'increase' ) {
+                               $results['abuse_count']++;
+                       } else {
+                               $results['abuse_count']--;
+                       }
+                       // no negative numbers
+                       $results['abuse_count'] = max( 0, 
$results['abuse_count'] );
+
+                       // Return a flag in the JSON, that turns the link red.
+                       if ( $results['abuse_count'] >= 
$wgArticleFeedbackv5AbusiveThreshold ) {
+                               $results['abusive'] = 1;
+                       }
+
+                       // Adding a new abuse flag: abusive++
+                       if ( $direction == 'increase' ) {
+                               $activity = 'flag';
+                               $filters['abusive'] = 1;
+                               // NOTE: we are bypassing traditional sql 
escaping here
+                               $update[] = "af_abuse_count = af_abuse_count + 
1";
+
+                               // Auto-hide after threshold flags
+                               if ( $record->af_abuse_count > 
$wgArticleFeedbackv5HideAbuseThreshold
+                                  && false == $record->af_is_hidden ) {
+                                       // hide
+                                       $update['af_is_hidden'] = true;
+                                       $update['af_is_unhidden'] = false;
+                                       // 0 is used for "autohidden" purposes, 
we'll explicitly set it to overwrite last hider
+                                       $update['af_hide_user_id'] = 0;
+                                       $update['af_hide_timestamp'] = 
$timestamp;
+
+                                       $filters = $this->changeFilterCounts( 
$record, $filters, 'hide' );
+                                       $results['abuse-hidden'] = 1;
+                                       $implicit_hide = true;
+
+                                       // tell front-end autohiding was done
+                                       $results['autohidden'] = 1;
+                                       // This is data for the "hidden by, 
oversighted by" red line
+                                       $results['hide-user'] = 
ApiArticleFeedbackv5Utils::getUserLink( null, $default_user );
+                                       $results['hide-timestamp'] = 
wfTimestamp( TS_RFC2822, $timestamp );
+                               }
+                       }
+
+                       // Removing the last abuse flag: abusive--
+                       elseif ( $direction == 'decrease' ) {
+                               $activity = 'unflag';
+                               $filters['abusive'] = -1;
+                               // NOTE: we are bypassing traditional sql 
escaping here
+                               $update[] = "af_abuse_count = 
GREATEST(CONVERT(af_abuse_count, SIGNED) -1, 0)";
+
+                               // Un-hide if we don't have 5 flags anymore
+                               if ( $record->af_abuse_count == 5 && true == 
$record->af_is_hidden ) {
+                                       $update['af_is_hidden'] = false;
+                                       $update['af_is_unhidden'] = true;
+
+                                       $filters = $this->changeFilterCounts( 
$record, $filters, 'show' );
+
+                                       $implicit_unhide = true;
+                               }
+                       } else {
+                               // TODO: real error here?
+                               $error = 
'articlefeedbackv5-invalid-feedback-flag';
+                       }
+
+               // NOTE: this is actually request/unrequest oversight and works 
similar to abuse
+               } elseif ( 'oversight' === $flag && $this->isAllowed( 
'aftv5-hide-feedback' ) ) {
+
+                       if ( $direction == 'increase' ) {
+                               $activity = 'request';
+                               $filters['needsoversight'] = 1;
+                               // NOTE: we are bypassing traditional sql 
escaping here
+                               $update[] = "af_oversight_count = 
af_oversight_count + 1";
+
+                               // autohide if not hidden
+                               if ( false == $record->af_is_hidden ) {
+                                       $update['af_is_hidden'] = true;
+                                       $update['af_is_unhidden'] = false;
+                                       // 0 is used for "autohidden" purposes, 
we'll explicitly set it to overwrite last hider
+                                       $update['af_hide_user_id'] = 0;
+                                       $update['af_hide_timestamp'] = 
$timestamp;
+
+                                       $filters = $this->changeFilterCounts( 
$record, $filters, 'hide' );
+                                       $implicit_hide = true; // for logging
+                                       // tell front-end autohiding was done
+                                       $results['autohidden'] = 1;
+                                       // This is data for the "hidden by, 
oversighted by" red line
+                                       $results['hide-user'] = 
ApiArticleFeedbackv5Utils::getUserLink( null, $default_user );
+                                       $results['hide-timestamp'] = 
wfTimestamp( TS_RFC2822, $timestamp );
+                               }
+
+                               // IF the previous setting was 0, send an email
+                               if ( $record->af_oversight_count < 1 ) {
+                                        $this->sendOversightEmail();
+                               }
+
+                       } elseif ( $direction == 'decrease' ) {
+                               $activity = 'unrequest';
+                               $filters['needsoversight'] = -1;
+                               // NOTE: we are bypassing traditional sql 
escaping here
+                               $update[] = "af_oversight_count = 
GREATEST(CONVERT(af_oversight_count, SIGNED) - 1, 0)";
+                       } else {
+                               // TODO: real error here?
+                               $error = 
'articlefeedbackv5-invalid-feedback-flag';
+                       }
+
+               // helpful and unhelpful flagging
+               } elseif ( 'unhelpful' === $flag || 'helpful' === $flag ) {
+
+                       $results['toggle'] = $toggle;
+                       $helpful = $record->af_helpful_count;
+                       $unhelpful = $record->af_unhelpful_count;
+
+                       // if toggle is on, we are decreasing one and 
increasing the other atomically
+                       // means one less http request and the counts don't 
mess up
+                       if ( true == $toggle ) {
+
+                               if ( ( ( $flag == 'helpful' && $direction == 
'increase' )
+                                || ( $flag == 'unhelpful' && $direction == 
'decrease' ) )
+                               ) {
+                                       // NOTE: we are bypassing traditional 
sql escaping here
+                                       $update[] = "af_helpful_count = 
af_helpful_count + 1";
+                                       $update[] = "af_unhelpful_count = 
GREATEST(0, CONVERT(af_unhelpful_count, SIGNED) - 1)";
+                                       $helpful++;
+                                       $unhelpful--;
+
+                               } elseif ( ( ( $flag == 'unhelpful' && 
$direction == 'increase' )
+                                || ( $flag == 'helpful' && $direction == 
'decrease' ) )
+                               ) {
+                                       // NOTE: we are bypassing traditional 
sql escaping here
+                                       $update[] = "af_unhelpful_count = 
af_unhelpful_count + 1";
+                                       $update[] = "af_helpful_count = 
GREATEST(0, CONVERT(af_helpful_count, SIGNED) - 1)";
+                                       $helpful--;
+                                       $unhelpful++;
+                               }
+
+                       } else {
+
+                               if ( 'unhelpful' === $flag && $direction == 
'increase' ) {
+                                       // NOTE: we are bypassing traditional 
sql escaping here
+                                       $update[] = "af_unhelpful_count = 
af_unhelpful_count + 1";
+                                       $unhelpful++;
+                               } elseif ( 'unhelpful' === $flag && $direction 
== 'decrease' ) {
+                                       // NOTE: we are bypassing traditional 
sql escaping here
+                                       $update[] = "af_unhelpful_count = 
GREATEST(0, CONVERT(af_unhelpful_count, SIGNED) - 1)";
+                                       $unhelpful--;
+                               } elseif ( $flag == 'helpful' && $direction == 
'increase' ) {
+                                       // NOTE: we are bypassing traditional 
sql escaping here
+                                       $update[] = "af_helpful_count = 
af_helpful_count + 1";
+                                       $helpful++;
+                               } elseif ( $flag == 'helpful' && $direction == 
'decrease' ) {
+                                       // NOTE: we are bypassing traditional 
sql escaping here
+                                       $update[] = "af_helpful_count = 
GREATEST(0, CONVERT(af_helpful_count, SIGNED) - 1)";
+                                       $helpful--;
+                               }
+
+                       }
+
+                       $netHelpfulness = $helpful - $unhelpful;
+
+                       // increase helpful OR decrease unhelpful
+                       if ( ( ( $flag == 'helpful' && $direction == 'increase' 
)
+                        || ( $flag == 'unhelpful' && $direction == 'decrease' 
) )
+                       ) {
+                               // net was -1: no longer unhelpful
+                               if ( $netHelpfulness == -1 ) {
+                                       $filters['unhelpful'] = -1;
+                               }
+
+                               // net was 0: now helpful
+                               if ( $netHelpfulness == 0 ) {
+                                       $filters['helpful'] = 1;
+                               }
+                       }
+
+                       // increase unhelpful OR decrease unhelpful
+                       if ( ( ( $flag == 'unhelpful' && $direction == 
'increase' )
+                        || ( $flag == 'helpful' && $direction == 'decrease' ) )
+                       ) {
+                               // net was 1: no longer helpful
+                               if ( $netHelpfulness == 1 ) {
+                                       $filters['helpful'] = -1;
+                               }
+
+                               // net was 0: now unhelpful
+                               if ( $netHelpfulness == 0 ) {
+                                       $filters['unhelpful'] = 1;
+                               }
+                       }
+
+               } else {
+                       $error = 'articlefeedbackv5-invalid-feedback-flag';
+               }
+
+               // we were valid
+               if ( !isset( $error ) ) {
+
+                       $success = $dbw->update(
+                               'aft_article_feedback',
+                               $update,
+                               $where,
+                               __METHOD__
+                       );
+
+                       // Update the filter count rollups.
+                       ApiArticleFeedbackv5Utils::updateFilterCounts( $dbw, 
$this->pageId, $filters );
+
+                       $dbw->commit(); // everything went well, so we commit 
our db changes
+
+                       // helpfulness counts are NOT logged, no activity is set
+                       if ( isset( $activity ) ) {
+                               ApiArticleFeedbackv5Utils::logActivity( 
$activity, $this->pageId, $this->feedbackId, $notes, $this->isSystemCall() );
+                       }
+
+                       // handle implicit hide/show logging
+                       if ( isset( $implicit_hide ) && $implicit_hide ) {
+                               ApiArticleFeedbackv5Utils::logActivity( 
'hidden' , $this->pageId, $this->feedbackId, '', true );
+                       }
+
+                       // Update helpful/unhelpful display count after 
submission.
+                       if ( $flag == 'helpful' || $flag == 'unhelpful' ) {
+
+                               // no negative numbers please
+                               $helpful = max( 0, $helpful );
+                               $unhelpful = max( 0, $unhelpful );
+
+                               $results['helpful'] = wfMessage(
+                                       'articlefeedbackv5-form-helpful-votes',
+                                       $helpful, $unhelpful
+                               )->escaped();
+
+                               // Update net_helpfulness after flagging as 
helpful/unhelpful.
+                               $dbw->update(
+                                       'aft_article_feedback',
+                                       array( 'af_net_helpfulness = 
CONVERT(af_helpful_count, SIGNED) - CONVERT(af_unhelpful_count, SIGNED)' ),
+                                       array(
+                                               'af_id' => 
$params['feedbackid'],
+                                       ),
+                                       __METHOD__
+                               );
+                       }
+               }
+
+               if ( isset( $error ) ) {
+                       $results['result'] = 'Error';
+                       $results['reason'] = $error;
+               } else {
+                       $results['result'] = 'Success';
+                       $results['reason'] = null;
+               }
+
+               return $results;
+       }
+
+       /**
+        * Returns whether this is a system call rather than a user-directed one
+        *
+        * @return bool
+        */
+       public function isSystemCall() {
+               return $this->user === 0;
+       }
+
+       /**
+        * Returns whether an action is allowed
+        *
+        * @param  $action string the name of the action
+        * @return bool whether it's allowed
+        */
+       public function isAllowed( $action ) {
+               if ( $this->isSystemCall() ) {
+                       return true;
+               }
+               return $this->user->isAllowed( $action );
+       }
+
+       /**
+        * Gets the user id
+        *
+        * @return mixed the user's ID, or zero if it's a system call
+        */
+       public function getUserId() {
+               if ( $this->isSystemCall() ) {
+                       return 0;
+               }
+               return $this->user->getId();
+       }
+
+       /**
+        * Gets the user link, for use in displays
+        *
+        * @return string the link
+        */
+       public function getUserLink() {
+               if ( $this->isSystemCall() ) {
+                       return ApiArticleFeedbackv5Utils::getUserLink( null, 
$default_user );
+               }
+               return ApiArticleFeedbackv5Utils::getUserLink( $this->user );
+       }
+
+       /**
+        * Helper function to grab a record from the database with information
+        * about the current feedback row
+        *
+        * @param  object $dbw     connection to database
+        * @param  int    $id      id of the feedback to fetch
+        * @return object database record
+        */
+       private function fetchRecord( $dbw, $id ) {
+               $record = $dbw->selectRow(
+                       'aft_article_feedback',
+                       array(
+                               'af_id',
+                               'af_page_id',
+                               'af_abuse_count',
+                               'af_is_hidden',
+                               'af_helpful_count',
+                               'af_unhelpful_count',
+                               'af_is_deleted',
+                               'af_net_helpfulness',
+                               'af_is_unhidden',
+                               'af_is_undeleted',
+                               'af_is_declined',
+                               'af_has_comment',
+                               'af_oversight_count' ),
+                       array( 'af_id' => $id )
+               );
+               return $record;
+       }
+
+       /**
+        * Helper function to manipulate all flags when hiding/showing a piece 
of feedback
+        *
+        * @param  object $record  existing feedback database record
+        * @param  array  $filters existing filters
+        * @param  string $action  'hide' or 'show'
+        * @return array  the filter array with new filter choices added
+        */
+       protected function changeFilterCounts( $record, $filters, $action ) {
+               // only filters that hide shouldn't manipulate are
+               // all, deleted, undeleted, and notdeleted
+
+               // use -1 (decrement) for hide, 1 for increment (show) - 
default is hide
+               switch( $action ) {
+                       case 'show':
+                               $int = 1;
+                               // if we're showing, this will increment
+                               $filters['unhidden'] = 1;
+                               break;
+                       default:
+                               // if we're hiding, and was unhidden, decrement
+                               if ( true == $record->af_is_unhidden ) {
+                                       $filters['unhidden'] = -1;
+                               }
+                               $int = -1;
+                               break;
+               }
+
+               // visible, invisible, unhidden
+               $filters['visible'] = $int;
+               $filters['invisible'] = -$int; // opposite of int
+
+               // comment
+               if ( true == $record->af_has_comment ) {
+                       $filters['comment'] = $int;
+               }
+
+               // abusive
+               if ( $record->af_abuse_count > 1 ) {
+                       $filters['abusive'] = $int;
+               }
+               // helpful and unhelpful
+               if ( $record->af_net_helpfulness > 1 ) {
+                       $filters['helpful'] = $int;
+               } elseif ( $record->af_net_helpfulness < 1 ) {
+                       $filters['unhelpful'] = $int;
+               }
+
+               return $filters;
+       }
+
+       /**
+        * Helper function to dig out page url and title, feedback permalink, 
and
+        * requestor page url and name - if all this data can be retrieved 
properly
+        * it shoves an email job into the queue for sending to the 
oversighters'
+        * mailing list - only called for NEW oversight requests
+        */
+       protected function sendOversightEmail() {
+               global $wgUser;
+
+               // jobs need a title object
+               $title_object = Title::newFromID( $this->pageId );
+
+               if ( !$title_object ) {
+                       return; // no title object, no mail
+               }
+
+               // get the string name of the page
+               $page_name = $title_object->getDBKey();
+
+               // make a title out of our user (sigh)
+               $user_page = $wgUser->getPage();
+
+               if ( !$user_page ) {
+                       return; // no user title object, no mail
+               }
+
+               // to build our permalink, use the feedback entry key + the 
page name (isn't page name a title? but title is an object? confusing)
+               $permalink = SpecialPage::getTitleFor( 'ArticleFeedbackv5', 
"$page_name/" . $this->feedbackId );
+
+               if ( !$permalink ) {
+                       return; // no proper permalink? no mail
+               }
+
+               // build our params
+               $params = array( 'user_name' => $wgUser->getName(),
+                               'user_url' => $user_page->getCanonicalUrl(),
+                               'page_name' => $title_object->getPrefixedText(),
+                               'page_url' => $title_object->getCanonicalUrl(),
+                               'permalink' => $permalink->getCanonicalUrl() );
+
+               $job = new ArticleFeedbackv5MailerJob( $title_object, $params );
+               $job->insert();
+       }
+
+}
+


Property changes on: 
branches/wmf/1.19wmf1/extensions/ArticleFeedbackv5/ArticleFeedbackv5.flagging.php
___________________________________________________________________
Added: svn:eol-style
   + native


_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to