Merge remote-tracking branch 'opf/feature/rails3' into feature/ie_10_svg_text_rendering

Conflicts:
	doc/CHANGELOG.md
pull/639/head
Martin Czuchra 11 years ago
commit 7443dc83ac
  1. 24
      .travis.yml
  2. 4
      Gemfile
  3. 5
      Gemfile.lock
  4. 18
      app/controllers/application_controller.rb
  5. 31
      app/controllers/wiki_controller.rb
  6. 6
      app/controllers/wiki_menu_items_controller.rb
  7. 22
      app/helpers/application_helper.rb
  8. 1
      app/models/journal_manager.rb
  9. 2
      app/models/wiki_content.rb
  10. 2
      app/models/wiki_page.rb
  11. 8
      app/views/wiki/annotate.html.erb
  12. 2
      app/views/wiki/destroy.html.erb
  13. 6
      app/views/wiki/diff.html.erb
  14. 4
      app/views/wiki/edit.html.erb
  15. 2
      app/views/wiki/edit_parent_page.html.erb
  16. 4
      app/views/wiki/export_multiple.html.erb
  17. 4
      app/views/wiki/history.html.erb
  18. 2
      app/views/wiki/rename.html.erb
  19. 38
      app/views/wiki/show.html.erb
  20. 4
      app/views/wiki_menu_items/edit.html.erb
  21. 4
      config/configuration.yml.example
  22. 6
      config/routes.rb
  23. 45
      db/migrate/20130612120042_migrate_serialized_yaml_from_syck_to_psych.rb
  24. 25
      db/migrate/20130917131710_planning_element_data_to_work_packages.rb
  25. 76
      db/migrate/20130920095747_legacy_planning_element_journal_data.rb
  26. 11
      db/migrate/20131101125921_migrate_default_values_in_work_package_journals.rb
  27. 32
      db/migrate/migration_utils/attachable_utils.rb
  28. 36
      db/migrate/migration_utils/customizable_utils.rb
  29. 4
      db/migrate/migration_utils/db_worker.rb
  30. 3
      db/migrate/migration_utils/legacy_journal_migrator.rb
  31. 8
      db/migrate/migration_utils/utils.rb
  32. 48
      db/migrate/migration_utils/yaml_migrator.rb
  33. 11
      doc/CHANGELOG.md
  34. 56
      features/wiki/wiki_create_child.feature
  35. 1
      lib/open_project/configuration.rb
  36. 2
      lib/open_project/version.rb
  37. 8
      lib/redmine/menu_manager/menu_helper.rb
  38. 24
      lib/tasks/fix_attachments_collation.rake
  39. 61
      lib/tasks/quote_strings_with_invalid_characters_in_legacy_journals.rake
  40. 3
      lib/tasks/remove_timelines_historical_comparison_from_options.rake
  41. 3
      spec/controllers/wiki_controller_spec.rb
  42. 18
      spec/models/work_package/work_package_acts_as_journalized_spec.rb
  43. 5
      spec/spec_helper.rb
  44. 24
      test/functional/wiki_controller_test.rb
  45. 6
      test/test_helper.rb
  46. 20
      test/unit/lib/redmine/wiki_formatting/macros_test.rb

@ -34,17 +34,17 @@ branches:
- feature/rails3
env:
# mysql2
- "TEST_SUITE=cucumber RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite:development"
- "TEST_SUITE=test:units RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite:development"
- "TEST_SUITE=test:functionals RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite:development"
- "TEST_SUITE=test:integration RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite:development"
- "TEST_SUITE=spec RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite:development"
- "TEST_SUITE=cucumber CI=true RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite:development"
- "TEST_SUITE=test:units CI=true RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite:development"
- "TEST_SUITE=test:functionals CI=true RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite:development"
- "TEST_SUITE=test:integration CI=true RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite:development"
- "TEST_SUITE=spec CI=true RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite:development"
# postgres
- "TEST_SUITE=cucumber RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite:development"
- "TEST_SUITE=test:units RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite:development"
- "TEST_SUITE=test:functionals RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite:development"
- "TEST_SUITE=test:integration RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite:development"
- "TEST_SUITE=spec RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite:development"
- "TEST_SUITE=cucumber CI=true RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite:development"
- "TEST_SUITE=test:units CI=true RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite:development"
- "TEST_SUITE=test:functionals CI=true RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite:development"
- "TEST_SUITE=test:integration CI=true RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite:development"
- "TEST_SUITE=spec CI=true RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite:development"
script: "bundle exec rake $TEST_SUITE"
before_install:
- "export DISPLAY=:99.0"
@ -53,3 +53,7 @@ before_script:
- "RAILS_ENV=production bundle exec rake ci:travis:prepare"
notifications:
email: false
addons:
code_climate:
repo_token:
secure: "BIooLUZt/ZPBh0KSOMrSdsm0uwyax9veNULE20hJLXxuj8dStxAHcrZW8gZvNaU0/vgz3RWKQeNJ6xV9BXYVjnAtYIkQmI+kiryRT9PE6OsLbqeU/OirahFi8Dwm4xfh/e9DVgTOyYHHhZ69aVFQxvKbXARGmfoHcy8gFduJ7ss="

@ -78,6 +78,8 @@ gem 'daemons'
gem 'rack-protection'
gem 'syck', :platforms => [:ruby_20, :mingw_20], :require => false
group :production do
# we use dalli as standard memcache client remove this if you don't
# requires memcached 1.4+
@ -129,8 +131,8 @@ group :test do
gem 'simplecov', ">= 0.8.pre"
gem "shoulda-matchers"
gem "json_spec"
gem "activerecord-tableless", "~> 1.0"
gem "codeclimate-test-reporter", :require => nil
end
group :ldap do

@ -69,6 +69,8 @@ GEM
timers (~> 1.1.0)
childprocess (0.3.9)
ffi (~> 1.0, >= 1.0.11)
codeclimate-test-reporter (0.1.1)
simplecov (>= 0.7.1, < 1.0.0)
coderay (1.0.9)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
@ -318,6 +320,7 @@ GEM
railties (~> 3.0)
structured_warnings (0.1.4)
svg-graph (1.0.5)
syck (1.0.1)
test-unit (2.5.5)
therubyracer (0.11.4)
libv8 (~> 3.11.8.12)
@ -355,6 +358,7 @@ DEPENDENCIES
awesome_nested_set
capybara
capybara-screenshot
codeclimate-test-reporter
coderay (~> 1.0.5)
coffee-rails (~> 3.2.1)
color-tools (~> 1.3.0)
@ -424,6 +428,7 @@ DEPENDENCIES
sqlite3
strong_parameters
svg-graph
syck
therubyracer
thin
timecop (~> 0.6.1)

