API: Enforce section=new constraint when using 'redirect' mode
authorAdam Roses Wight <awight@wikimedia.org>
Sun, 18 May 2014 07:04:36 +0000 (00:04 -0700)
committerBrad Jorsch <bjorsch@wikimedia.org>
Thu, 22 May 2014 20:08:22 +0000 (16:08 -0400)
Finish implementation of the 'redirect' edit API parameter.  This flag
should only be used as a convenience when adding a new section, appending,
or prepending.  Any other usage must be done using the long-cut (fetch page,
determine if it is a redirect, follow redirects and edit the target page's
content).

This patch takes apart the "EditConflict_redirect" test, because it no
longer makes sense--unless you dear reader can figure out how to force an
edit conflict when only adding a section?

Bug: 24330
Change-Id: Ie3f7273c18e156da1e50e1a36aac2e5341710982

includes/api/ApiEditPage.php
tests/phpunit/includes/api/ApiEditPageTest.php

index 635f6f8..cb0f8c2 100644 (file)
@@ -48,6 +48,9 @@ class ApiEditPage extends ApiBase {
                $apiResult = $this->getResult();
 
                if ( $params['redirect'] ) {
+                       if ( $params['prependtext'] === null && $params['appendtext'] === null && $params['section'] !== 'new' ) {
+                               $this->dieUsage( 'You have attempted to edit using the "redirect"-following mode, which must be used in conjuction with section=new, prependtext, or appendtext.', 'redirect-appendonly' );
+                       }
                        if ( $titleObj->isRedirect() ) {
                                $oldTitle = $titleObj;
 
@@ -526,7 +529,7 @@ class ApiEditPage extends ApiBase {
                                array( 'editconflict' ),
                                array( 'emptynewsection' ),
                                array( 'unknownerror', 'retval' ),
-                               array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
+                               array( 'code' => 'nosuchsection', 'info' => 'There is no such section.' ),
                                array(
                                        'code' => 'invalidsection',
                                        'info' => 'The section parameter must be a valid section id or \'new\''
@@ -542,6 +545,10 @@ class ApiEditPage extends ApiBase {
                                array(
                                        'code' => 'appendnotsupported',
                                        'info' => 'This type of page can not be edited by appending or prepending text.' ),
+                               array(
+                                       'code' => 'redirect-appendonly',
+                                       'info' => 'You have attempted to edit using the "redirect"-following mode, which must be used in conjuction with section=new, prependtext, or appendtext.',
+                               ),
                                array(
                                        'code' => 'badformat',
                                        'info' => 'The requested serialization format can not be applied to the page\'s content model'
index 2e1c265..9f8c139 100644 (file)
@@ -274,6 +274,100 @@ class ApiEditPageTest extends ApiTestCase {
                $this->assertEquals( "== header ==\n\ntest\n\n== header ==\n\ntest", $text );
        }
 
+       /**
+        * Ensure we can edit through a redirect, if adding a section
+        */
+       public function testEdit_redirect() {
+               static $count = 0;
+               $count++;
+
+               // assume NS_HELP defaults to wikitext
+               $name = "Help:ApiEditPageTest_testEdit_redirect_$count";
+               $title = Title::newFromText( $name );
+               $page = WikiPage::factory( $title );
+
+               $rname = "Help:ApiEditPageTest_testEdit_redirect_r$count";
+               $rtitle = Title::newFromText( $rname );
+               $rpage = WikiPage::factory( $rtitle );
+
+               // base edit for content
+               $page->doEditContent( new WikitextContent( "Foo" ),
+                       "testing 1", EDIT_NEW, false, self::$users['sysop']->user );
+               $this->forceRevisionDate( $page, '20120101000000' );
+               $baseTime = $page->getRevision()->getTimestamp();
+
+               // base edit for redirect
+               $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ),
+                       "testing 1", EDIT_NEW, false, self::$users['sysop']->user );
+               $this->forceRevisionDate( $rpage, '20120101000000' );
+
+               // conflicting edit to redirect
+               $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ),
+                       "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user );
+               $this->forceRevisionDate( $rpage, '20120101020202' );
+
+               // try to save edit, following the redirect
+               list( $re, , ) = $this->doApiRequestWithToken( array(
+                       'action' => 'edit',
+                       'title' => $rname,
+                       'text' => 'nix bar!',
+                       'basetimestamp' => $baseTime,
+                       'section' => 'new',
+                       'redirect' => true,
+               ), null, self::$users['sysop']->user );
+
+               $this->assertEquals( 'Success', $re['edit']['result'],
+                       "no problems expected when following redirect" );
+       }
+
+       /**
+        * Ensure we cannot edit through a redirect, if attempting to overwrite content
+        */
+       public function testEdit_redirectText() {
+               static $count = 0;
+               $count++;
+
+               // assume NS_HELP defaults to wikitext
+               $name = "Help:ApiEditPageTest_testEdit_redirectText_$count";
+               $title = Title::newFromText( $name );
+               $page = WikiPage::factory( $title );
+
+               $rname = "Help:ApiEditPageTest_testEdit_redirectText_r$count";
+               $rtitle = Title::newFromText( $rname );
+               $rpage = WikiPage::factory( $rtitle );
+
+               // base edit for content
+               $page->doEditContent( new WikitextContent( "Foo" ),
+                       "testing 1", EDIT_NEW, false, self::$users['sysop']->user );
+               $this->forceRevisionDate( $page, '20120101000000' );
+               $baseTime = $page->getRevision()->getTimestamp();
+
+               // base edit for redirect
+               $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ),
+                       "testing 1", EDIT_NEW, false, self::$users['sysop']->user );
+               $this->forceRevisionDate( $rpage, '20120101000000' );
+
+               // conflicting edit to redirect
+               $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ),
+                       "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user );
+               $this->forceRevisionDate( $rpage, '20120101020202' );
+
+               // try to save edit, following the redirect but without creating a section
+               try {
+                       $this->doApiRequestWithToken( array(
+                               'action' => 'edit',
+                               'title' => $rname,
+                               'text' => 'nix bar!',
+                               'basetimestamp' => $baseTime,
+                               'redirect' => true,
+                       ), null, self::$users['sysop']->user );
+
+                       $this->fail( 'redirect-appendonly error expected' );
+               } catch ( UsageException $ex ) {
+                       $this->assertEquals( 'redirect-appendonly', $ex->getCodeString() );
+               }
+       }
+
        public function testEditConflict() {
                static $count = 0;
                $count++;
@@ -310,60 +404,41 @@ class ApiEditPageTest extends ApiTestCase {
                }
        }
 
-       public function testEditConflict_redirect() {
+       /**
+        * Ensure that editing using section=new will prevent simple conflicts
+        */
+       public function testEditConflict_newSection() {
                static $count = 0;
                $count++;
 
                // assume NS_HELP defaults to wikitext
-               $name = "Help:ApiEditPageTest_testEditConflict_redirect_$count";
+               $name = "Help:ApiEditPageTest_testEditConflict_newSection_$count";
                $title = Title::newFromText( $name );
-               $page = WikiPage::factory( $title );
 
-               $rname = "Help:ApiEditPageTest_testEditConflict_redirect_r$count";
-               $rtitle = Title::newFromText( $rname );
-               $rpage = WikiPage::factory( $rtitle );
+               $page = WikiPage::factory( $title );
 
-               // base edit for content
+               // base edit
                $page->doEditContent( new WikitextContent( "Foo" ),
                        "testing 1", EDIT_NEW, false, self::$users['sysop']->user );
                $this->forceRevisionDate( $page, '20120101000000' );
                $baseTime = $page->getRevision()->getTimestamp();
 
-               // base edit for redirect
-               $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ),
-                       "testing 1", EDIT_NEW, false, self::$users['sysop']->user );
-               $this->forceRevisionDate( $rpage, '20120101000000' );
-
-               // conflicting edit to redirect
-               $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ),
+               // conflicting edit
+               $page->doEditContent( new WikitextContent( "Foo bar" ),
                        "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user );
-               $this->forceRevisionDate( $rpage, '20120101020202' );
+               $this->forceRevisionDate( $page, '20120101020202' );
 
-               // try to save edit; should work, because we follow the redirect
+               // try to save edit, expect no conflict
                list( $re, , ) = $this->doApiRequestWithToken( array(
                        'action' => 'edit',
-                       'title' => $rname,
+                       'title' => $name,
                        'text' => 'nix bar!',
                        'basetimestamp' => $baseTime,
-                       'redirect' => true,
+                       'section' => 'new',
                ), null, self::$users['sysop']->user );
 
                $this->assertEquals( 'Success', $re['edit']['result'],
-                       "no edit conflict expected when following redirect" );
-
-               // try again, without following the redirect. Should fail.
-               try {
-                       $this->doApiRequestWithToken( array(
-                               'action' => 'edit',
-                               'title' => $rname,
-                               'text' => 'nix bar!',
-                               'basetimestamp' => $baseTime,
-                       ), null, self::$users['sysop']->user );
-
-                       $this->fail( 'edit conflict expected' );
-               } catch ( UsageException $ex ) {
-                       $this->assertEquals( 'editconflict', $ex->getCodeString() );
-               }
+                       "no edit conflict expected here" );
        }
 
        public function testEditConflict_bug41990() {
@@ -405,6 +480,7 @@ class ApiEditPageTest extends ApiTestCase {
                        'action' => 'edit',
                        'title' => $rname,
                        'text' => 'nix bar!',
+                       'section' => 'new',
                        'redirect' => true,
                ), null, self::$users['sysop']->user );