Migrate protect log to new log system
authorumherirrender <umherirrender_de.wp@web.de>
Sat, 22 Aug 2015 18:29:00 +0000 (20:29 +0200)
committerSiebrand Mazeland <siebrand@kitano.nl>
Fri, 25 Sep 2015 15:07:50 +0000 (17:07 +0200)
This localize the protect type, level and expiry on Special:Log/protect.
To allow i18n there are some details stored in the log params of new log
items, these details also shown on API output.
The details cannot get from the old existing data, because there are
containing L10n strings therefore i18n works only for new items.
In the api and for IRC the old description text is still stored in the
log params for backward compatibility.

This allows use of gender on Special:Log. Old messages are kept for use
in IRC. Tests already exists to ensure an unchanged IRC message.

Bug: T47988
Change-Id: I3bb85c61b857972e66c99c499d7d785c88cafb25

includes/DefaultSettings.php
includes/logging/LogFormatter.php
includes/logging/LogPage.php
includes/logging/ProtectLogFormatter.php
includes/page/WikiPage.php
languages/i18n/en.json
languages/i18n/qqq.json
tests/phpunit/includes/logging/LogFormatterTest.php
tests/phpunit/includes/logging/LogFormatterTestCase.php
tests/phpunit/includes/logging/ProtectLogFormatterTest.php