@ -45,8 +45,6 @@ require_dependency 'principal'
class ApplicationController < ActionController::Base
# ensure the OpenProject models are required in the right order (as they have circular dependencies)
class_attribute :_model_object
class_attribute :_model_scope
class_attribute :accept_key_auth_actions
@ -87,7 +85,9 @@ class ApplicationController < ActionController::Base
:reset_i18n_fallbacks,
:set_localization,
:check_session_lifetime,
:stop_if_feeds_disabled
:stop_if_feeds_disabled,
:set_cache_buster
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
@ -104,6 +104,18 @@ class ApplicationController < ActionController::Base
{ :layout => params["layout"] }
end
# set http headers so that the browser does not store any
# data (caches) of this site
# see: https://websecuritytool.codeplex.com/wikipage?title=Checks#http-cache-control-header-no-store
# see: http://stackoverflow.com/questions/711418/how-to-prevent-browser-page-caching-in-rails
def set_cache_buster
if OpenProject::Configuration['disable_browser_cache']
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
end
# the current user is a per-session kind of thing and session stuff is controller responsibility.
# a globally accessible User.current is a big code smell. when used incorrectly it allows getting
# the current user outside of a session scope, i.e. in the model layer, from mailers or in the console

@ -57,6 +57,7 @@ class WikiController < ApplicationController
:add_attachment,
:list_attachments,
:destroy]
before_filter :build_wiki_page_and_content, only: [:new, :create]
verify :method => :post, :only => [:protect], :redirect_to => { :action => :show }
verify :method => :get, :only => [:new, :new_child], :render => {:nothing => true, :status => :method_not_allowed}
@ -94,11 +95,6 @@ class WikiController < ApplicationController
end
def new
@page = WikiPage.new(:wiki => @wiki)
@page.content = WikiContent.new(:page => @page)
@content = @page.content_for_version(nil)
@content.text = initial_page_content(@page)
end
def new_child
@ -107,15 +103,13 @@ class WikiController < ApplicationController
old_page = @page
new
build_wiki_page_and_content
@page.parent = old_page
render :action => 'new'
end
def create
new
@page.title = params[:page][:title]
@page.parent_id = params[:page][:parent_id]
@ -126,6 +120,7 @@ class WikiController < ApplicationController
attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page)
call_hook(:controller_wiki_edit_after_save, :params => params, :page => @page)
flash[:notice] = l(:notice_successful_create)
redirect_to_show
else
render :action => 'new'
@ -304,7 +299,11 @@ class WikiController < ApplicationController
end
@page.destroy
redirect_to @wiki.pages.any? ? {:action => 'index', :project_id => @project} : project_path(@project)
if page = @wiki.find_page(@wiki.start_page) || @wiki.pages.first
redirect_to :action => 'index', :project_id => @project, id: page
else
redirect_to project_path(@project)
end
end
# Export wiki to a single html file
@ -334,7 +333,7 @@ class WikiController < ApplicationController
return render_403 unless editable?
attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page)
redirect_to :action => 'show', :id => @page.title, :project_id => @project
redirect_to :action => 'show', :id => @page, :project_id => @project
end
def list_attachments
@ -352,7 +351,7 @@ class WikiController < ApplicationController
nil
end
private
private
def find_wiki
@project = Project.find(params[:project_id])
@ -368,6 +367,14 @@ private
render_404 if @page.nil?
end
def build_wiki_page_and_content
@page = WikiPage.new wiki: @wiki
@page.content = WikiContent.new page: @page
@content = @page.content_for_version nil
@content.text = initial_page_content @page
end
# Returns true if the current user is allowed to edit the page, otherwise false
def editable?(page = @page)
page.editable_by?(User.current)
@ -389,6 +396,6 @@ private
end
def redirect_to_show
redirect_to :action => 'show', :project_id => @project, :id => @page.title
redirect_to action: :show, project_id: @project, id: @page
end
end

@ -75,10 +75,10 @@ class WikiMenuItemsController < ApplicationController
if not @wiki_menu_item.errors.size >= 1 and (@wiki_menu_item.destroyed? or @wiki_menu_item.save)
flash[:notice] = l(:notice_successful_update)
redirect_back_or_default({ :action => 'edit', :id => @page_title })
redirect_back_or_default({ :action => 'edit', :id => @page })
else
respond_to do |format|
format.html { render :action => 'edit', :id => @page_title }
format.html { render :action => 'edit', :id => @page }
end
end
end
@ -96,7 +96,7 @@ class WikiMenuItemsController < ApplicationController
current_menu_item.destroy
end
redirect_to action: :edit, id: current_page.title
redirect_to action: :edit, id: current_page
end
private

