Project

General

Profile

Actions

Defect #43360

open

RestApi modify/add wiki page results in broken wiki state

Added by René van Dorst 3 days ago. Updated 3 days ago.

Status:
Confirmed
Priority:
Normal
Assignee:
-
Category:
Wiki
Target version:
-
Start date:
Due date:
% Done:

0%

Estimated time:
Resolution:
Affected version:

Description

I know that the rest api for wikis is in alpha but breaking redmine is not what I was expecting.

I need to modify a lot external hyperlinks on many wiki pages so I went to the Rest API to modify the page.

I am using the rest api:
  • project api to fetch all the projects which has wiki module enabled in json.
  • all the wiki page names of the project
  • each wiki-page content to see if I need to modify it.

But when I want to modify the page via XML but also tested JSON, it is returning status code 201 Created not 204 No Content.

Below a XML example used to PUT to https://redmine.internal.domain/projects/prj/wiki/Release.xml, version of the wikipage is 5.


<?xml version="1.0"?><wiki_page><text>Some test</text><version>5</version><comments>Update links to SVN see #11655</comments></wiki_page>    

In redmine the page in question is not modify.
If I click on the wiki -> index by date, the page returns 500 Internal error. See also log output below.
But index by name I can see the pagename when using a other Release-test.xml.

logs snippits

Redmine log when clicking of index by date