index aa21c44..8b220f3 100644 (file)
@@ -6934,11 +6934,7 @@ $wgLogHeaders = array(
  *
  * Extensions with custom log types may add to this array.
  */
-$wgLogActions = array(
-       'protect/modify' => 'modifiedarticleprotection',
-       'protect/protect' => 'protectedarticle',
-       'protect/unprotect' => 'unprotectedarticle',
-);
+$wgLogActions = array();
 
 /**
  * The same as above, but here values are names of classes,
@@ -6965,7 +6961,10 @@ $wgLogActionsHandlers = array(
        'move/move' => 'MoveLogFormatter',
        'move/move_redir' => 'MoveLogFormatter',
        'patrol/patrol' => 'PatrolLogFormatter',
+       'protect/modify' => 'ProtectLogFormatter',
        'protect/move_prot' => 'ProtectLogFormatter',
+       'protect/protect' => 'ProtectLogFormatter',
+       'protect/unprotect' => 'ProtectLogFormatter',
        'rights/autopromote' => 'RightsLogFormatter',
        'rights/rights' => 'RightsLogFormatter',
        'suppress/block' => 'BlockLogFormatter',
index f31a42a..1d31088 100644 (file)
@@ -263,7 +263,7 @@ class LogFormatter {
                                switch ( $entry->getSubtype() ) {
                                        case 'protect':
                                                $text = wfMessage( 'protectedarticle' )
-                                                       ->rawParams( $target . ' ' . $parameters[0] )->inContentLanguage()->escaped();
+                                                       ->rawParams( $target . ' ' . $parameters['4::description'] )->inContentLanguage()->escaped();
                                                break;
                                        case 'unprotect':
                                                $text = wfMessage( 'unprotectedarticle' )
@@ -271,7 +271,7 @@ class LogFormatter {
                                                break;
                                        case 'modify':
                                                $text = wfMessage( 'modifiedarticleprotection' )
-                                                       ->rawParams( $target . ' ' . $parameters[0] )->inContentLanguage()->escaped();
+                                                       ->rawParams( $target . ' ' . $parameters['4::description'] )->inContentLanguage()->escaped();
                                                break;
                                        case 'move_prot':
                                                $text = wfMessage( 'movedarticleprotection' )
@@ -932,32 +932,6 @@ class LegacyLogFormatter extends LogFormatter {
                $type = $this->entry->getType();
                $subtype = $this->entry->getSubtype();
 
-               if ( $type == 'protect'
-                       && ( $subtype == 'protect' || $subtype == 'modify' || $subtype == 'unprotect' )
-               ) {
-                       $links = array(
-                               Linker::link( $title,
-                                       $this->msg( 'hist' )->escaped(),
-                                       array(),
-                                       array(
-                                               'action' => 'history',
-                                               'offset' => $this->entry->getTimestamp()
-                                       )
-                               )
-                       );
-                       if ( $this->context->getUser()->isAllowed( 'protect' ) ) {
-                               $links[] = Linker::linkKnown(
-                                       $title,
-                                       $this->msg( 'protect_change' )->escaped(),
-                                       array(),
-                                       array( 'action' => 'protect' )
-                               );
-                       }
-
-                       return $this->msg( 'parentheses' )->rawParams(
-                               $this->context->getLanguage()->pipeList( $links ) )->escaped();
-               }
-
                // Do nothing. The implementation is handled by the hook modifiying the
                // passed-by-ref parameters. This also changes the default value so that
                // getComment() and getActionLinks() do not call them indefinitely.
index 82e5808..2e28917 100644 (file)
@@ -285,30 +285,10 @@ class LogPage {
                                        $rv = wfMessage( $wgLogActions[$key] )->rawParams( $titleLink )
                                                ->inLanguage( $langObj )->escaped();
                                } else {
-                                       $details = '';
                                        array_unshift( $params, $titleLink );
 
-                                       // Page protections
-                                       if ( $type == 'protect' && count( $params ) == 3 ) {
-                                               // Restrictions and expiries
-                                               if ( $skin ) {
-                                                       $details .= $wgLang->getDirMark() . htmlspecialchars( " {$params[1]}" );
-                                               } else {
-                                                       $details .= " {$params[1]}";
-                                               }
-
-                                               // Cascading flag...
-                                               if ( $params[2] ) {
-                                                       $text = wfMessage( 'protect-summary-cascade' )
-                                                               ->inLanguage( $langObj )->text();
-                                                       $details .= ' ';
-                                                       $details .= wfMessage( 'brackets', $text )->inLanguage( $langObj )->text();
-
-                                               }
-                                       }
-
                                        $rv = wfMessage( $wgLogActions[$key] )->rawParams( $params )
-                                                       ->inLanguage( $langObj )->escaped() . $details;
+                                                       ->inLanguage( $langObj )->escaped();
                                }
                        }
                } else {
index 5327e07..bd04f15 100644 (file)
@@ -37,11 +37,38 @@ class ProtectLogFormatter extends LogFormatter {
                return array();
        }
 
+       protected function getMessageKey() {
+               $key = parent::getMessageKey();
+               $params = $this->extractParameters();
+               if ( isset( $params[4] ) && $params[4] ) {
+                       // Messages: logentry-protect-protect-cascade, logentry-protect-modify-cascade
+                       $key .= '-cascade';
+               }
+
+               return $key;
+       }
+
        protected function getMessageParameters() {
                $params = parent::getMessageParameters();
 
                $subtype = $this->entry->getSubtype();
-               if ( $subtype === 'move_prot' ) {
+               if ( $subtype === 'protect' || $subtype === 'modify' ) {
+                       $rawParams = $this->entry->getParameters();
+                       if ( isset( $rawParams['details'] ) ) {
+                               $params[3] = $this->createProtectDescription( $rawParams['details'] );
+                       } elseif ( isset( $params[3] ) ) {
+                               // Old way of Restrictions and expiries
+                               $params[3] = $this->context->getLanguage()->getDirMark() . $params[3];
+                       } else {
+                               // Very old way (nothing set)
+                               $params[3] = '';
+                       }
+                       // Cascading flag
+                       if ( isset( $params[4] ) ) {
+                               // handled in getMessageKey
+                               unset( $params[4] );
+                       }
+               } elseif ( $subtype === 'move_prot' ) {
                        $oldname = $this->makePageLink( Title::newFromText( $params[3] ), array( 'redirect' => 'no' ) );
                        $params[3] = Message::rawParam( $oldname );
                }
@@ -49,15 +76,59 @@ class ProtectLogFormatter extends LogFormatter {
                return $params;
        }
 
+       public function getActionLinks() {
+               $subtype = $this->entry->getSubtype();
+               if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) // Action is hidden
+                       || $subtype === 'move_prot' // the move log entry has the right action link
+               ) {
+                       return '';
+               }
+
+               // Show history link for all changes after the protection
+               $title = $this->entry->getTarget();
+               $links = array(
+                       Linker::link( $title,
+                               $this->msg( 'hist' )->escaped(),
+                               array(),
+                               array(
+                                       'action' => 'history',
+                                       'offset' => $this->entry->getTimestamp(),
+                               )
+                       )
+               );
+
+               // Show change protection link
+               if ( $this->context->getUser()->isAllowed( 'protect' ) ) {
+                       $links[] = Linker::linkKnown(
+                               $title,
+                               $this->msg( 'protect_change' )->escaped(),
+                               array(),
+                               array( 'action' => 'protect' )
+                       );
+               }
+
+               return $this->msg( 'parentheses' )->rawParams(
+                       $this->context->getLanguage()->pipeList( $links ) )->escaped();
+       }
+
        protected function getParametersForApi() {
                $entry = $this->entry;
+               $subtype = $this->entry->getSubtype();
                $params = $entry->getParameters();
 
-               static $map = array(
-                       // param keys for move_prot sub type
-                       '4:title:oldtitle',
-                       '4::oldtitle' => '4:title:oldtitle',
-               );
+               $map = array();
+               if ( $subtype === 'protect' || $subtype === 'modify' ) {
+                       $map = array(
+                               '4::description',
+                               '5:bool:cascade',
+                               'details' => ':array:details',
+                       );
+               } elseif ( $subtype === 'move_prot' ) {
+                       $map = array(
+                               '4:title:oldtitle',
+                               '4::oldtitle' => '4:title:oldtitle',
+                       );
+               }
                foreach ( $map as $index => $key ) {
                        if ( isset( $params[$index] ) ) {
                                $params[$key] = $params[$index];
@@ -65,6 +136,78 @@ class ProtectLogFormatter extends LogFormatter {
                        }
                }
 
+               // Change string to explicit boolean
+               if ( isset( $params['5:bool:cascade'] ) && is_string( $params['5:bool:cascade'] ) ) {
+                       $params['5:bool:cascade'] = $params['5:bool:cascade'] === 'cascade';
+               }
+
                return $params;
        }
+
+       public function formatParametersForApi() {
+               global $wgContLang;
+
+               $ret = parent::formatParametersForApi();
+               if ( isset( $ret['details'] ) && is_array( $ret['details'] ) ) {
+                       foreach ( $ret['details'] as &$detail ) {
+                               if ( isset( $detail['expiry'] ) ) {
+                                       $detail['expiry'] = $wgContLang->formatExpiry( $detail['expiry'], TS_ISO_8601, 'infinite' );
+                               }
+                       }
+               }
+
+               return $ret;
+       }
+
+       /**
+        * Create the protect description to show in the log formatter
+        *
+        * @param array $details
+        * @return string
+        */
+       public function createProtectDescription( array $details ) {
+               $protectDescription = '';
+
+               foreach ( $details as $param ) {
+                       $expiryText = $this->formatExpiry( $param['expiry'] );
+
+                       // Messages: restriction-edit, restriction-move, restriction-create,
+                       // restriction-upload
+                       $action = $this->context->msg( 'restriction-' . $param['type'] )->escaped();
+
+                       $protectionLevel = $param['level'];
+                       // Messages: protect-level-autoconfirmed, protect-level-sysop
+                       $message = $this->context->msg( 'protect-level-' . $protectionLevel );
+                       if ( $message->isDisabled() ) {
+                               // Require "$1" permission
+                               $restrictions = $this->context->msg( "protect-fallback", $protectionLevel )->parse();
+                       } else {
+                               $restrictions = $message->escaped();
+                       }
+
+                       if ( $protectDescription !== '' ) {
+                               $protectDescription .= $this->context->msg( 'word-separator' )->escaped();
+                       }
+
+                       $protectDescription .= $this->context->msg( 'protect-summary-desc' )
+                               ->params( $action, $restrictions, $expiryText )->escaped();
+               }
+
+               return $protectDescription;
+       }
+
+       private function formatExpiry( $expiry ) {
+               if ( wfIsInfinity( $expiry ) ) {
+                       return $this->context->msg( 'protect-expiry-indefinite' )->text();
+               }
+               $lang = $this->context->getLanguage();
+               $user = $this->context->getUser();
+               return $this->context->msg(
+                       'protect-expiring-local',
+                       $lang->userTimeAndDate( $expiry, $user ),
+                       $lang->userDate( $expiry, $user ),
+                       $lang->userTime( $expiry, $user )
+               )->text();
+       }
+
 }