@ -240,19 +240,17 @@ module ApplicationHelper
end
def render_page_hierarchy(pages, node=nil, options={})
content = ''
if pages[node]
content << "<ul class=\"pages-hierarchy\">\n"
pages[node].each do |page|
content << "<li>"
content << link_to(page.pretty_title, project_wiki_path(page.project, page),
:title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
content << "</li>\n"
end
content << "</ul>\n"
return '' unless pages[node]
content_tag :ul, class: 'pages-hierarchy' do
pages[node].collect do |page|
content_tag :li do
concat link_to(page.pretty_title, project_wiki_path(page.project, page),
title: (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
concat render_page_hierarchy(pages, page.id, options) if pages[page.id]
end
end.join.html_safe
end
content.html_safe
end
# Renders flash messages

@ -51,6 +51,7 @@ class JournalManager
predecessor = journable.journals.last.data.journaled_attributes
current = normalize_newlines(current)
predecessor = normalize_newlines(predecessor)
return predecessor.map{|k,v| current[k.to_s] != v}
.inject(false) { |r, c| r || c }

@ -43,7 +43,7 @@ class WikiContent < ActiveRecord::Base
acts_as_journalized :event_type => 'wiki-page',
:event_title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.journal.journable.page.title} (##{o.journal.journable.version})"},
:event_url => Proc.new {|o| {:controller => '/wiki', :action => 'show', :id => o.journal.journable.page.title, :project_id => o.journal.journable.page.wiki.project, :version => o.journal.journable.version}},
:event_url => Proc.new {|o| {:controller => '/wiki', :action => 'show', :id => o.journal.journable.page, :project_id => o.journal.journable.page.wiki.project, :version => o.journal.journable.version}},
:activity_type => 'wiki_edits',
:activity_permission => :view_wiki_edits,
:activity_find_options => { :include => { :page => { :wiki => :project } } }

@ -239,7 +239,7 @@ class WikiPage < ActiveRecord::Base
end
def to_param
title
CGI.escape title
end
def is_only_wiki_page?

@ -28,14 +28,14 @@ See doc/COPYRIGHT.rdoc for more details.
++#%>
<div class="contextual">
<%= link_to(l(:button_edit), {:action => 'edit', :id => @page.title}, :class => 'icon icon-edit') %>
<%= link_to(l(:label_history), {:action => 'history', :id => @page.title}, :class => 'icon icon-history') %>
<%= link_to(l(:button_edit), {:action => 'edit', :id => @page}, :class => 'icon icon-edit') %>
<%= link_to(l(:label_history), {:action => 'history', :id => @page}, :class => 'icon icon-history') %>
</div>
<h2><%= h(@page.pretty_title) %></h2>
<p>
<%= Version.model_name.human %> <%= link_to h(@annotate.content.version), :action => 'show', :id => @page.title, :version => @annotate.content.version %>
<%= Version.model_name.human %> <%= link_to h(@annotate.content.version), :action => 'show', :id => @page, :version => @annotate.content.version %>
<em>(<%= h(@annotate.content.journable.author ? @annotate.content.journable.author.name : l(:label_user_anonymous)) %>, <%= format_time(@annotate.content.journable.updated_on) %>)</em>
</p>
@ -47,7 +47,7 @@ See doc/COPYRIGHT.rdoc for more details.
<% @annotate.lines.each do |line| -%>
<tr class="bloc-<%= colors[line[0]] %>">
<th class="line-num"><%= line_num %></th>
<td class="revision"><%= link_to line[0], :controller => '/wiki', :action => 'show', :project_id => @project, :id => @page.title, :version => line[0] %></td>
<td class="revision"><%= link_to line[0], :controller => '/wiki', :action => 'show', :project_id => @project, :id => @page, :version => line[0] %></td>
<td class="author"><%= h(line[1]) %></td>
<td class="line-code"><pre><%=h line[2] %></pre></td>
</tr>

@ -45,5 +45,5 @@ See doc/COPYRIGHT.rdoc for more details.
</div>
<%= submit_tag l(:button_apply) %>
<%= link_to l(:button_cancel), :controller => '/wiki', :action => 'show', :project_id => @project, :id => @page.title %>
<%= link_to l(:button_cancel), controller: '/wiki', action: 'show', project_id: @project, id: @page %>
<% end %>

@ -28,16 +28,16 @@ See doc/COPYRIGHT.rdoc for more details.
++#%>
<div class="contextual">
<%= link_to(l(:label_history), {:action => 'history', :id => @page.title}, :class => 'icon icon-history') %>
<%= link_to(l(:label_history), {:action => 'history', :id => @page}, :class => 'icon icon-history') %>
</div>
<h2><%= h(@page.pretty_title) %></h2>
<p>
<%= Version.model_name.human %> <%= link_to @diff.content_from.version, :action => 'show', :id => @page.title, :project_id => @page.project, :version => @diff.content_from.version %>/<%= @page.content.version %>
<%= Version.model_name.human %> <%= link_to @diff.content_from.version, :action => 'show', :id => @page, :project_id => @page.project, :version => @diff.content_from.version %>/<%= @page.content.version %>
<em>(<%= @diff.content_from.user ? link_to_user(@diff.content_from.user) : l(:label_user_anonymous) %>, <%= format_time(@diff.content_from.created_at) %>)</em>
&#8594;
<%= Version.model_name.human %> <%= link_to @diff.content_to.version, :action => 'show', :id => @page.title, :project_id => @page.project, :version => @diff.content_to.version %>/<%= @page.content.version %>
<%= Version.model_name.human %> <%= link_to @diff.content_to.version, :action => 'show', :id => @page, :project_id => @page.project, :version => @diff.content_to.version %>/<%= @page.content.version %>
<em>(<%= @diff.content_to.user ? link_to_user(@diff.content_to.user) : l(:label_user_anonymous) %>, <%= format_time(@diff.content_to.created_at) %>)</em>
</p>

@ -29,7 +29,7 @@ See doc/COPYRIGHT.rdoc for more details.
<h2><%=h @page.pretty_title %></h2>
<%= form_for @content, :as => :content, :url => {:action => 'update', :id => @page.title}, :html => {:method => :put, :multipart => true, :id => 'wiki_form'} do |f| %>
<%= form_for @content, :as => :content, :url => {:action => 'update', :id => @page}, :html => {:method => :put, :multipart => true, :id => 'wiki_form'} do |f| %>
<%= f.hidden_field :lock_version %>
<%= error_messages_for 'content' %>
@ -40,7 +40,7 @@ See doc/COPYRIGHT.rdoc for more details.
<p><%= submit_tag l(:button_save) %>
<%= link_to_remote l(:label_preview),
{ :url => { :controller => '/wiki', :action => 'preview', :project_id => @project, :id => @page.title },
{ :url => { :controller => '/wiki', :action => 'preview', :project_id => @project, :id => @page },
:method => :post,
:update => 'preview',
:before => 'var form_data = Form.serialize("wiki_form", true); form_data._method="post";',

@ -31,7 +31,7 @@ See doc/COPYRIGHT.rdoc for more details.
<%= error_messages_for 'page' %>
<%= labelled_tabular_form_for @page, :url => { :action => 'update_parent_page' } do |f| %>
<%= labelled_tabular_form_for @page, :url => { id: @page, action: 'update_parent_page' } do |f| %>
<div class="box">
<p>
<%= f.select :parent_id,

@ -49,13 +49,13 @@ h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display
<strong><%= l(:label_index_by_title) %></strong>
<ul>
<% @pages.each do |page| %>
<li><a href="#<%= h(page.title) %>"><%= h(page.pretty_title) %></a></li>
<li><a href="#<%= h(page.to_param) %>"><%= h(page.pretty_title) %></a></li>
<% end %>
</ul>
<% @pages.each do |page| %>
<hr />
<a name="<%= h(page.title) %>" />
<a name="<%= h(page.to_param) %>" />
<%= textilizable page.content ,:text, :wiki_links => :anchor %>
<% end %>

@ -47,7 +47,7 @@ See doc/COPYRIGHT.rdoc for more details.
<% line_num = 1 %>
<% @versions.each do |ver| %>
<tr class="wiki-page-version <%= cycle("odd", "even") %>">
<td class="id"><%= link_to h(ver.version), :action => 'show', :id => @page.title, :project_id => @page.project, :version => ver.version %></td>
<td class="id"><%= link_to h(ver.version), :action => 'show', :id => @page, :project_id => @page.project, :version => ver.version %></td>
<td class="checkbox">
<% if show_diff && (line_num < @versions.size) %>
<label for="<%="cb-#{line_num}"%>" class="hidden-for-sighted"><%=l(:description_compare_from) + " " + h(ver.version.to_s) %></label>
@ -63,7 +63,7 @@ See doc/COPYRIGHT.rdoc for more details.
<td class="updated_on"><%= format_time(ver.created_at) %></td>
<td class="author"><%= link_to_user ver.user %></td>
<td class="comments"><%=h ver.notes %></td>
<td class="buttons"><%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %></td>
<td class="buttons"><%= link_to l(:button_annotate), :action => 'annotate', :id => @page, :version => ver.version %></td>
</tr>
<% line_num += 1 %>
<% end %>

@ -31,7 +31,7 @@ See doc/COPYRIGHT.rdoc for more details.
<%= error_messages_for 'page' %>
<%= labelled_tabular_form_for @page, :url => { :action => 'rename' }, :as => :wiki_page do |f| %>
<%= labelled_tabular_form_for @page, :url => { id: @page, action: 'rename' }, :as => :wiki_page do |f| %>
<div class="box">
<p><%= f.text_field :title, :required => true, :size => 100 %></p>
<p><%= f.check_box :redirect_existing_links %></p>

@ -30,44 +30,44 @@ See doc/COPYRIGHT.rdoc for more details.
<%= call_hook :wiki_navigation %>
<% content_for :action_menu_main do %>
<% if @editable %>
<%= li_unless_nil(link_to_if_authorized(l(:button_edit), {:action => 'edit', :id => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit))) if @content.version == @page.content.version %>
<%= li_unless_nil(link_to_if_authorized(l(:button_edit), {:action => 'edit', :id => @page}, :class => 'icon icon-edit', :accesskey => accesskey(:edit))) if @content.version == @page.content.version %>
<%= li_unless_nil(watcher_link(@page, User.current)) if Setting.notified_events.include?("wiki_content_added") or Setting.notified_events.include?("wiki_content_updated") %>
<% end %>
<% end %>
<% content_for :action_menu_more do %>
<% if @editable %>
<%= li_unless_nil(link_to_if_authorized(l(:button_lock), {:action => 'protect', :id => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock')) if !@page.protected? %>
<%= li_unless_nil(link_to_if_authorized(l(:button_unlock), {:action => 'protect', :id => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock')) if @page.protected? %>
<%= li_unless_nil(link_to_if_authorized(l(:button_lock), {:action => 'protect', :id => @page, :protected => 1}, :method => :post, :class => 'icon icon-lock')) if !@page.protected? %>
<%= li_unless_nil(link_to_if_authorized(l(:button_unlock), {:action => 'protect', :id => @page, :protected => 0}, :method => :post, :class => 'icon icon-unlock')) if @page.protected? %>
<% if User.current.allowed_to? :edit_wiki_pages, @project %>
<% if @page %>
<%= content_tag(:li, link_to(l(:create_child_page), wiki_new_child_path(:id => @page.title, :project_id => @project), :class => 'icon icon-duplicate')) %>
<%= content_tag(:li, link_to(l(:create_child_page), wiki_new_child_path(:id => @page, :project_id => @project), :class => 'icon icon-duplicate')) %>
<% end %>
<% end %>
<% if @content.version == @page.content.version %>
<%= li_unless_nil(link_to_if_authorized(t(:button_rename),
{:action => 'rename', :id => @page.title},
{:action => 'rename', :id => @page},
:class => 'icon icon-rename')) %>
<%= li_unless_nil(link_to_if_authorized(t(:button_change_parent_page),
{:action => 'edit_parent_page', :id => @page.title},
{:action => 'edit_parent_page', :id => @page},
:class => 'icon icon-parent')) %>
<% end %>
<%= li_unless_nil(link_to_if_authorized(l(:button_delete), {:action => 'destroy', :id => @page.title}, :method => :delete, :confirm => l(:text_are_you_sure), :class => 'icon icon-del')) %>
<%= li_unless_nil(link_to_if_authorized(l(:button_rollback), {:action => 'edit', :id => @page.title, :version => @content.version }, :class => 'icon icon-cancel')) if @content.version < @page.content.version %>
<%= li_unless_nil(link_to_if_authorized(l(:button_delete), {:action => 'destroy', :id => @page}, :method => :delete, :confirm => l(:text_are_you_sure), :class => 'icon icon-del')) %>
<%= li_unless_nil(link_to_if_authorized(l(:button_rollback), {:action => 'edit', :id => @page, :version => @content.version }, :class => 'icon icon-cancel')) if @content.version < @page.content.version %>
<% end %>
<%= li_unless_nil(link_to_if_authorized(l(:label_history), {:action => 'history', :id => @page.title}, :class => 'icon icon-history')) %>
<%= li_unless_nil(link_to_if_authorized(l(:button_wiki_menu_entry), {:controller => '/wiki_menu_items', :action => 'edit', :project_id => @project.identifier, :id => @page.title}, :class => 'icon icon-configure')) %>
<%= li_unless_nil(link_to_if_authorized(l(:label_history), {:action => 'history', :id => @page}, :class => 'icon icon-history')) %>
<%= li_unless_nil(link_to_if_authorized(l(:button_wiki_menu_entry), {:controller => '/wiki_menu_items', :action => 'edit', :project_id => @project.identifier, :id => @page}, :class => 'icon icon-configure')) %>
<% end %>
<% breadcrumb_paths(*(@page.ancestors.reverse.collect {|parent| link_to h(parent.breadcrumb_title), {:id => parent.title, :project_id => parent.project}} + [h(@page.breadcrumb_title)])) %>
<% breadcrumb_paths(*(@page.ancestors.reverse.collect {|parent| link_to h(parent.breadcrumb_title), {:id => parent, :project_id => parent.project}} + [h(@page.breadcrumb_title)])) %>
<% if @content.version != @page.content.version %>
<p>
<%= link_to(l(:label_previous), { :action => 'show', :id => @page.title, :project_id => @page.project, :version => (@content.version - 1) }, :class => 'navigate-left') + " - " if @content.version > 1 %>
<%= link_to(l(:label_previous), { :action => 'show', :id => @page, :project_id => @page.project, :version => (@content.version - 1) }, :class => 'navigate-left') + " - " if @content.version > 1 %>
<%= "#{Version.model_name.human} #{@content.version}/#{@page.content.version}" %>
<%= '(' + link_to(l(:label_diff), :controller => '/wiki', :action => 'diff', :id => @page.title, :project_id => @page.project, :version => @content.version) + ')' if @content.version > 1 %> -
<%= link_to(l(:label_next), :action => 'show', :id => @page.title, :project_id => @page.project, :version => (@content.version + 1), :class => 'navigate-right') + " - " if @content.version < @page.content.version %>
<%= link_to(l(:label_current_version), :action => 'show', :id => @page.title, :project_id => @page.project) %>
<%= '(' + link_to(l(:label_diff), :controller => '/wiki', :action => 'diff', :id => @page, :project_id => @page.project, :version => @content.version) + ')' if @content.version > 1 %> -
<%= link_to(l(:label_next), :action => 'show', :id => @page, :project_id => @page.project, :version => (@content.version + 1), :class => 'navigate-right') + " - " if @content.version < @page.content.version %>
<%= link_to(l(:label_current_version), :action => 'show', :id => @page, :project_id => @page.project) %>
<br />
<em><%= @content.author ? link_to_user(@content.author) : l(:label_user_anonymous) %>, <%= format_time(@content.updated_on) %> </em><br />
<%=h @content.comments %>
@ -83,7 +83,7 @@ See doc/COPYRIGHT.rdoc for more details.
<div id="wiki_add_attachment">
<p><%= link_to l(:label_attachment_new), {}, :onclick => "Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;",
:id => 'attach_files_link' %></p>
<%= form_tag({ :controller => '/wiki', :action => 'add_attachment', :project_id => @project, :id => @page.title }, :multipart => true, :id => "add_attachment_form", :style => "display:none;") do %>
<%= form_tag({ :controller => '/wiki', :action => 'add_attachment', :project_id => @project, :id => @page }, :multipart => true, :id => "add_attachment_form", :style => "display:none;") do %>
<div class="box">
<p><%= render :partial => 'attachments/form' %></p>
</div>
@ -98,9 +98,9 @@ See doc/COPYRIGHT.rdoc for more details.
:action => 'index',
:show_wiki_edits => 1,
:key => User.current.rss_key } %>
<%= f.link_to 'HTML', :url => { :version => @content.version } %>
<%= f.link_to 'TXT', :url => { :version => @content.version } %>
<%= call_hook(:view_wiki_show_other_formats, {:link_builder => f, :url_params => {:id => @page.title, :version => @content.version}}) %>
<%= f.link_to 'HTML', :url => { version: @content.version, id: @page } %>
<%= f.link_to 'TXT', :url => { version: @content.version, id: @page } %>
<%= call_hook(:view_wiki_show_other_formats, {:link_builder => f, :url_params => {:id => @page, :version => @content.version}}) %>
<% end if User.current.allowed_to?(:export_wiki_pages, @project) %>
<% content_for :header_tags do %>

@ -29,10 +29,10 @@ See doc/COPYRIGHT.rdoc for more details.
<%= error_messages_for "wiki_menu_item", :object => @wiki_menu_item %>
<% breadcrumb_paths(*@page.ancestors.reverse.collect {|parent| link_to h(parent.pretty_title), {:id => parent.title, :project_id => parent.project}}.push(@page.title)) %>
<% breadcrumb_paths(*@page.ancestors.reverse.collect {|parent| link_to h(parent.pretty_title), {:id => parent.title, :project_id => parent.project}}.push(@page)) %>
<h2><%= l(:wiki_menu_item_for, :title => @page_title) %></h2>
<%= form_for @wiki_menu_item, :html => {:id => 'wiki_menu_item_form', :class => 'wiki_menu_item_form', :method => :put}, :url => wiki_menu_item_path(@project, @page_title) do |form| %>
<%= form_for @wiki_menu_item, :html => {:id => 'wiki_menu_item_form', :class => 'wiki_menu_item_form', :method => :put}, :url => wiki_menu_item_path(@project, @page) do |form| %>
<p class="name_of_item">
<%= form.label :name, l(:label_menu_item_name), {:id => 'name_of_item'} %>
<% if @wiki_menu_item.name.nil? %>

@ -127,6 +127,10 @@ default:
# autologin_cookie_path:
# autologin_cookie_secure:
# disable browser cache for security reasons
# see: https://websecuritytool.codeplex.com/wikipage?title=Checks#http-cache-control-header-no-store
# disable_browser_cache: true
# Configuration of SCM executable command.
# Absolute path (e.g. /usr/local/bin/hg) or command name (e.g. hg.exe, bzr.exe)
# On Windows, *.cmd, *.bat (e.g. hg.cmd, bzr.bat) does not work.

@ -30,6 +30,12 @@
OpenProject::Application.routes.draw do
root :to => 'welcome#index', :as => 'home'
# Redirect deprecated issue links to new work packages uris
match '/issues(/)' => redirect('/work_packages/')
# The URI.escape doesn't escape / unless you ask it to.
# see https://github.com/rails/rails/issues/5688
match '/issues/*rest' => redirect { |params, req| "/work_packages/#{URI.escape(params[:rest])}" }
scope :controller => 'account' do
get '/account/force_password_change', :action => 'force_password_change'
post '/account/change_password', :action => 'change_password'

@ -0,0 +1,45 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2011-2013 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.md for more details.
#++
require_relative 'migration_utils/yaml_migrator'
class MigrateSerializedYamlFromSyckToPsych < ActiveRecord::Migration
include Migration::YamlMigrator
def up
migrate_yaml_columns('syck', 'psych')
end
def down
migrate_yaml_columns('psych', 'syck')
end
def migrate_yaml_columns(source_yamler, target_yamler)
['filters', 'column_names', 'sort_criteria'].each do |column|
migrate_yaml('queries', column, source_yamler, target_yamler)
end
migrate_yaml('custom_field_translations', 'possible_values', source_yamler, target_yamler)
migrate_yaml('roles', 'permissions', source_yamler, target_yamler)
migrate_yaml('settings', 'value', source_yamler, target_yamler)
migrate_yaml('timelines', 'options', source_yamler, target_yamler)
migrate_yaml('user_preferences', 'others', source_yamler, target_yamler)
migrate_yaml('wiki_menu_items', 'options', source_yamler, target_yamler)
end
end

@ -9,7 +9,12 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
#
require_relative 'migration_utils/utils'
class PlanningElementDataToWorkPackages < ActiveRecord::Migration
include Migration::Utils
def up
add_new_id_column
@ -251,13 +256,31 @@ class PlanningElementDataToWorkPackages < ActiveRecord::Migration
num_updated = 1
while num_updated != 0
num_updated = update <<-SQL
num_updated = update set_root_id_for_children_db_statement
end
end
def set_root_id_for_children_db_statement
if mysql?
<<-SQL
UPDATE #{db_work_packages_table} AS child
JOIN #{db_work_packages_table} AS parent
ON (child.#{db_column('parent_id')} = parent.#{db_column('id')})
SET child.#{db_column('root_id')} = parent.#{db_column('id')}
WHERE child.#{db_column('root_id')} IS NULL
SQL
else
<<-SQL
UPDATE #{db_work_packages_table}
SET #{db_column('root_id')} = (SELECT parent.#{db_column('root_id')}
FROM #{db_work_packages_table} AS parent
WHERE parent.#{db_column('id')} = #{db_work_packages_table}.#{db_column('parent_id')})
WHERE #{db_work_packages_table}.#{db_column('root_id')} IS NULL
SQL
end
end

@ -44,6 +44,37 @@ class LegacyPlanningElementJournalData < ActiveRecord::Migration
migrator.run
reset_public_key_sequence_in_postgres 'journals'
unless migrator.missing_type_ids.empty?
puts "Cannot resolve new type ids for all journals!"\
"\n\n"\
"The following list contains all legacy planning element "\
"type ids for which no new type id exists. Furthermore, "\
"the list contains all journal ids for which no new type "\
"id exists."\
"\n\n"\
"#{migrator.missing_type_ids}"\
"\n\n"\
"The type id is set to '0' for all journals containing a "\
"planning element type id for which no type id exists."\
"\n\n\n"
end
unless migrator.missing_journaled_ids.empty?
puts "Cannot resolve work package ids for all journals!"\
"\n\n"\
"The following list contains all legacy planning element "\
"ids for which no new work package id exists. Furthermore,"\
" the list contains all journal ids for which no new"\
"work package id exists."\
"\n\n"\
"#{migrator.missing_journaled_ids}"\
"\n\n"\
"The work package id is set to '0' for all journals "\
"containing a planning element type id for which no type "\
"id exists."
"\n\n\n"
end
end
def down
@ -66,7 +97,7 @@ class LegacyPlanningElementJournalData < ActiveRecord::Migration
def migrate_key_value_pairs!(to_insert, legacy_journal, journal_id)
update_type_id(to_insert)
update_type_id(to_insert, journal_id)
set_empty_description(to_insert)
@ -77,30 +108,31 @@ class LegacyPlanningElementJournalData < ActiveRecord::Migration
end
def update_journaled_id(legacy_journal)
new_journaled_id = new_journaled_id_for_old(legacy_journal["journaled_id"])
legecy_journal_id = legacy_journal["id"]
old_journaled_id = legacy_journal["journaled_id"]
new_journaled_id = new_journaled_id_for_old(old_journaled_id)
if new_journaled_id.nil?
raise UnknownJournaledError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n"
No new journaled_id could be found to replace the journaled_id value of
#{legacy_journal["journaled_id"]} for the legacy journal with the id
#{legacy_journal["id"]}
MESSAGE
add_missing_journaled_id_for_legacy_journal_id(old_journaled_id, legecy_journal_id)
new_journaled_id = 0
end
legacy_journal["journaled_id"] = new_journaled_id
end
def update_type_id(to_insert)
def update_type_id(to_insert, journal_id)
return if to_insert["planning_element_type_id"].nil? ||
to_insert["planning_element_type_id"].last.nil?
new_type_id = new_type_id_for_old(to_insert["planning_element_type_id"].last)
old_type_id = to_insert["planning_element_type_id"].last
new_type_id = new_type_id_for_old(old_type_id)
if new_type_id.nil?
raise UnknownTypeError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n"
No new type_id could be found to replace the type_id value of
#{to_insert["planning_element_type_id"].last}
MESSAGE
add_missing_type_id_for_journal_id(old_type_id, journal_id)
new_type_id = 0
end
to_insert["type_id"] = [nil, new_type_id]
@ -146,6 +178,24 @@ class LegacyPlanningElementJournalData < ActiveRecord::Migration
def set_empty_description(to_insert)
to_insert['description'] = [nil, ''] unless to_insert.has_key?('description')
end
def missing_journaled_ids
@missing_journaled_ids ||= {}
end
def add_missing_journaled_id_for_legacy_journal_id(old_journaled_id, legacy_journal_id)
missing_journaled_ids[old_journaled_id] = [] unless missing_journaled_ids.has_key? old_journaled_id
missing_journaled_ids[old_journaled_id] << legacy_journal_id
end
def missing_type_ids
@missing_type_ids ||= {}
end
def add_missing_type_id_for_journal_id(old_type_id, journal_id)
missing_type_ids[old_type_id] = [] unless missing_type_ids.has_key? old_type_id
missing_type_ids[old_type_id] << journal_id
end
end
end
end

@ -27,7 +27,10 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require_relative 'migration_utils/utils'
class MigrateDefaultValuesInWorkPackageJournals < ActiveRecord::Migration
include Migration::Utils
def up
@ -47,14 +50,6 @@ class MigrateDefaultValuesInWorkPackageJournals < ActiveRecord::Migration
%w(author_id status_id priority_id)
end
def postgres?
ActiveRecord::Base.connection.instance_values["config"][:adapter] == "postgresql"
end
def mysql?
ActiveRecord::Base.connection.instance_values["config"][:adapter] == "mysql2"
end
def migrate_field(field)
if postgres?
execute <<-SQL

@ -67,16 +67,28 @@ module Migration::Utils
private
def missing_attachments
result = select_all <<-SQL
SELECT * FROM (
SELECT a.container_id AS journaled_id, a.container_type AS journaled_type, a.id AS attachment_id, a.filename, MAX(aj.id) AS aj_id, MAX(j.version) AS last_version
FROM attachments AS a JOIN journals AS j
ON (a.container_id = j.journable_id AND a.container_type = j.journable_type) LEFT JOIN attachable_journals AS aj
ON (a.id = aj.attachment_id)
GROUP BY a.container_id, a.container_type, a.id, a.filename
) AS tmp
WHERE aj_id IS NULL
SQL
begin
result = select_all <<-SQL
SELECT * FROM (
SELECT a.container_id AS journaled_id, a.container_type AS journaled_type, a.id AS attachment_id, a.filename, MAX(aj.id) AS aj_id, MAX(j.version) AS last_version
FROM attachments AS a JOIN journals AS j
ON (a.container_id = j.journable_id AND a.container_type = j.journable_type) LEFT JOIN attachable_journals AS aj
ON (a.id = aj.attachment_id)
GROUP BY a.container_id, a.container_type, a.id, a.filename
) AS tmp
WHERE aj_id IS NULL
SQL
rescue ActiveRecord::StatementInvalid => ex
raise ex unless mysql?
raise "An MySQL error occured (see details below)!"\
"\n\n"\
"If you're facing an 'Illegal mix of collations error, consider "\
"running rake task "\
"'migrations:journals:fix_attachments_collation'."\
"\n\n"\
"#{ex.message}"
end
result.collect { |row| MissingAttachment.new(row['journaled_id'],
row['journaled_type'],

@ -19,6 +19,8 @@ module Migration::Utils
:last_version)
def add_missing_customizable_journals
delete_invalid_work_package_custom_values
result = missing_custom_values
repair_journals(result)
@ -66,6 +68,40 @@ module Migration::Utils
private
# Removes all work package custom values that are not referenced by the
# work package's project AND type.
def delete_invalid_work_package_custom_values
if mysql?
delete <<-SQL
DELETE cv.* FROM custom_values AS cv
JOIN work_packages AS w ON (w.id = cv.customized_id AND cv.customized_type = 'WorkPackage')
JOIN custom_fields AS cf ON (cv.custom_field_id = cf.id)
JOIN projects AS p ON (w.project_id = p.id)
LEFT JOIN custom_fields_projects AS cfp ON (cv.custom_field_id = cfp.custom_field_id AND w.project_id = cfp.project_id)
LEFT JOIN custom_fields_types AS cft ON (cv.custom_field_id = cft.custom_field_id AND w.type_id = cft.type_id)
WHERE cfp.project_id IS NULL
OR cft.type_id IS NULL
SQL
else
delete <<-SQL
DELETE FROM custom_values AS cvd
WHERE EXISTS
(
SELECT w.id, cf.id, cfp.project_id, p.name, cft.type_id
FROM work_packages AS w
JOIN custom_values AS cv ON (w.id = cv.customized_id AND cv.customized_type = 'WorkPackage')
JOIN custom_fields AS cf ON (cv.custom_field_id = cf.id)
JOIN projects AS p ON (w.project_id = p.id)
LEFT JOIN custom_fields_projects AS cfp ON (cv.custom_field_id = cfp.custom_field_id AND w.project_id = cfp.project_id)
LEFT JOIN custom_fields_types AS cft ON (cv.custom_field_id = cft.custom_field_id AND w.type_id = cft.type_id)
WHERE (cfp.project_id IS NULL
OR cft.type_id IS NULL)
AND cv.id = cvd.id
);
SQL
end
end
def missing_custom_values
result = select_all <<-SQL
SELECT tmp.customized_id,

@ -41,6 +41,10 @@ module Migration
ActiveRecord::Base.connection.table_exists? name
end
def db_column(name)
ActiveRecord::Base.connection.quote_column_name(name)
end
def db_columns(table_name)
ActiveRecord::Base.connection.columns table_name
end

@ -29,6 +29,7 @@
require_relative 'db_worker'
require_relative 'legacy_table_checker'
require 'syck'
module Migration
class IncompleteJournalsError < ::StandardError
@ -279,7 +280,7 @@ module Migration
changed_data = journal["changed_data"]
return Hash.new if changed_data.nil?
current_yamler = YAML::ENGINE.yamler
current_yamler = YAML::ENGINE.yamler || 'psych'
begin
# The change to 'syck' ensures that legacy data is correctly read from
# the 'legacy_journals' table. Otherwise, we would end up with false

@ -56,6 +56,14 @@ module Migration
ActiveRecord::Base.connection.reset_pk_sequence!(table)
end
def postgres?
ActiveRecord::Base.connection.instance_values["config"][:adapter] == "postgresql"
end
def mysql?
ActiveRecord::Base.connection.instance_values["config"][:adapter] == "mysql2"
end
private
def select_rows_from_database(table, column_list, conditions)

@ -0,0 +1,48 @@
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require_relative 'db_worker'
require 'syck'
module Migration
module YamlMigrator
include DbWorker
def migrate_yaml(table, column, source_yamler, target_yamler)
current_yamler = YAML::ENGINE.yamler
fetch_data(table,column).each do | data |
db_execute <<-SQL
UPDATE #{quoted_table_name(table)}
SET #{db_column(column)} = #{quote_value(yaml_to_yaml(data[column],source_yamler, target_yamler))}
WHERE id = #{data['id']};
SQL
end
ensure
# psych is the default starting at ruby 1.9.3, so we explicitely set it here
# in case no yamler was set to return to a sensible default
YAML::ENGINE.yamler = current_yamler.present? ? current_yamler : 'psych'
end
def fetch_data(table, column)
ActiveRecord::Base.connection.select_all <<-SQL
SELECT #{db_column('id')}, #{db_column(column)}
FROM #{quoted_table_name(table)}
WHERE #{db_column(column)} LIKE #{quote_value('---%')}
SQL
end
def yaml_to_yaml(data, source_yamler, target_yamler)
YAML::ENGINE.yamler = source_yamler
original = YAML.load(data)
YAML::ENGINE.yamler = target_yamler
YAML.dump original
end
end
end

@ -31,12 +31,21 @@ See doc/COPYRIGHT.rdoc for more details.
* `#2653` Remove relative vertical offset corrections and custom border fixes for IE8.
* `#2654` Remove custom font rendering/kerning as well as VML from timelines.
* `#2699` [Wiki] 400 error when entering special character in wiki title
* Fix mysql data migrations
## 3.0.0pre30
* Redirect old issue links to new work package URIs
* `#2721` Fix: Fix: Fix: Fix: Missing journal entries for customizable_journals
* `#2731` Migrated serialized yaml from syck to psych
## 3.0.0pre29
* `#2473` [Timelines] Tooltip in timeline report shows star * instead of hash # in front of ID
* `#2473` [Timelines] Tooltip in timeline report shows star * instead of hash # in front of ID
* `#2721` Fix: Fix: Fix: Missing journal entries for customizable_journals
* `#2718` Newlines in workpackage descriptions aren't normalized for change tracking
* `#1748` Add option to diable browser cache
## 3.0.0pre28

@ -0,0 +1,56 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2013 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
Feature: Creating a wiki child page
Background:
Given there are no wiki menu items
And there is 1 user with the following:
| login | bob |
And there is a role "member"
And the role "member" may have the following rights:
| view_wiki_pages |
| edit_wiki_pages |
And there is 1 project with the following:
| name | project1 |
| identifier | project1 |
And the user "bob" is a "member" in the project "project1"
And I am already logged in as "bob"
@javascript
Scenario: Creating a wiki child page the title of which contains special characters
Given the project "project1" has 1 wiki page with the following:
| title | ParentWikiPage |
And the project "project1" has 1 wiki menu item with the following:
| title | ParentWikiPage |
| new_wiki_page | true |
When I go to the wiki new child page below the "ParentWikiPage" page of the project called "project1"
And I click "Create new child page"
And I fill in "page_title" with "Child Page !@#{$%^&*()_},./<>?;':"
And I click "Save"
Then I should see "Successful creation."

@ -39,6 +39,7 @@ module OpenProject
'database_cipher_key' => nil,
'scm_git_command' => nil,
'scm_subversion_command' => nil,
'disable_browser_cache' => true,
# email configuration
'email_delivery_method' => nil,

@ -49,7 +49,7 @@ module OpenProject
#
# 2.0.0debian-2
def self.special
'pre29'
'pre30'
end
def self.revision

@ -49,27 +49,27 @@ module Redmine::MenuManager::MenuHelper
WikiMenuItem.main_items(project_wiki).each do |main_item|
Redmine::MenuManager.loose :project_menu do |menu|
menu.push "#{main_item.item_class}".to_sym,
{ :controller => '/wiki', :action => 'show', :id => h(main_item.title) },
{ :controller => '/wiki', :action => 'show', :id => CGI.escape(main_item.title) },
:param => :project_id,
:caption => main_item.name,
:after => :repository
menu.push :"#{main_item.item_class}_new_page",
{ :action=>"new_child", :controller=>"/wiki", :id => h(main_item.title) },
{ :action=>"new_child", :controller=>"/wiki", :id => CGI.escape(main_item.title) },
:param => :project_id,
:caption => :create_child_page,
:parent => "#{main_item.item_class}".to_sym if main_item.new_wiki_page and
WikiPage.find_by_wiki_id_and_title(project_wiki.id, main_item.title)
menu.push :"#{main_item.item_class}_toc",
{ :action => 'index', :controller => '/wiki', :id => h(main_item.title) },
{ :action => 'index', :controller => '/wiki', :id => CGI.escape(main_item.title) },
:param => :project_id,
:caption => :label_table_of_contents,
:parent => "#{main_item.item_class}".to_sym if main_item.index_page
main_item.children.each do |child|
menu.push "#{child.item_class}".to_sym,
{ :controller => '/wiki', :action => 'show', :id => h(child.title) },
{ :controller => '/wiki', :action => 'show', :id => CGI.escape(child.title) },
:param => :project_id,
:caption => child.name,
:parent => "#{main_item.item_class}".to_sym

@ -0,0 +1,24 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require_relative '../../db/migrate/migration_utils/utils'
namespace :migrations do
namespace :journals do
desc "Fixes 'attachments' table collation"
task :fix_attachments_collation => :environment do |task|
ActiveRecord::Base.connection.execute <<-SQL
ALTER TABLE attachments CONVERT TO character SET utf8 COLLATE utf8_unicode_ci;
SQL
end
end
end

@ -0,0 +1,61 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require_relative '../../db/migrate/migration_utils/timelines'
namespace :migrations do
namespace :journals do
desc "Quotes all strings starting with an invalid character in column 'changed_data' of table 'legacy_journals'"
task :quote_strings_with_invalid_characters_in_legacy_journals => :environment do |task|
quoter = InvalidChangedDataStringQuoter.new
quoter.quote_strings_with_invalid_characters
end
private
class InvalidChangedDataStringQuoter < ActiveRecord::Migration
include Migration::Utils
def quote_strings_with_invalid_characters
say_with_time_silently "Quote journal strings with invalid characters" do
update_column_values('legacy_journals',
['changed_data'],
quote_invalid_strings,
invalid_changed_data_filter)
end
end
private
def invalid_changed_data_filter
"changed_data LIKE '%- ,%'"
end
INVALID_STARTING_CHARACTER_REGEX = /(?<start>\n- )(?<text>,.*)(?<end>\n.*:)/
def quote_invalid_strings
Proc.new do |row|
changed_data = row['changed_data']
quoted_changed_data = changed_data.gsub(INVALID_STARTING_CHARACTER_REGEX) do |m|
"#{$1}\"#{$2}\"#{$3}"
end
row['changed_data'] = quoted_changed_data
UpdateResult.new(row, changed_data != quoted_changed_data)
end
end
end
end
end

@ -14,9 +14,6 @@ require_relative '../../db/migrate/migration_utils/timelines'
namespace :migrations do
namespace :timelines do
desc "Sets all timelines with historical comparison from 'historical' to 'none'"
task :remove_timelines_historical_comparison_from_options => :environment do |task|
setter = TimelinesHistoricalComparisonSetter.new

@ -192,6 +192,7 @@ describe WikiController do
describe 'successful action' do
context 'when it is not the only wiki page' do
let(:wiki) { @project.wiki }
let(:redirect_page_after_destroy) { wiki.find_page(wiki.start_page) || wiki.pages.first }
before do
another_wiki_page = FactoryGirl.create :wiki_page, wiki: wiki
@ -199,7 +200,7 @@ describe WikiController do
it 'redirects to wiki#index' do
delete :destroy, project_id: @project, id: @existing_page
response.should redirect_to action: 'index', project_id: @project
response.should redirect_to action: 'index', project_id: @project, id: redirect_page_after_destroy
end
end

@ -106,14 +106,16 @@ describe WorkPackage do
end
context 'when there is a legacy journal containing non-escaped newlines' do
before do
work_package_1.save
# force the latest journal to match the description with unescaped newline characters
legacy_journal = work_package_1.journals.last
legacy_journal.data.update_column :description, changed_description
# rollback work package description to normalized newlines
work_package_1.update_attributes description: description
end
let!(:work_package_journal_1) { FactoryGirl.create(:work_package_journal,
journable_id: work_package_1.id,
version: 2,
data: FactoryGirl.build(:journal_work_package_journal,
description: description)) }
let!(:work_package_journal_2) { FactoryGirl.create(:work_package_journal,
journable_id: work_package_1.id,
version: 3,
data: FactoryGirl.build(:journal_work_package_journal,
description: changed_description)) }
subject { work_package_1.journals.last.details }

@ -27,6 +27,11 @@
#++
require 'rubygems'
if ENV['CI'] == true
# we are running on a CI server, report coverage to code climate
require "codeclimate-test-reporter"
CodeClimate::TestReporter.start
end
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'

@ -43,6 +43,14 @@ class WikiControllerTest < ActionController::TestCase
User.current = nil
end
def wiki
Project.first.wiki
end
def redirect_page
wiki.find_page(wiki.start_page) || wiki.pages.first
end
def test_show_start_page
get :show, :project_id => 'ecookbook'
assert_response :success
@ -97,7 +105,7 @@ class WikiControllerTest < ActionController::TestCase
:content => {:comments => 'Created the page',
:text => "h1. New page\n\nThis is a new page" }
assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page'
page = Project.find(1).wiki.find_page('New page')
page = wiki.find_page('New page')
assert !page.new_record?
assert_not_nil page.content
assert_equal 'Created the page', page.content.last_journal.notes
@ -115,7 +123,7 @@ class WikiControllerTest < ActionController::TestCase
:attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
end
end
page = Project.find(1).wiki.find_page('New page')
page = wiki.find_page('New page')
assert_equal 1, page.attachments.count
assert_equal 'testfile.txt', page.attachments.first.filename
end
@ -326,7 +334,6 @@ class WikiControllerTest < ActionController::TestCase
:wiki_page => { :title => 'Another renamed page',
:redirect_existing_links => 1 }
assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
wiki = Project.find(1).wiki
# Check redirects
assert_not_nil wiki.find_page('Another page')
assert_nil wiki.find_page('Another page', :with_redirect => false)
@ -338,7 +345,6 @@ class WikiControllerTest < ActionController::TestCase
:wiki_page => { :title => 'Another renamed page',
:redirect_existing_links => "0" }
assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
wiki = Project.find(1).wiki
# Check that there's no redirects
assert_nil wiki.find_page('Another page')
end
@ -346,7 +352,7 @@ class WikiControllerTest < ActionController::TestCase
def test_destroy_child
@request.session[:user_id] = 2
delete :destroy, :project_id => 1, :id => 'Child_1'
assert_redirected_to :action => 'index', :project_id => 'ecookbook'
assert_redirected_to action: 'index', project_id: 'ecookbook', id: redirect_page
end
def test_destroy_parent
@ -363,7 +369,7 @@ class WikiControllerTest < ActionController::TestCase
assert_difference('WikiPage.count', -1) do
delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'nullify'
end
assert_redirected_to :action => 'index', :project_id => 'ecookbook'
assert_redirected_to action: 'index', project_id: 'ecookbook', id: redirect_page
assert_nil WikiPage.find_by_id(2)
end
@ -372,7 +378,7 @@ class WikiControllerTest < ActionController::TestCase
assert_difference('WikiPage.count', -3) do
delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'destroy'
end
assert_redirected_to :action => 'index', :project_id => 'ecookbook'
assert_redirected_to action: 'index', project_id: 'ecookbook', id: redirect_page
assert_nil WikiPage.find_by_id(2)
assert_nil WikiPage.find_by_id(5)
end
@ -382,7 +388,7 @@ class WikiControllerTest < ActionController::TestCase
assert_difference('WikiPage.count', -1) do
delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'reassign', :reassign_to_id => 1
end
assert_redirected_to :action => 'index', :project_id => 'ecookbook'
assert_redirected_to action: 'index', project_id: 'ecookbook', id: redirect_page
assert_nil WikiPage.find_by_id(2)
assert_equal WikiPage.find(1), WikiPage.find_by_id(5).parent
end
@ -393,7 +399,7 @@ class WikiControllerTest < ActionController::TestCase
assert_template 'index'
pages = assigns(:pages)
assert_not_nil pages
assert_equal Project.find(1).wiki.pages.size, pages.size
assert_equal wiki.pages.size, pages.size
assert_equal pages.first.content.updated_on, pages.first.updated_on
assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },

@ -29,6 +29,12 @@
ENV["RAILS_ENV"] = "test"
if ENV['CI'] == true
# we are running on a CI server, report coverage to code climate
require "codeclimate-test-reporter"
CodeClimate::TestReporter.start
end
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'fileutils'

@ -73,10 +73,10 @@ class Redmine::WikiFormatting::MacrosTest < HelperTestCase
end
def test_macro_child_pages
expected = "<p><ul class=\"pages-hierarchy\">\n" +
"<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>\n" +
"<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
"</ul>\n</p>"
expected = "<p><ul class=\"pages-hierarchy\">" +
"<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>" +
"<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>" +
"</ul></p>"
@project = Project.find(1)
# child pages of the current wiki page
@ -89,12 +89,12 @@ class Redmine::WikiFormatting::MacrosTest < HelperTestCase
end
def test_macro_child_pages_with_option
expected = "<p><ul class=\"pages-hierarchy\">\n" +
"<li><a href=\"/projects/ecookbook/wiki/Another_page\">Another page</a>\n" +
"<ul class=\"pages-hierarchy\">\n" +
"<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>\n" +
"<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
"</ul>\n</li>\n</ul>\n</p>"
expected = "<p><ul class=\"pages-hierarchy\">" +
"<li><a href=\"/projects/ecookbook/wiki/Another_page\">Another page</a>" +
"<ul class=\"pages-hierarchy\">" +
"<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>" +
"<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>" +
"</ul></li></ul></p>"
@project = Project.find(1)
# child pages of the current wiki page

Loading…
Cancel
Save