I, [2025-10-16T14:26:15.911151 #1]  INFO -- : [e4e0d7c8-1537-4d2d-99ff-904b2cf39f31] Started GET "/projects/prj/wiki/date_index" for <ipadr> at 2025-10-16 14:26:15 +0200
I, [2025-10-16T14:26:15.911810 #1]  INFO -- : [e4e0d7c8-1537-4d2d-99ff-904b2cf39f31] Processing by WikiController#date_index as HTML
I, [2025-10-16T14:26:15.911841 #1]  INFO -- : [e4e0d7c8-1537-4d2d-99ff-904b2cf39f31]   Parameters: {"project_id"=>"prj"}
I, [2025-10-16T14:26:15.917863 #1]  INFO -- : [e4e0d7c8-1537-4d2d-99ff-904b2cf39f31]   Current user: r.v.dorst (id=7)
I, [2025-10-16T14:26:15.923989 #1]  INFO -- : [e4e0d7c8-1537-4d2d-99ff-904b2cf39f31] Completed 500 Internal Server Error in 12ms (ActiveRecord: 4.9ms (13 queries, 1 cached) | GC: 0.0ms)
F, [2025-10-16T14:26:15.927093 #1] FATAL -- : [e4e0d7c8-1537-4d2d-99ff-904b2cf39f31]
[e4e0d7c8-1537-4d2d-99ff-904b2cf39f31] NoMethodError (undefined method `to_date' for nil):
[e4e0d7c8-1537-4d2d-99ff-904b2cf39f31]
[e4e0d7c8-1537-4d2d-99ff-904b2cf39f31] app/controllers/wiki_controller.rb:62:in `block in date_index'
[e4e0d7c8-1537-4d2d-99ff-904b2cf39f31] app/controllers/wiki_controller.rb:62:in `each'
[e4e0d7c8-1537-4d2d-99ff-904b2cf39f31] app/controllers/wiki_controller.rb:62:in `group_by'
[e4e0d7c8-1537-4d2d-99ff-904b2cf39f31] app/controllers/wiki_controller.rb:62:in `date_index'
[e4e0d7c8-1537-4d2d-99ff-904b2cf39f31] lib/redmine/sudo_mode.rb:78:in `sudo_mode'

Redmine Log for the XML PUT

I, [2025-10-16T14:25:33.264844 #1]  INFO -- : [9f9f6af1-64cf-4956-b3e7-1b4f73d5792d] Started PUT "/projects/prj/wiki/Release.xml?key=<snip>" for <ipadr> at 2025-10-16 14:25:33 +0200
I, [2025-10-16T14:25:33.265305 #1]  INFO -- : [9f9f6af1-64cf-4956-b3e7-1b4f73d5792d] Processing by WikiController#update as XML
I, [2025-10-16T14:25:33.265329 #1]  INFO -- : [9f9f6af1-64cf-4956-b3e7-1b4f73d5792d]   Parameters: {"key"=>"<snip>", "project_id"=>"prj", "id"=>"Release"}
I, [2025-10-16T14:25:33.267488 #1]  INFO -- : [9f9f6af1-64cf-4956-b3e7-1b4f73d5792d]   Current user: r.v.dorst (id=7)
I, [2025-10-16T14:25:33.277452 #1]  INFO -- : [9f9f6af1-64cf-4956-b3e7-1b4f73d5792d] Completed 201 Created in 12ms (Views: 0.9ms | ActiveRecord: 4.8ms (14 queries, 2 cached) | GC: 0.0ms)

database

After some investigation in the database.

I see:

MariaDB [redmine]> select * from wiki_pages order by id desc limit 10;
+------+---------+-----------------------------+---------------------+-----------+-----------+
| id   | wiki_id | title                       | created_on          | protected | parent_id |
+------+---------+-----------------------------+---------------------+-----------+-----------+
| 2697 |     160 | Index                       | 2025-10-16 14:25:33 |         0 |      NULL |
MariaDB [redmine]> select * from wiki_contents where page_id = 2697 ;
Empty set (0.001 sec)

rerun the script

When running the code again. Fetching the wikipages json goes wrong to.
See the last line, version=null and updated_on=null.

{"wiki_pages":[{"title":"Changelog","parent":{"title":"Wiki"},"version":3,"created_on":"2021-04-20T12:51:32Z","updated_on":"2022-04-04T10:25:09Z"},{"title":"Deleted","version":6,"created_on":"2016-10-05T09:21:01Z","updated_on":"2021-04-22T15:46:54Z"},
{"title":"Index","version":null,"created_on":"2025-10-16T12:12:32Z","updated_on":null},

fix the issue

To fix the index by date issue and JSON API, removing id=2697 from wiki_pages seems enough to fix it.

I hope this can be fix soon or have a workaround.

Actions #1

Updated by René van Dorst 3 days ago

Update, When I remove the version field from the request

<?xml version="1.0"?><wiki_page><text>Some test</text><comments>Update links to SVN see #11655</comments></wiki_page>

Redmine returns error
422 Unprocessable
<errors type="array"><error>Text field cannot be blank</error></errors>

for me text field seems filled so looks like the input parser is maybe faulty?

Actions #2

Updated by Holger Just 3 days ago

  • Category changed from REST API to Wiki
  • Status changed from New to Confirmed

When sending XML or JSON to the API, you have to make sure to set the Content-Type header of your request accordingly. The .xml or .json extension on the URL path only determines the format of the returned data, not the way the data from the request is [arsed. It is entirely possible to send JSON (or browser form data) and receive XML back. Specifically the XML data from your first example, can be successfully parses as form data (which is the default if no Content-Type is set in the request). However, as this does not extract the actual fields from the XML, it is basically an empty request. Once you set the appropriate Content-Type header in your request (application/xml in this case), your API calls should succeed.

Still, it seems you have create a wiki page without any content. It seems that Redmine currently does not fully handle this case everywhere (which has resulted in the exception you saw). The exception in showing the date index with content-less pages in Redmine could be solved with the following patch:

diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb
index bcb3b08911..967e1eaf0f 100644
--- a/app/controllers/wiki_controller.rb
+++ b/app/controllers/wiki_controller.rb
@@ -59,7 +59,7 @@ def index
   # List of page, by last update
   def date_index
     load_pages_for_index
-    @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
+    @pages_by_date = @pages.group_by {|p| p.updated_on&.to_date}
   end

   def new
diff --git a/app/views/wiki/date_index.html.erb b/app/views/wiki/date_index.html.erb
index c8acf933c9..24fec82a1a 100644
--- a/app/views/wiki/date_index.html.erb
+++ b/app/views/wiki/date_index.html.erb
@@ -14,7 +14,7 @@
 <p class="nodata"><%= l(:label_no_data) %></p>
 <% end %>

-<% @pages_by_date.keys.sort.reverse_each do |date| %>
+<% @pages_by_date.keys.sort_by { |date| date || Float::INFINITY }.reverse_each do |date| %>
 <h3><%= format_date(date) %></h3>
 <ul>
 <% @pages_by_date[date].each do |page| %>
diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb
index 04b687531c..4e5196dca7 100644
--- a/test/functional/wiki_controller_test.rb
+++ b/test/functional/wiki_controller_test.rb
@@ -340,6 +340,23 @@ def test_create_page
     assert_equal 'Created the page', page.content.comments
   end

+  def test_create_empty_page
+    @request.session[:user_id] = 2
+    assert_difference 'WikiPage.count' do
+      assert_no_difference 'WikiContent.count' do
+        put :update, :params => {
+          :project_id => 1,
+          :id => 'New page'
+        }
+      end
+    end
+    assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page'
+    page = Project.find(1).wiki.find_page('New page')
+    assert !page.new_record?
+    assert_nil page.content
+    assert_nil page.parent
+  end
+
   def test_create_page_with_attachments
     set_tmp_attachments_directory
     @request.session[:user_id] = 2
@@ -1129,6 +1146,15 @@ def test_date_index
     assert_select 'a[href=?]', '/projects/ecookbook/activity.atom?show_wiki_edits=1'
   end

+  def test_date_index_with_empty_page
+    # Create an empty page with no content
+    WikiPage.create!(:wiki_id => 1, :title => 'Foo')
+
+    get :date_index, :params => {:project_id => 'ecookbook'}
+    assert_response :success
+    assert_select 'a[href=?]', '/projects/ecookbook/wiki/Foo'
+  end
+
   def test_not_found
     get :show, :params => {:project_id => 999}
     assert_response :not_found

We may also want to adjust the validation logic so that it is not possible to create a new WikiPage without an associated WikiPageContent in the first place. Accordingly, we may then also want to destroy a wiki page entirely when its last WikiPageContent is destroyed.

Actions #3

Updated by René van Dorst 3 days ago

Holger, thanks for your quick feedback.

Your extra explanation is great feedback and useful to know.

I made an big error.
I did not send the body, XML part, in the request.
I also did not send the content-type.

Strange to see that I go different result when changing the "XML part".
Probably to do with using a existing wiki-page vs non-existing name while testing.

As you explained that it still possible to make a new wiki page without content, which I probably did while testing.

Sending a requenst with body and content-type, everything is working as expected.

I still going to test your patch, need some time to setup a dev environment.

Actions

Also available in: Atom PDF