index c99928a..8b88986 100644 (file)
@@ -2409,6 +2409,7 @@ class WikiPage implements Page, IDBAccessObject {
 
                $logRelationsValues = array();
                $logRelationsField = null;
+               $logParamsDetails = array();
 
                if ( $id ) { // Protection of existing page
                        if ( !Hooks::run( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
@@ -2467,6 +2468,7 @@ class WikiPage implements Page, IDBAccessObject {
                                        __METHOD__
                                );
                                if ( $restrictions != '' ) {
+                                       $cascadeValue = ( $cascade && $action == 'edit' ) ? 1 : 0;
                                        $dbw->insert(
                                                'page_restrictions',
                                                array(
@@ -2474,12 +2476,18 @@ class WikiPage implements Page, IDBAccessObject {
                                                        'pr_page' => $id,
                                                        'pr_type' => $action,
                                                        'pr_level' => $restrictions,
-                                                       'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
+                                                       'pr_cascade' => $cascadeValue,
                                                        'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
                                                ),
                                                __METHOD__
                                        );
                                        $logRelationsValues[] = $dbw->insertId();
+                                       $logParamsDetails[] = array(
+                                               'type' => $action,
+                                               'level' => $restrictions,
+                                               'expiry' => $expiry[$action],
+                                               'cascade' => (bool)$cascadeValue,
+                                       );
                                }
                        }
 
@@ -2510,6 +2518,11 @@ class WikiPage implements Page, IDBAccessObject {
                                                'pt_reason' => $reason,
                                        ), __METHOD__
                                );
+                               $logParamsDetails[] = array(
+                                       'type' => 'create',
+                                       'level' => $limit['create'],
+                                       'expiry' => $expiry['create'],
+                               );
                        } else {
                                $dbw->delete( 'protected_titles',
                                        array(
@@ -2527,15 +2540,24 @@ class WikiPage implements Page, IDBAccessObject {
                        $params = array();
                } else {
                        $protectDescriptionLog = $this->protectDescriptionLog( $limit, $expiry );
-                       $params = array( $protectDescriptionLog, $cascade ? 'cascade' : '' );
+                       $params = array(
+                               '4::description' => $protectDescriptionLog, // parameter for IRC
+                               '5:bool:cascade' => $cascade,
+                               'details' => $logParamsDetails, // parameter for localize and api
+                       );
                }
 
                // Update the protection log
-               $log = new LogPage( 'protect' );
-               $logId = $log->addEntry( $logAction, $this->mTitle, $reason, $params, $user );
+               $logEntry = new ManualLogEntry( 'protect', $logAction );
+               $logEntry->setTarget( $this->mTitle );
+               $logEntry->setComment( $reason );
+               $logEntry->setPerformer( $user );
+               $logEntry->setParameters( $params );
                if ( $logRelationsField !== null && count( $logRelationsValues ) ) {
-                       $log->addRelations( $logRelationsField, $logRelationsValues, $logId );
+                       $logEntry->setRelations( array( $logRelationsField => $logRelationsValues ) );
                }
+               $logId = $logEntry->insert();
+               $logEntry->publish( $logId );
 
                return Status::newGood();
        }
index 3fc297c..bbdd679 100644 (file)
        "logentry-newusers-byemail": "User account $3 was {{GENDER:$2|created}} by $1 and password was sent by email",
        "logentry-newusers-autocreate": "User account $1 was {{GENDER:$2|created}} automatically",
        "logentry-protect-move_prot": "$1 {{GENDER:$2|moved}} protection settings from $4 to $3",
+       "logentry-protect-unprotect": "$1 {{GENDER:$2|removed}} protection from $3",
+       "logentry-protect-protect": "$1 {{GENDER:$2|protected}} $3 $4",
+       "logentry-protect-protect-cascade": "$1 {{GENDER:$2|protected}} $3 $4 [cascading]",
+       "logentry-protect-modify": "$1 {{GENDER:$2|changed}} protection level for $3 $4",
+       "logentry-protect-modify-cascade": "$1 {{GENDER:$2|changed}} protection level for $3 $4 [cascading]",
+       "logentry-protect-unprotect": "$1 {{GENDER:$2|removed}} protection from $3",
        "logentry-rights-rights": "$1 {{GENDER:$2|changed}} group membership for $3 from $4 to $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|changed}} group membership for $3",
        "logentry-rights-autopromote": "$1 was automatically {{GENDER:$2|promoted}} from $4 to $5",
index 79d8a69..9bb321b 100644 (file)
        "logentry-contentmodel-change-revert": "Prefilled edit summary when reverting a content model change. {{identical|revertmove}}",
        "protectlogpage": "{{doc-logpage}}\n\nTitle of [[Special:Log/protect]].",
        "protectlogtext": "Text in [[Special:Log/protect]].",
-       "protectedarticle": "Text describing an action on [[Special:Log]]. $1 is a page title.",
-       "modifiedarticleprotection": "Text describing an action on [[Special:Log]]. $1 is a page title.",
-       "unprotectedarticle": "Used as action in the log. Parameters:\n* $1 - target page title",
+       "protectedarticle": "This is a ''logentry'' message only used on IRC.\nText describing an action. $1 is a page title.",
+       "modifiedarticleprotection": "This is a ''logentry'' message only used on IRC.\nText describing an action. $1 is a page title.",
+       "unprotectedarticle": "This is a ''logentry'' message only used on IRC.\nUsed as action. Parameters:\n* $1 - target page title",
        "movedarticleprotection": "This is a ''logentry'' message only used on IRC. It appears in the log if a protected page is renamed.\n\nExample:\n<code>00:51, 16 September 2010 Siebrand +(Talk â€¢ contribs â€¢ block) moved protection settings from \"User:Siebrand/prot-move\" to \"User:Siebrand/prot-moved\" â€Ž (User:Siebrand/prot-move moved to User:Siebrand/prot-moved: prot_move test.)</code>\n\nParameters:\n* $1 - target page title\n* $2 - source page title",
        "protect-title": "Title for the protection form. $1 is the title of the page to be (un)protected.",
        "protect-title-notallowed": "Same as {{msg-mw|Protect-title}}, but when the user does not have the right to change protection levels.\n\nParameters:\n* $1 - page title",
        "logentry-newusers-byemail": "{{Logentry|[[Special:Log/newusers]]}}\n\n$4 is the name of the user that was created.",
        "logentry-newusers-autocreate": "{{Logentry|[[Special:Log/newusers]]}}\n\n$4 is the gender of the target user.",
        "logentry-protect-move_prot": "{{Logentry|[[Special:Log/protect]]}}\n* $4 - the old title",
+       "logentry-protect-protect": "{{Logentry|[[Special:Log/protect]]}}\n\n* $4 - protect expiry (formatted with {{msg-mw|protect-summary-desc}}, multiple possible)",
+       "logentry-protect-protect-cascade": "{{Logentry|[[Special:Log/protect]]}}\n\n* $4 - protect expiry (formatted with {{msg-mw|protect-summary-desc}}, multiple possible)\nFor word \"cascading\" see {{msg-mw|protect-summary-cascade}}",
+       "logentry-protect-modify": "{{Logentry|[[Special:Log/protect]]}}\n\n* $4 - protect expiry (formatted with {{msg-mw|protect-summary-desc}}, multiple possible)",
+       "logentry-protect-modify-cascade": "{{Logentry|[[Special:Log/protect]]}}\n\n* $4 - protect expiry (formatted with {{msg-mw|protect-summary-desc}}, multiple possible)\nFor word \"cascading\" see {{msg-mw|protect-summary-cascade}}",
+       "logentry-protect-unprotect": "{{Logentry|[[Special:Log/protect]]}}",
        "logentry-rights-rights": "* $1 - username\n* $2 - (see below)\n* $3 - username\n* $4 - list of user groups or {{msg-mw|Rightsnone}}\n* $5 - list of user groups or {{msg-mw|Rightsnone}}\n----\n{{Logentry|[[Special:Log/rights]]}}",
        "logentry-rights-rights-legacy": "* $1 - username\n* $2 - (see below)\n* $3 - username\n----\n{{Logentry|[[Special:Log/rights]]}}",
        "logentry-rights-autopromote": "* $1 - username\n* $2 - (see below)\n* $3 - (see below)\n* $4 - comma separated list of old user groups or {{msg-mw|Rightsnone}}\n* $5 - comma separated list of new user groups\n----\n{{Logentry|[[Special:Log/rights]]}}",
index 844c9af..5d8a258 100644 (file)
@@ -462,13 +462,13 @@ class LogFormatterTest extends MediaWikiLangTestCase {
         */
        public function testIrcMsgForLogTypeProtect() {
                $protectParams = array(
-                       '[edit=sysop] (indefinite) â€Ž[move=sysop] (indefinite)'
+                       '4::description' => '[edit=sysop] (indefinite) â€Ž[move=sysop] (indefinite)'
                );
                $sep = $this->context->msg( 'colon-separator' )->text();
 
                # protect/protect
                $this->assertIRCComment(
-                       $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams[0] )
+                       $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams['4::description'] )
                                ->plain() . $sep . $this->user_comment,
                        'protect', 'protect',
                        $protectParams,
@@ -485,7 +485,7 @@ class LogFormatterTest extends MediaWikiLangTestCase {
 
                # protect/modify
                $this->assertIRCComment(
-                       $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams[0] )
+                       $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams['4::description'] )
                                ->plain() . $sep . $this->user_comment,
                        'protect', 'modify',
                        $protectParams,
index e88452b..f175482 100644 (file)
@@ -49,6 +49,7 @@ abstract class LogFormatterTestCase extends MediaWikiLangTestCase {
 
        private static function removeSomeHtml( $html ) {
                $html = str_replace( '&quot;', '"', $html );
+               $html = preg_replace( '/\xE2\x80[\x8E\x8F]/', '', $html ); // Strip lrm/rlm
                return trim( preg_replace( '/<(a|span)[^>]*>([^<]*)<\/\1>/', '$2', $html ) );
        }
 
index 611b2df..718c08c 100644 (file)
@@ -2,6 +2,365 @@
 
 class ProtectLogFormatterTest extends LogFormatterTestCase {
 
+       /**
+        * Provide different rows from the logging table to test
+        * for backward compatibility.
+        * Do not change the existing data, just add a new database row
+        */
+       public static function provideProtectLogDatabaseRows() {
+               return array(
+                       // Current format
+                       array(
+                               array(
+                                       'type' => 'protect',
+                                       'action' => 'protect',
+                                       'comment' => 'protect comment',
+                                       'namespace' => NS_MAIN,
+                                       'title' => 'ProtectPage',
+                                       'params' => array(
+                                               '4::description' => '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               '5:bool:cascade' => false,
+                                               'details' => array(
+                                                       array(
+                                                               'type' => 'edit',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinity',
+                                                               'cascade' => false,
+                                                       ),
+                                                       array(
+                                                               'type' => 'move',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinity',
+                                                               'cascade' => false,
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'text' => 'User protected ProtectPage [Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite)',
+                                       'api' => array(
+                                               'description' => '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               'cascade' => false,
+                                               'details' => array(
+                                                       array(
+                                                               'type' => 'edit',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinite',
+                                                               'cascade' => false,
+                                                       ),
+                                                       array(
+                                                               'type' => 'move',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinite',
+                                                               'cascade' => false,
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                       ),
+
+                       // Current format with cascade
+                       array(
+                               array(
+                                       'type' => 'protect',
+                                       'action' => 'protect',
+                                       'comment' => 'protect comment',
+                                       'namespace' => NS_MAIN,
+                                       'title' => 'ProtectPage',
+                                       'params' => array(
+                                               '4::description' => '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               '5:bool:cascade' => true,
+                                               'details' => array(
+                                                       array(
+                                                               'type' => 'edit',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinity',
+                                                               'cascade' => true,
+                                                       ),
+                                                       array(
+                                                               'type' => 'move',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinity',
+                                                               'cascade' => false,
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'text' => 'User protected ProtectPage [Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite) [cascading]',
+                                       'api' => array(
+                                               'description' => '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               'cascade' => true,
+                                               'details' => array(
+                                                       array(
+                                                               'type' => 'edit',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinite',
+                                                               'cascade' => true,
+                                                       ),
+                                                       array(
+                                                               'type' => 'move',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinite',
+                                                               'cascade' => false,
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                       ),
+
+                       // Legacy format
+                       array(
+                               array(
+                                       'type' => 'protect',
+                                       'action' => 'protect',
+                                       'comment' => 'protect comment',
+                                       'namespace' => NS_MAIN,
+                                       'title' => 'ProtectPage',
+                                       'params' => array(
+                                               '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               '',
+                                       ),
+                               ),
+                               array(
+                                       'legacy' => true,
+                                       'text' => 'User protected ProtectPage [edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                       'api' => array(
+                                               'description' => '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               'cascade' => false,
+                                       ),
+                               ),
+                       ),
+
+                       // Legacy format with cascade
+                       array(
+                               array(
+                                       'type' => 'protect',
+                                       'action' => 'protect',
+                                       'comment' => 'protect comment',
+                                       'namespace' => NS_MAIN,
+                                       'title' => 'ProtectPage',
+                                       'params' => array(
+                                               '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               'cascade',
+                                       ),
+                               ),
+                               array(
+                                       'legacy' => true,
+                                       'text' => 'User protected ProtectPage [edit=sysop] (indefinite)[move=sysop] (indefinite) [cascading]',
+                                       'api' => array(
+                                               'description' => '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               'cascade' => true,
+                                       ),
+                               ),
+                       ),
+               );
+       }
+
+
+       /**
+        * @dataProvider provideProtectLogDatabaseRows
+        */
+       public function testProtectLogDatabaseRows( $row, $extra ) {
+               $this->doTestLogFormatter( $row, $extra );
+       }
+
+       /**
+        * Provide different rows from the logging table to test
+        * for backward compatibility.
+        * Do not change the existing data, just add a new database row
+        */
+       public static function provideModifyLogDatabaseRows() {
+               return array(
+                       // Current format
+                       array(
+                               array(
+                                       'type' => 'protect',
+                                       'action' => 'modify',
+                                       'comment' => 'protect comment',
+                                       'namespace' => NS_MAIN,
+                                       'title' => 'ProtectPage',
+                                       'params' => array(
+                                               '4::description' => '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               '5:bool:cascade' => false,
+                                               'details' => array(
+                                                       array(
+                                                               'type' => 'edit',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinity',
+                                                               'cascade' => false,
+                                                       ),
+                                                       array(
+                                                               'type' => 'move',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinity',
+                                                               'cascade' => false,
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'text' => 'User changed protection level for ProtectPage [Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite)',
+                                       'api' => array(
+                                               'description' => '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               'cascade' => false,
+                                               'details' => array(
+                                                       array(
+                                                               'type' => 'edit',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinite',
+                                                               'cascade' => false,
+                                                       ),
+                                                       array(
+                                                               'type' => 'move',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinite',
+                                                               'cascade' => false,
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                       ),
+
+                       // Current format with cascade
+                       array(
+                               array(
+                                       'type' => 'protect',
+                                       'action' => 'modify',
+                                       'comment' => 'protect comment',
+                                       'namespace' => NS_MAIN,
+                                       'title' => 'ProtectPage',
+                                       'params' => array(
+                                               '4::description' => '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               '5:bool:cascade' => true,
+                                               'details' => array(
+                                                       array(
+                                                               'type' => 'edit',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinity',
+                                                               'cascade' => true,
+                                                       ),
+                                                       array(
+                                                               'type' => 'move',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinity',
+                                                               'cascade' => false,
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'text' => 'User changed protection level for ProtectPage [Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite) [cascading]',
+                                       'api' => array(
+                                               'description' => '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               'cascade' => true,
+                                               'details' => array(
+                                                       array(
+                                                               'type' => 'edit',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinite',
+                                                               'cascade' => true,
+                                                       ),
+                                                       array(
+                                                               'type' => 'move',
+                                                               'level' => 'sysop',
+                                                               'expiry' => 'infinite',
+                                                               'cascade' => false,
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                       ),
+
+                       // Legacy format
+                       array(
+                               array(
+                                       'type' => 'protect',
+                                       'action' => 'modify',
+                                       'comment' => 'protect comment',
+                                       'namespace' => NS_MAIN,
+                                       'title' => 'ProtectPage',
+                                       'params' => array(
+                                               '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               '',
+                                       ),
+                               ),
+                               array(
+                                       'legacy' => true,
+                                       'text' => 'User changed protection level for ProtectPage [edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                       'api' => array(
+                                               'description' => '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               'cascade' => false,
+                                       ),
+                               ),
+                       ),
+
+                       // Legacy format with cascade
+                       array(
+                               array(
+                                       'type' => 'protect',
+                                       'action' => 'modify',
+                                       'comment' => 'protect comment',
+                                       'namespace' => NS_MAIN,
+                                       'title' => 'ProtectPage',
+                                       'params' => array(
+                                               '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               'cascade',
+                                       ),
+                               ),
+                               array(
+                                       'legacy' => true,
+                                       'text' => 'User changed protection level for ProtectPage [edit=sysop] (indefinite)[move=sysop] (indefinite) [cascading]',
+                                       'api' => array(
+                                               'description' => '[edit=sysop] (indefinite)[move=sysop] (indefinite)',
+                                               'cascade' => true,
+                                       ),
+                               ),
+                       ),
+               );
+       }
+
+
+       /**
+        * @dataProvider provideModifyLogDatabaseRows
+        */
+       public function testModifyLogDatabaseRows( $row, $extra ) {
+               $this->doTestLogFormatter( $row, $extra );
+       }
+
+       /**
+        * Provide different rows from the logging table to test
+        * for backward compatibility.
+        * Do not change the existing data, just add a new database row
+        */
+       public static function provideUnprotectLogDatabaseRows() {
+               return array(
+                       // Current format
+                       array(
+                               array(
+                                       'type' => 'protect',
+                                       'action' => 'unprotect',
+                                       'comment' => 'unprotect comment',
+                                       'namespace' => NS_MAIN,
+                                       'title' => 'ProtectPage',
+                                       'params' => array(),
+                               ),
+                               array(
+                                       'text' => 'User removed protection from ProtectPage',
+                                       'api' => array(),
+                               ),
+                       ),
+               );
+       }
+
+
+       /**
+        * @dataProvider provideUnprotectLogDatabaseRows
+        */
+       public function testUnprotectLogDatabaseRows( $row, $extra ) {
+               $this->doTestLogFormatter( $row, $extra );
+       }
+
        /**
         * Provide different rows from the logging table to test
         * for backward compatibility.