diff --git a/Gemfile b/Gemfile index f2c01a6..68b1b43 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,8 @@ source 'https://rubygems.org' -if Gem::Version.new(Bundler::VERSION) < Gem::Version.new('1.5.0') - abort "Redmine requires Bundler 1.5.0 or higher (you're using #{Bundler::VERSION}).\nPlease update with 'gem update bundler'." -end +gem "bundler", ">= 1.5.0", "< 2.0.0" -gem "rails", "4.2.8" +gem "rails", "4.2.11.1" gem "addressable", "2.4.0" if RUBY_VERSION < "2.0" if RUBY_VERSION < "2.1" gem "public_suffix", (RUBY_VERSION < "2.0" ? "~> 1.4" : "~> 2.0.5") @@ -23,13 +21,17 @@ gem "mail", "~> 2.6.4" gem "nokogiri", (RUBY_VERSION >= "2.1" ? "~> 1.8.1" : "~> 1.6.8") gem "i18n", "~> 0.7.0" gem "ffi", "1.9.14", :platforms => :mingw if RUBY_VERSION < "2.0" +gem "xpath", "< 3.2.0" if RUBY_VERSION < "2.3" # Request at least rails-html-sanitizer 1.0.3 because of security advisories gem "rails-html-sanitizer", ">= 1.0.3" +# TODO: Remove the following line when #32223 is fixed +gem "sprockets", "~> 3.7.2" + # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :x64_mingw, :mswin] -gem "rbpdf", "~> 1.19.3" +gem "rbpdf", "~> 1.19.6" # Optional gem for LDAP authentication group :ldap do @@ -38,14 +40,14 @@ end # Optional gem for OpenID authentication group :openid do - gem "ruby-openid", "~> 2.3.0", :require => "openid" + gem "ruby-openid", "~> 2.9.2", :require => "openid" gem "rack-openid" end platforms :mri, :mingw, :x64_mingw do # Optional gem for exporting the gantt to a PNG file, not supported with jruby group :rmagick do - gem "rmagick", ">= 2.14.0" + gem "rmagick", "~> 2.16.0" end # Optional Markdown support, not for JRuby @@ -94,12 +96,12 @@ end group :test do gem "minitest" gem "rails-dom-testing" - gem "mocha" + gem 'mocha', '>= 1.4.0' gem "simplecov", "~> 0.9.1", :require => false # TODO: remove this after upgrading to Rails 5 gem "test_after_commit", "~> 0.4.2" # For running UI tests - gem "capybara" + gem "capybara", '~> 2.13' gem "selenium-webdriver", "~> 2.53.4" end diff --git a/app/controllers/account_controller.rb b/app/controllers/account_controller.rb index d6e1da8..5070295 100644 --- a/app/controllers/account_controller.rb +++ b/app/controllers/account_controller.rb @@ -87,7 +87,7 @@ class AccountController < ApplicationController @user.must_change_passwd = false if @user.save @token.destroy - Mailer.password_updated(@user) + Mailer.password_updated(@user, { remote_ip: request.remote_ip }) flash[:notice] = l(:notice_account_password_updated) redirect_to signin_path return diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb index 1b18662..64cb05e 100644 --- a/app/controllers/attachments_controller.rb +++ b/app/controllers/attachments_controller.rb @@ -60,7 +60,7 @@ class AttachmentsController < ApplicationController @attachment.increment_download end - if stale?(:etag => @attachment.digest) + if stale?(:etag => @attachment.digest, :template => false) # images are sent inline send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename), :type => detect_content_type(@attachment), @@ -70,7 +70,7 @@ class AttachmentsController < ApplicationController def thumbnail if @attachment.thumbnailable? && tbnail = @attachment.thumbnail(:size => params[:size]) - if stale?(:etag => tbnail) + if stale?(:etag => tbnail, :template => false) send_file tbnail, :filename => filename_for_content_disposition(@attachment.filename), :type => detect_content_type(@attachment), diff --git a/app/controllers/auto_completes_controller.rb b/app/controllers/auto_completes_controller.rb index 293ac20..2d707f9 100644 --- a/app/controllers/auto_completes_controller.rb +++ b/app/controllers/auto_completes_controller.rb @@ -19,7 +19,7 @@ class AutoCompletesController < ApplicationController before_action :find_project def issues - @issues = [] + issues = [] q = (params[:q] || params[:term]).to_s.strip status = params[:status].to_s issue_id = params[:issue_id].to_s @@ -32,13 +32,14 @@ class AutoCompletesController < ApplicationController scope = scope.where.not(:id => issue_id.to_i) end if q.match(/\A#?(\d+)\z/) - @issues << scope.find_by_id($1.to_i) + issues << scope.find_by_id($1.to_i) end - @issues += scope.like(q).order(:id => :desc).limit(10).to_a - @issues.compact! + issues += scope.like(q).order(:id => :desc).limit(10).to_a + issues.compact! end - render :layout => false + + render :json => format_issues_json(issues) end private @@ -50,4 +51,13 @@ class AutoCompletesController < ApplicationController rescue ActiveRecord::RecordNotFound render_404 end + + def format_issues_json(issues) + issues.map {|issue| { + 'id' => issue.id, + 'label' => "#{issue.tracker} ##{issue.id}: #{issue.subject.to_s.truncate(60)}", + 'value' => issue.id + } + } + end end diff --git a/app/controllers/enumerations_controller.rb b/app/controllers/enumerations_controller.rb index e5e3cc3..a04d7b1 100644 --- a/app/controllers/enumerations_controller.rb +++ b/app/controllers/enumerations_controller.rb @@ -91,8 +91,10 @@ class EnumerationsController < ApplicationController def build_new_enumeration class_name = params[:enumeration] && params[:enumeration][:type] || params[:type] - @enumeration = Enumeration.new_subclass_instance(class_name, enumeration_params) - if @enumeration.nil? + @enumeration = Enumeration.new_subclass_instance(class_name) + if @enumeration + @enumeration.attributes = enumeration_params || {} + else render_404 end end @@ -105,6 +107,7 @@ class EnumerationsController < ApplicationController def enumeration_params # can't require enumeration on #new action - params.permit(:enumeration => [:name, :active, :is_default, :position])[:enumeration] + cf_ids = @enumeration.available_custom_fields.map{|c| c.id.to_s} + params.permit(:enumeration => [:name, :active, :is_default, :position, :custom_field_values => cf_ids])[:enumeration] end end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 23d3992..69a947b 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -40,7 +40,8 @@ class IssuesController < ApplicationController helper :timelog def index - retrieve_query + use_session = !request.format.csv? + retrieve_query(IssueQuery, use_session) if @query.valid? respond_to do |format| @@ -367,7 +368,12 @@ class IssuesController < ApplicationController when 'destroy' # nothing to do when 'nullify' + if Setting.timelog_required_fields.include?('issue_id') + flash.now[:error] = l(:field_issue) + " " + ::I18n.t('activerecord.errors.messages.blank') + return + else time_entries.update_all(:issue_id => nil) + end when 'reassign' reassign_to = @project && @project.issues.find_by_id(params[:reassign_to_id]) if reassign_to.nil? diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 36bae86..ce9e21f 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -37,7 +37,7 @@ class SearchController < ApplicationController end # quick jump to an issue - if (m = @question.match(/^#?(\d+)$/)) && (issue = Issue.visible.find_by_id(m[1].to_i)) + if !api_request? && (m = @question.match(/^#?(\d+)$/)) && (issue = Issue.visible.find_by_id(m[1].to_i)) redirect_to issue_path(issue) return end @@ -49,7 +49,7 @@ class SearchController < ApplicationController when 'my_projects' User.current.projects when 'subprojects' - @project ? (@project.self_and_descendants.active.to_a) : nil + @project ? (@project.self_and_descendants.to_a) : nil else @project end diff --git a/app/controllers/timelog_controller.rb b/app/controllers/timelog_controller.rb index 18b911c..df6b618 100644 --- a/app/controllers/timelog_controller.rb +++ b/app/controllers/timelog_controller.rb @@ -114,6 +114,7 @@ class TimelogController < ApplicationController :time_entry => { :project_id => params[:time_entry][:project_id], :issue_id => @time_entry.issue_id, + :spent_on => @time_entry.spent_on, :activity_id => @time_entry.activity_id }, :back_url => params[:back_url] diff --git a/app/controllers/trackers_controller.rb b/app/controllers/trackers_controller.rb index b0e6979..caf6b11 100644 --- a/app/controllers/trackers_controller.rb +++ b/app/controllers/trackers_controller.rb @@ -106,6 +106,6 @@ class TrackersController < ApplicationController return end @trackers = Tracker.sorted.to_a - @custom_fields = IssueCustomField.all.sort + @custom_fields = IssueCustomField.sorted end end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 8bad792..a28e2bd 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -32,7 +32,7 @@ class WikiController < ApplicationController default_search_scope :wiki_pages before_action :find_wiki, :authorize - before_action :find_existing_or_new_page, :only => [:show, :edit, :update] + before_action :find_existing_or_new_page, :only => [:show, :edit] before_action :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version] before_action :find_attachments, :only => [:preview] accept_api_auth :index, :show, :update, :destroy @@ -42,8 +42,6 @@ class WikiController < ApplicationController helper :watchers include Redmine::Export::PDF - include ActionView::Helpers::SanitizeHelper - # List of pages, sorted alphabetically and by parent (hierarchy) def index load_pages_for_index @@ -109,7 +107,7 @@ class WikiController < ApplicationController send_data(export, :type => 'text/html', :filename => filename_for_content_disposition("#{@page.title}.html")) return elsif params[:format] == 'txt' - send_data(strip_tags(@content.text), :type => 'text/plain', :filename => filename_for_content_disposition("#{@page.title}.txt")) + send_data(@content.text, :type => 'text/plain', :filename => filename_for_content_disposition("#{@page.title}.txt")) return end end @@ -152,6 +150,8 @@ class WikiController < ApplicationController # Creates a new page or updates an existing one def update + @page = @wiki.find_or_new_page(params[:id]) + return render_403 unless editable? was_new_page = @page.new_record? @page.safe_attributes = params[:wiki_page] diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c5d4c76..792af98 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1272,7 +1272,7 @@ module ApplicationHelper link_to_function '', "toggleCheckboxesBySelector('#{selector}')", :title => "#{l(:button_check_all)} / #{l(:button_uncheck_all)}", - :class => 'toggle-checkboxes' + :class => 'icon icon-checked' end def progress_bar(pcts, options={}) @@ -1423,10 +1423,13 @@ module ApplicationHelper end if email.present? gravatar(email.to_s.downcase, options) rescue nil - else + elsif user.is_a?(AnonymousUser) + options[:size] &&= options[:size].to_s image_tag 'anonymous.png', GravatarHelper::DEFAULT_OPTIONS .except(:default, :rating, :ssl).merge(options) + else + nil end else '' diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 020fed7..181907e 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -127,8 +127,8 @@ module IssuesHelper content_tag('td', check_box_tag("ids[]", other_issue.id, false, :id => nil), :class => 'checkbox') + content_tag('td', relation.to_s(@issue) {|other| link_to_issue(other, :project => Setting.cross_project_issue_relations?)}.html_safe, :class => 'subject', :style => 'width: 50%') + content_tag('td', other_issue.status, :class => 'status') + - content_tag('td', other_issue.start_date, :class => 'start_date') + - content_tag('td', other_issue.due_date, :class => 'due_date') + + content_tag('td', format_date(other_issue.start_date), :class => 'start_date') + + content_tag('td', format_date(other_issue.due_date), :class => 'due_date') + content_tag('td', other_issue.disabled_core_fields.include?('done_ratio') ? '' : progress_bar(other_issue.done_ratio), :class=> 'done_ratio') + content_tag('td', link, :class => 'buttons'), :id => "relation-#{relation.id}", @@ -246,8 +246,12 @@ module IssuesHelper issue_fields_rows do |rows| values.each_with_index do |value, i| css = "cf_#{value.custom_field.id}" + attr_value = show_value(value) + if value.custom_field.text_formatting == 'full' + attr_value = content_tag('div', attr_value, class: 'wiki') + end m = (i < half ? :left : :right) - rows.send m, custom_field_name_tag(value.custom_field), show_value(value), :class => css + rows.send m, custom_field_name_tag(value.custom_field), attr_value, :class => css end end end @@ -310,7 +314,7 @@ module IssuesHelper # Returns an array of users that are proposed as watchers # on the new issue form def users_for_new_issue_watchers(issue) - users = issue.watcher_users + users = issue.watcher_users.select{|u| u.status == User::STATUS_ACTIVE} if issue.project.users.count <= 20 users = (users + issue.project.users.sort).uniq end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 40c59f2..8788711 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -18,14 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module SearchHelper - include ActionView::Helpers::SanitizeHelper - def highlight_tokens(text, tokens) return text unless text && tokens && !tokens.empty? re_tokens = tokens.collect {|t| Regexp.escape(t)} regexp = Regexp.new "(#{re_tokens.join('|')})", Regexp::IGNORECASE result = '' - text = strip_tags(text) text.split(regexp).each_with_index do |words, i| if result.length > 1200 # maximum length of the preview reached diff --git a/app/models/attachment.rb b/app/models/attachment.rb index eea8013..64ac2c2 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -156,7 +156,7 @@ class Attachment < ActiveRecord::Base end def title - title = filename.to_s + title = filename.dup if description.present? title << " (#{description})" end diff --git a/app/models/import.rb b/app/models/import.rb index d2c53ba..71bc3c1 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -217,6 +217,7 @@ class Import < ActiveRecord::Base csv_options = {:headers => false} csv_options[:encoding] = settings['encoding'].to_s.presence || 'UTF-8' + csv_options[:encoding] = 'bom|UTF-8' if csv_options[:encoding] == 'UTF-8' separator = settings['separator'].to_s csv_options[:col_sep] = separator if separator.size == 1 wrapper = settings['wrapper'].to_s diff --git a/app/models/issue.rb b/app/models/issue.rb index c84377d..f995903 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -275,7 +275,8 @@ class Issue < ActiveRecord::Base end end unless options[:watchers] == false - self.watcher_user_ids = issue.watcher_user_ids.dup + self.watcher_user_ids = + issue.watcher_users.select{|u| u.status == User::STATUS_ACTIVE}.map(&:id) end @copied_from = issue @copy_options = options @@ -1086,7 +1087,7 @@ class Issue < ActiveRecord::Base if leaf? estimated_hours else - @total_estimated_hours ||= self_and_descendants.sum(:estimated_hours) + @total_estimated_hours ||= self_and_descendants.visible.sum(:estimated_hours) end end @@ -1300,7 +1301,7 @@ class Issue < ActiveRecord::Base # Reschedules the issue on the given date or the next working day and saves the record. # If the issue is a parent task, this is done by rescheduling its subtasks. - def reschedule_on!(date) + def reschedule_on!(date, journal=nil) return if date.nil? if leaf? || !dates_derived? if start_date.nil? || start_date != date @@ -1308,6 +1309,9 @@ class Issue < ActiveRecord::Base # Issue can not be moved earlier than its soonest start date date = [soonest_start(true), date].compact.max end + if journal + init_journal(journal.user) + end reschedule_on(date) begin save @@ -1631,6 +1635,8 @@ class Issue < ActiveRecord::Base copy.author = author copy.project = project copy.parent_issue_id = copied_issue_ids[child.parent_id] + copy.fixed_version_id = nil unless child.fixed_version.present? && child.fixed_version.status == 'open' + copy.assigned_to = nil unless child.assigned_to_id.present? && child.assigned_to.status == User::STATUS_ACTIVE unless copy.save logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger next @@ -1789,7 +1795,7 @@ class Issue < ActiveRecord::Base def reschedule_following_issues if start_date_changed? || due_date_changed? relations_from.each do |relation| - relation.set_issue_to_dates + relation.set_issue_to_dates(@current_journal) end end end diff --git a/app/models/issue_import.rb b/app/models/issue_import.rb index ad04c0b..9fc4f55 100644 --- a/app/models/issue_import.rb +++ b/app/models/issue_import.rb @@ -122,7 +122,10 @@ class IssueImport < Import end end if issue.project && version_name = row_value(row, 'fixed_version') - if version = issue.project.versions.named(version_name).first + version = + issue.project.versions.named(version_name).first || + issue.project.shared_versions.named(version_name).first + if version attributes['fixed_version_id'] = version.id elsif create_versions? version = issue.project.versions.build diff --git a/app/models/issue_query.rb b/app/models/issue_query.rb index f852c14..c833607 100644 --- a/app/models/issue_query.rb +++ b/app/models/issue_query.rb @@ -37,8 +37,8 @@ class IssueQuery < Query QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", :totalable => true), QueryColumn.new(:total_estimated_hours, - :sortable => "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" + - " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)", + :sortable => -> { "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" + + " WHERE #{Issue.visible_condition(User.current).gsub(/\bissues\b/, 'subtasks')} AND subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)" }, :default_order => 'desc'), QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), @@ -221,6 +221,7 @@ class IssueQuery < Query end disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')} + disabled_fields << "total_estimated_hours" if disabled_fields.include?("estimated_hours") @available_columns.reject! {|column| disabled_fields.include?(column.name.to_s) } @@ -373,7 +374,7 @@ class IssueQuery < Query neg = (operator == '!' ? 'NOT' : '') subquery = "SELECT 1 FROM #{Journal.table_name} sj" + " WHERE sj.journalized_type='Issue' AND sj.journalized_id=#{Issue.table_name}.id AND (#{sql_for_field field, '=', value, 'sj', 'user_id'})" + - " AND sj.id = (SELECT MAX(#{Journal.table_name}.id) FROM #{Journal.table_name}" + + " AND sj.id IN (SELECT MAX(#{Journal.table_name}.id) FROM #{Journal.table_name}" + " WHERE #{Journal.table_name}.journalized_type='Issue' AND #{Journal.table_name}.journalized_id=#{Issue.table_name}.id" + " AND (#{Journal.visible_notes_condition(User.current, :skip_pre_condition => true)}))" @@ -382,8 +383,26 @@ class IssueQuery < Query def sql_for_watcher_id_field(field, operator, value) db_table = Watcher.table_name - "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + - sql_for_field(field, '=', value, db_table, 'user_id') + ')' + + me, others = value.partition { |id| ['0', User.current.id.to_s].include?(id) } + sql = if others.any? + "SELECT #{Issue.table_name}.id FROM #{Issue.table_name} " + + "INNER JOIN #{db_table} ON #{Issue.table_name}.id = #{db_table}.watchable_id AND #{db_table}.watchable_type = 'Issue' " + + "LEFT OUTER JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Issue.table_name}.project_id " + + "WHERE (" + + sql_for_field(field, '=', me, db_table, 'user_id') + + ') OR (' + + Project.allowed_to_condition(User.current, :view_issue_watchers) + + ' AND ' + + sql_for_field(field, '=', others, db_table, 'user_id') + + ')' + else + "SELECT #{db_table}.watchable_id FROM #{db_table} " + + "WHERE #{db_table}.watchable_type='Issue' AND " + + sql_for_field(field, '=', me, db_table, 'user_id') + end + + "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (#{sql})" end def sql_for_member_of_group_field(field, operator, value) diff --git a/app/models/issue_relation.rb b/app/models/issue_relation.rb index d38825e..f23f744 100644 --- a/app/models/issue_relation.rb +++ b/app/models/issue_relation.rb @@ -176,10 +176,10 @@ class IssueRelation < ActiveRecord::Base set_issue_to_dates end - def set_issue_to_dates + def set_issue_to_dates(journal=nil) soonest_start = self.successor_soonest_start if soonest_start && issue_to - issue_to.reschedule_on!(soonest_start) + issue_to.reschedule_on!(soonest_start, journal) end end diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb index 518e8c8..2390339 100755 --- a/app/models/mail_handler.rb +++ b/app/models/mail_handler.rb @@ -54,7 +54,7 @@ class MailHandler < ActionMailer::Base def self.safe_receive(*args) receive(*args) rescue Exception => e - logger.error "MailHandler: an unexpected error occurred when receiving email: #{e.message}" if logger + Rails.logger.error "MailHandler: an unexpected error occurred when receiving email: #{e.message}" return false end @@ -65,7 +65,7 @@ class MailHandler < ActionMailer::Base %w(project status tracker category priority assigned_to fixed_version).each do |option| options[:issue][option.to_sym] = env[option] if env[option] end - %w(allow_override unknown_user no_permission_check no_account_notice default_group project_from_subaddress).each do |option| + %w(allow_override unknown_user no_permission_check no_account_notice no_notification default_group project_from_subaddress).each do |option| options[option.to_sym] = env[option] if env[option] end if env['private'] @@ -250,8 +250,8 @@ class MailHandler < ActionMailer::Base # add To and Cc as watchers before saving so the watchers can reply to Redmine add_watchers(issue) - add_attachments(issue) issue.save! + add_attachments(issue) if logger logger.info "MailHandler: issue ##{issue.id} updated by #{user}" end @@ -286,7 +286,7 @@ class MailHandler < ActionMailer::Base reply else if logger - logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" + logger.info "MailHandler: ignoring reply from [#{email.from.first}] to a locked topic" end end end diff --git a/app/models/mailer.rb b/app/models/mailer.rb index 316b4e3..0c8c55c 100644 --- a/app/models/mailer.rb +++ b/app/models/mailer.rb @@ -311,7 +311,7 @@ class Mailer < ActionMailer::Base end # Notifies user that his password was updated - def self.password_updated(user) + def self.password_updated(user, options={}) # Don't send a notification to the dummy email address when changing the password # of the default admin account which is required after the first login # TODO: maybe not the best way to handle this @@ -320,6 +320,8 @@ class Mailer < ActionMailer::Base security_notification(user, message: :mail_body_password_updated, title: :button_change_password, + remote_ip: options[:remote_ip], + originator: user, url: {controller: 'my', action: 'password'} ).deliver end @@ -333,7 +335,6 @@ class Mailer < ActionMailer::Base end def security_notification(recipients, options={}) - redmine_headers 'Sender' => User.current.login @user = Array(recipients).detect{|r| r.is_a? User } set_language_if_valid(@user.try :language) @message = l(options[:message], @@ -341,7 +342,11 @@ class Mailer < ActionMailer::Base value: options[:value] ) @title = options[:title] && l(options[:title]) + @originator = options[:originator] || User.current + @remote_ip = options[:remote_ip] || @originator.remote_ip @url = options[:url] && (options[:url].is_a?(Hash) ? url_for(options[:url]) : options[:url]) + redmine_headers 'Sender' => @originator.login + redmine_headers 'Url' => @url mail :to => recipients, :subject => "[#{Setting.app_title}] #{l(:mail_subject_security_notification)}" end diff --git a/app/models/query.rb b/app/models/query.rb index c8c8986..bfa010d 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -319,9 +319,10 @@ class Query < ActiveRecord::Base " INNER JOIN #{table_name_prefix}queries_roles#{table_name_suffix} qr on qr.query_id = q.id" + " INNER JOIN #{MemberRole.table_name} mr ON mr.role_id = qr.role_id" + " INNER JOIN #{Member.table_name} m ON m.id = mr.member_id AND m.user_id = ?" + + " INNER JOIN #{Project.table_name} p ON p.id = m.project_id AND p.status <> ?" + " WHERE q.project_id IS NULL OR q.project_id = m.project_id))" + " OR #{table_name}.user_id = ?", - VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, user.id) + VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, Project::STATUS_ARCHIVED, user.id) elsif user.logged? scope.where("#{table_name}.visibility = ? OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, user.id) else @@ -340,7 +341,7 @@ class Query < ActiveRecord::Base if project (user.roles_for_project(project) & roles).any? else - Member.where(:user_id => user.id).joins(:roles).where(:member_roles => {:role_id => roles.map(&:id)}).any? + user.memberships.joins(:member_roles).where(:member_roles => {:role_id => roles.map(&:id)}).any? end else user == self.user @@ -398,6 +399,8 @@ class Query < ActiveRecord::Base params[:v][field] = options[:values] end params[:c] = column_names + params[:group_by] = group_by.to_s if group_by.present? + params[:t] = totalable_names.map(&:to_s) if totalable_names.any? params[:sort] = sort_criteria.to_param params[:set_filter] = 1 params diff --git a/app/models/version.rb b/app/models/version.rb index b489389..ee1d3da 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -45,7 +45,7 @@ class Version < ActiveRecord::Base scope :like, lambda {|arg| if arg.present? pattern = "%#{arg.to_s.strip}%" - where("LOWER(#{Version.table_name}.name) LIKE :p", :p => pattern) + where([Redmine::Database.like("#{Version.table_name}.name", '?'), pattern]) end } scope :open, lambda { where(:status => 'open') } @@ -268,7 +268,7 @@ class Version < ActiveRecord::Base end def deletable? - fixed_issues.empty? && !referenced_by_a_custom_field? + fixed_issues.empty? && !referenced_by_a_custom_field? && attachments.empty? end def default_project_version diff --git a/app/views/account/login.html.erb b/app/views/account/login.html.erb index 5e48de8..1440e32 100644 --- a/app/views/account/login.html.erb +++ b/app/views/account/login.html.erb @@ -1,7 +1,6 @@ <%= call_hook :view_account_login_top %>
-

<%= l(:label_login) %>

<%= form_tag(signin_path, onsubmit: 'return keepAnchorOnSignIn(this);') do %> <%= back_url_hidden_field_tag %> diff --git a/app/views/admin/info.html.erb b/app/views/admin/info.html.erb index c56d14e..2ff8de5 100644 --- a/app/views/admin/info.html.erb +++ b/app/views/admin/info.html.erb @@ -11,7 +11,7 @@ <% end %>
-
+
<%= Redmine::Info.environment %>
diff --git a/app/views/admin/plugins.html.erb b/app/views/admin/plugins.html.erb index e04e06a..3d88c90 100644 --- a/app/views/admin/plugins.html.erb +++ b/app/views/admin/plugins.html.erb @@ -1,19 +1,21 @@ <%= title l(:label_plugins) %> <% if @plugins.any? %> - - <% @plugins.each do |plugin| %> - - - - - - - <% end %> -
<%= plugin.name %> - <%= content_tag('span', plugin.description, :class => 'description') unless plugin.description.blank? %> - <%= content_tag('span', link_to(plugin.url, plugin.url), :class => 'url') unless plugin.url.blank? %> - <%= plugin.author_url.blank? ? plugin.author : link_to(plugin.author, plugin.author_url) %><%= plugin.version %><%= link_to(l(:button_configure), plugin_settings_path(plugin)) if plugin.configurable? %>
+
+ + <% @plugins.each do |plugin| %> + + + + + + + <% end %> +
<%= plugin.name %> + <%= content_tag('span', plugin.description, :class => 'description') unless plugin.description.blank? %> + <%= content_tag('span', link_to(plugin.url, plugin.url), :class => 'url') unless plugin.url.blank? %> + <%= plugin.author_url.blank? ? plugin.author : link_to(plugin.author, plugin.author_url) %><%= plugin.version %><%= link_to(l(:button_configure), plugin_settings_path(plugin)) if plugin.configurable? %>
+

<%= l(:label_check_for_updates) %>

<% else %>

<%= l(:label_no_data) %>

@@ -27,7 +29,7 @@ $(document).ready(function(){ dataType: "jsonp", url: "https://www.redmine.org/plugins/check_updates", data: <%= raw_json plugin_data_for_updates(@plugins) %>, - timeout: 3000, + timeout: 10000, beforeSend: function(){ $('#ajax-indicator').show(); }, diff --git a/app/views/context_menus/issues.html.erb b/app/views/context_menus/issues.html.erb index c24a7ae..244f9c5 100644 --- a/app/views/context_menus/issues.html.erb +++ b/app/views/context_menus/issues.html.erb @@ -39,7 +39,7 @@ @@ -97,7 +97,7 @@ diff --git a/app/views/files/index.html.erb b/app/views/files/index.html.erb index a1149bf..3e8dda1 100644 --- a/app/views/files/index.html.erb +++ b/app/views/files/index.html.erb @@ -6,41 +6,43 @@ <% delete_allowed = User.current.allowed_to?(:manage_files, @project) %> - - - <%= sort_header_tag('filename', :caption => l(:field_filename)) %> - <%= sort_header_tag('created_on', :caption => l(:label_date), :default_order => 'desc') %> - <%= sort_header_tag('size', :caption => l(:field_filesize), :default_order => 'desc') %> - <%= sort_header_tag('downloads', :caption => l(:label_downloads_abbr), :default_order => 'desc') %> - - - - -<% @containers.each do |container| %> - <% next if container.attachments.empty? -%> - <% if container.is_a?(Version) -%> - - - - <% end -%> - <% container.attachments.each do |file| %> - - - - - - - - +
+
<%= l(:field_digest) %>
- <%= link_to(container, {:controller => 'versions', :action => 'show', :id => container}, :class => "icon icon-package") %> -
<%= link_to_attachment file, :title => file.description -%><%= format_time(file.created_on) %><%= number_to_human_size(file.filesize) %><%= file.downloads %><%= file.digest_type %>: <%= file.digest %> - <%= link_to_attachment file, class: 'icon-only icon-download', title: l(:button_download), download: true %> - <%= link_to(l(:button_delete), attachment_path(file), :class => 'icon-only icon-del', - :data => {:confirm => l(:text_are_you_sure)}, :method => :delete) if delete_allowed %> -
+ + <%= sort_header_tag('filename', :caption => l(:field_filename)) %> + <%= sort_header_tag('created_on', :caption => l(:label_date), :default_order => 'desc') %> + <%= sort_header_tag('size', :caption => l(:field_filesize), :default_order => 'desc') %> + <%= sort_header_tag('downloads', :caption => l(:label_downloads_abbr), :default_order => 'desc') %> + + + + + <% @containers.each do |container| %> + <% next if container.attachments.empty? -%> + <% if container.is_a?(Version) -%> + + + + <% end -%> + <% container.attachments.each do |file| %> + + + + + + + + + <% end %> <% end %> -<% end %> - -
<%= l(:field_digest) %>
+ <%= link_to(container, {:controller => 'versions', :action => 'show', :id => container}, :class => "icon icon-package") %> +
<%= link_to_attachment file, :title => file.description -%><%= format_time(file.created_on) %><%= number_to_human_size(file.filesize) %><%= file.downloads %><%= file.digest_type %>: <%= file.digest %> + <%= link_to_attachment file, class: 'icon-only icon-download', title: l(:button_download), download: true %> + <%= link_to(l(:button_delete), attachment_path(file), :class => 'icon-only icon-del', + :data => {:confirm => l(:text_are_you_sure)}, :method => :delete) if delete_allowed %> +
+ + +
<% html_title(l(:label_attachment_plural)) -%> diff --git a/app/views/issue_relations/create.js.erb b/app/views/issue_relations/create.js.erb index 0c2e036..1572a91 100644 --- a/app/views/issue_relations/create.js.erb +++ b/app/views/issue_relations/create.js.erb @@ -2,6 +2,6 @@ $('#relations').html('<%= escape_javascript(render :partial => 'issues/relations <% if @relation.errors.empty? %> $('#relation_delay').val(''); $('#relation_issue_to_id').val(''); - $('#relation_issue_to_id').focus(); <% end %> $('#new-relation-form').show(); +$('#relation_issue_to_id').focus(); diff --git a/app/views/issues/_changesets.html.erb b/app/views/issues/_changesets.html.erb index a407a90..3bd775c 100644 --- a/app/views/issues/_changesets.html.erb +++ b/app/views/issues/_changesets.html.erb @@ -13,6 +13,8 @@ <% end %>
<%= authoring(changeset.committed_on, changeset.author) %>

-
<%= format_changeset_comments changeset %>
+
+ <%= format_changeset_comments changeset %> +
<% end %> diff --git a/app/views/issues/destroy.html.erb b/app/views/issues/destroy.html.erb index 9b08c36..61b841a 100644 --- a/app/views/issues/destroy.html.erb +++ b/app/views/issues/destroy.html.erb @@ -6,7 +6,9 @@

<%= l(:text_destroy_time_entries_question, :hours => number_with_precision(@hours, :precision => 2)) %>


+<% unless Setting.timelog_required_fields.include?('issue_id') %>
+<% end %> <% if @project %> <%= text_field_tag 'reassign_to_id', params[:reassign_to_id], :size => 6, :onfocus => '$("#todo_reassign").attr("checked", true);' %> diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb index aeb10d9..48ba562 100644 --- a/app/views/layouts/base.html.erb +++ b/app/views/layouts/base.html.erb @@ -2,31 +2,28 @@ - + <%= html_title %> - + <%= csrf_meta_tag %> <%= favicon %> -<%= stylesheet_link_tag 'jquery/jquery-ui-1.11.0', 'cookieconsent.min', 'application', 'responsive', :media => 'all' %> +<%= stylesheet_link_tag 'jquery/jquery-ui-1.11.0', 'application', 'responsive', :media => 'all' %> <%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %> -<% is_welcome = !User.current.logged? && current_page?(:controller => 'welcome', :action => 'index') %> -<%= stylesheet_link_tag 'frontpage', :media => 'all' if is_welcome %> - <%= javascript_heads %> - <%= heads_for_theme %> <%= call_hook :view_layouts_base_html_head %> <%= yield :header_tags -%> - + <%= call_hook :view_layouts_base_body_top %>

+ <% if User.current.logged? || !Setting.login_required? %>
- <%= l(:label_role_plural) %> <%= toggle_checkboxes_link('.roles-selection input') %> + <%= toggle_checkboxes_link('.roles-selection input') %><%= l(:label_role_plural) %>
<% User.current.managed_roles(@project).each do |role| %> diff --git a/app/views/principal_memberships/_new_form.html.erb b/app/views/principal_memberships/_new_form.html.erb index c49d849..90d91fc 100644 --- a/app/views/principal_memberships/_new_form.html.erb +++ b/app/views/principal_memberships/_new_form.html.erb @@ -1,5 +1,5 @@
- <%= l(:label_project_plural) %> <%= toggle_checkboxes_link('.projects-selection input:enabled') %> + <%= toggle_checkboxes_link('.projects-selection input:enabled') %><%= l(:label_project_plural) %>
<%= render_project_nested_lists(@projects) do |p| %> @@ -12,7 +12,7 @@
- <%= l(:label_role_plural) %> <%= toggle_checkboxes_link('.roles-selection input') %> + <%= toggle_checkboxes_link('.roles-selection input') %><%= l(:label_role_plural) %>
<% @roles.each do |role| %>
-
<%= format_changeset_comments @changeset %>
+
+ <%= format_changeset_comments @changeset %> +
<% if @changeset.issues.visible.any? || User.current.allowed_to?(:manage_related_issues, @repository.project) %> <%= render :partial => 'related_issues' %> diff --git a/app/views/trackers/_form.html.erb b/app/views/trackers/_form.html.erb index 7b5b7ea..3d71d76 100644 --- a/app/views/trackers/_form.html.erb +++ b/app/views/trackers/_form.html.erb @@ -21,10 +21,11 @@

<%= hidden_field_tag 'tracker[core_fields][]', '' %> -<% if IssueCustomField.all.any? %> +<% @issue_custom_fields = IssueCustomField.sorted %> +<% if @issue_custom_fields.present? %>

- <% IssueCustomField.all.each do |field| %> + <% @issue_custom_fields.each do |field| %>

<%= l(:label_home) %>

@@ -26,149 +24,3 @@ <%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :key => User.current.rss_key, :format => 'atom'}, :title => "#{Setting.app_title}: #{l(:label_activity)}") %> <% end %> - -<% else %> - -
<%= text_field_tag 'forcetop', nil, :style => 'display: none;' %> - - -
-
-

SuitePro

-

<%= l(:welcome_suitepro) %>

- -
- <%= l(:welcome_discover) %> -
- - -
-
-
-

A SIMPLE WAY TO GET WORK DONE  ;)

-

<%= raw l(:welcome_suitepro_is_redmine, :suitepro => 'SuitePro', :redmine => 'Redmine') %>

-
-
    -
  • Lorem
  • -
  • Ipsum
  • -
  • Dolor
  • -
-
-
- - -
-
-
<%= image_tag '/themes/circlepro/images/pic01.jpg' %>
-

<%= raw l(:welcome_spotlight_1_title) %>

-

<%= l(:welcome_spotlight_1_text) %>

-
-
-
-
<%= image_tag '/themes/circlepro/images/pic02.jpg' %>
-

<%= raw l(:welcome_spotlight_2_title) %>

-

<%= l(:welcome_spotlight_2_text) %>

-
-
-
-
<%= image_tag '/themes/circlepro/images/pic03.jpg' %>
-

<%= raw l(:welcome_spotlight_3_title) %>

-

<%= l(:welcome_spotlight_3_text) %>

-
-
-
- - -
-
-
-

<%= l(:welcome_other_features) %>

-
-
    -
  • -

    <%= l(:welcome_feature_1_title) %>

    -

    <%= l(:welcome_feature_1_text) %>

    -
  • -
  • -

    <%= l(:welcome_feature_2_title) %>

    -

    <%= l(:welcome_feature_2_text) %>

    -
  • -
  • -

    <%= l(:welcome_feature_3_title) %>

    -

    <%= l(:welcome_feature_3_text) %>

    -
  • -
  • -

    <%= l(:welcome_feature_4_title) %>

    -

    <%= l(:welcome_feature_4_text) %>

    -
  • -
  • -

    <%= l(:welcome_feature_5_title) %>

    -

    <%= l(:welcome_feature_5_text) %>

    -
  • -
  • -

    <%= l(:welcome_feature_6_title) %>

    -

    <%= l(:welcome_feature_6_text) %>

    -
  • -
-
-
- - -
-
- <%= call_hook :view_account_login_top %> - -
-

<%= l(:label_login) %>

- <%= form_tag(signin_path, onsubmit: 'return keepAnchorOnSignIn(this);') do %> - <%= back_url_hidden_field_tag %> - - - <%= text_field_tag 'username', params[:username], :tabindex => '1' %> - - - <%= password_field_tag 'password', nil, :tabindex => '2' %> - - <% if Setting.openid? %> - - <%= text_field_tag "openid_url", nil, :tabindex => '3' %> - <% end %> - - <% if Setting.autologin? %> - - <% end %> - - - <% end %> -
-
-
- - -
-
-
-

<%= l(:welcome_any_questions) %>

-

<%= l(:welcome_please_contact) %>

-
- -
-
- -
- - - - - - - -<% end %> diff --git a/app/views/wiki/show.html.erb b/app/views/wiki/show.html.erb index f903c1e..7e14716 100644 --- a/app/views/wiki/show.html.erb +++ b/app/views/wiki/show.html.erb @@ -46,8 +46,7 @@ <%= render(:partial => "wiki/content", :locals => {:content => @content}) %> -<% if @page.attachments.length > 0 || (@editable && authorize_for('wiki', 'add_attachment')) %> -
+ -<% end %> <% other_formats_links do |f| %> <%= f.link_to 'PDF', :url => {:id => @page.title, :version => params[:version]} %> diff --git a/config/locales/de.yml b/config/locales/de.yml index e50bff0..f01b4d2 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1211,8 +1211,8 @@ de: label_table_of_contents: Inhaltsverzeichnis error_no_projects_with_tracker_allowed_for_new_issue: Es gibt keine Projekte mit Trackern, für welche sie Tickets erzeugen können field_textarea_font: Schriftart für Textbereiche - label_font_default: Strandardschrift - label_font_monospace: Nichtproporzionale Schrift + label_font_default: Standardschrift + label_font_monospace: Nichtproportionale Schrift label_font_proportional: Proportionale Schrift setting_commit_logs_formatting: Textformatierung für Commit Nachrichten setting_mail_handler_enable_regex_delimiters: Reguläre Ausdrücke erlauben diff --git a/config/locales/en.yml b/config/locales/en.yml index aa3d78a..df57a57 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1209,40 +1209,3 @@ en: description_issue_category_reassign: Choose issue category description_wiki_subpages_reassign: Choose new parent page text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed.' - - welcome_suitepro: Planning, knowledge sharing, clients support and personal productivity, with both powerful and simplicity in mind. - welcome_discover: Discover - welcome_suitepro_is_redmine: "%{suitepro} is powered by %{redmine}, the popular project management and issue tracking solution based on the Ruby on Rails framework. Redmine lets us have a powerful workflow for getting tasks done and keep information in one place." - - welcome_spotlight_1_title: "The Basics: Projects,
Tasks, Issue Tracking" - welcome_spotlight_1_text: Keep track of everything, with visual indicators to monitorize tasks and subtasks in order to stay up to date with milestones, track of time, workflows and all that requires attention. - welcome_spotlight_2_title: "Documents, Wikis,
File Management" - welcome_spotlight_2_text: Keep documents and files availables wherever you are. Use the wiki project to save project requeriments, attached files, install and user guides, or meeting minutes at your fingertips. - welcome_spotlight_3_title: "Flexible control
of user access" - welcome_spotlight_3_text: Using a role-based approach, roles are a collection of permissions outlining how users can operate with the projects. Each member of a project can have one or more roles assigned by administrators. - - welcome_other_features: Other Features - welcome_feature_1_title: Gantt Chart And Calendar - welcome_feature_1_text: The gantt chart displays issues that have a start date and a due date. The calendar provides an overview of a project as a monthly view. - welcome_feature_2_title: News And Forums - welcome_feature_2_text: News to show information about the status of projects or any other subjects. The forums allow users from a project to communicate with each others. - welcome_feature_3_title: Email notifications And Feeds - welcome_feature_3_text: SuitePro can be configured to receive notifications via email. It also provides web feeds to use with external readers or aggregators. - welcome_feature_4_title: Code Repositories - welcome_feature_4_text: Version Control Systems like Git or Subversion can be used as code repositories and also keep track of changes made to the code. - welcome_feature_5_title: Responsive Design - welcome_feature_5_text: SuitePro is optimized to look great on mobile devices thanks to its responsive design. All pages adapt automatically to the screen size on a mobile phone, tablet or desktop computer. - welcome_feature_6_title: Is Open Source Software - welcome_feature_6_text: It means you are not locked into using a particular vendor’s system, and it’s continually evolving in real time as developers add to it and modify it. - - welcome_any_questions: Any Questions? - welcome_please_contact: Please feel free to contact me if you need any further information. - welcome_contact: Contact - welcome_about_me: More About Me - - link_my_blog: My Blog - - label_legal: Legal notice - label_legal_terms: Terms of use - label_legal_privacy: Privacy policy - label_legal_cookies: Cookies policy diff --git a/config/locales/es.yml b/config/locales/es.yml index 6177f32..efc8c2b 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -504,7 +504,7 @@ es: label_loading: Cargando... label_logged_as: Conectado como label_login: Iniciar sesión - label_logout: Cerrar sesión + label_logout: Terminar sesión label_max_size: Tamaño máximo label_me: yo mismo label_member: Miembro @@ -543,7 +543,7 @@ es: label_optional_description: Descripción opcional label_options: Opciones label_overall_activity: Actividad global - label_overview: Resumen + label_overview: Vistazo label_password_lost: ¿Olvidaste la contraseña? label_permissions: Permisos label_permissions_report: Informe de permisos @@ -1217,7 +1217,7 @@ es: mail_body_security_notification_notify_disabled: Se han desactivado las notificaciones para el correo electrónico %{value} mail_body_settings_updated: ! 'Las siguientes opciones han sido actualizadas:' field_remote_ip: Dirección IP - label_wiki_page_new: Nueva página + label_wiki_page_new: Nueva pagina wiki label_relations: Relaciones button_filter: Filtro mail_body_password_updated: Su contraseña se ha cambiado. @@ -1233,7 +1233,7 @@ es: label_font_monospace: Fuente Monospaced label_font_proportional: Fuente Proportional setting_timespan_format: Time span format - label_table_of_contents: Tabla de contenidos + label_table_of_contents: Table of contents setting_commit_logs_formatting: Apply text formatting to commit messages setting_mail_handler_enable_regex_delimiters: Enable regular expressions error_move_of_child_not_possible: 'Subtask %{child} could not be moved to the new @@ -1256,41 +1256,3 @@ es: permission_view_news: View news label_no_preview_alternative_html: No preview available. %{link} the file instead. label_no_preview_download: Download - - - welcome_suitepro: Potencia y sencillez para planificar, compartir conocimiento, prestar soporte a clientes y acelerar la productividad. - welcome_discover: Descubre - welcome_suitepro_is_redmine: "%{suitepro} es %{redmine}, la conocida herramienta para la gestión de proyectos y el seguimiento de peticiones basada en Ruby on Rails. Redmine apremia la finalización de las tareas y mantiene la información en un único sitio." - - welcome_spotlight_1_title: "Lo Básico: Proyectos,
Tareas, Peticiones" - welcome_spotlight_1_text: Podrás hacer un seguimiento completo de todo, monitorizar tareas y subtareas para estar al día de los hitos de proyecto, controlar los tiempos, los flujos de trabajo o cualquier elemento que requiera atención. - welcome_spotlight_2_title: "Documentos, Wikis,
Gestión de Archivos" - welcome_spotlight_2_text: Organizados para disponer de los documentos y los archivos allá donde se esté. Y el wiki de proyecto para estructurar los requerimientos, las guías de instalación y de usuario, o las actas de trabajo. - welcome_spotlight_3_title: "Control de accesos
flexible" - welcome_spotlight_3_text: Usando permisos agrupados en roles para establecer cómo pueden operar los usuarios en los proyectos. Cada miembro de un proyecto podrá tener uno o más roles asignados por los administradores. - - welcome_other_features: Otras Características - welcome_feature_1_title: Diagrama de Gantt y Calendario - welcome_feature_1_text: El diagrama de Gantt muestra las tareas que tienen fecha de inicio y vencimiento. Y el calendario da una visión general de los proyectos en una vista mensual. - welcome_feature_2_title: Noticias y Foros - welcome_feature_2_text: Las noticias muestran información sobre novedades en los proyectos u otros temas de interés. Y los foros permiten que los usuarios de un proyecto se comuniquen entre sí. - welcome_feature_3_title: Notificaciones y Sindicación - welcome_feature_3_text: SuitePro se puede configurar para recibir notificaciones por correo electrónico. Y también proporciona sindicaciones para utilizar con agregadores externos. - welcome_feature_4_title: Repositorios de Código - welcome_feature_4_text: Sistemas de Control de Versiones como Git o Subversion pueden usarse como repositorios de código y seguir desde SuitePro los cambios realizados en el código. - welcome_feature_5_title: Responsive Design - welcome_feature_5_text: SuitePro está optimizado para visualizarse en dispositivos móviles. Las páginas se adaptan automáticamente al tamaño de la pantalla, ya sea un teléfono móvil, una tableta o un ordenador. - welcome_feature_6_title: Es Software de Código Abierto - welcome_feature_6_text: Esto supone no estar limitado por ningún proveedor, y seguir en permanente evolución con desarrolladores que se involucran continuamente. - - welcome_any_questions: ¿Alguna Pregunta? - welcome_please_contact: No dudar en contactar conmigo para obtener más información. - welcome_contact: Contactar - welcome_about_me: Más Sobre Mí - - link_my_blog: Mi blog personal - - label_legal: Aviso legal - label_legal_terms: Condiciones de uso - label_legal_privacy: Política de privacidad - label_legal_cookies: Uso de cookies diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 95f2dd7..b5de3ac 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -140,8 +140,8 @@ gl: invalid: "non é válido" confirmation: "non coincide coa confirmación" accepted: "debe ser aceptado" - empty: "non pode estar valeiro" - blank: "non pode estar en blanco" + empty: "non pode estar baleiro" + blank: "non pode estar en branco" too_long: "é demasiado longo (non máis de %{count} carácteres)" too_short: "é demasiado curto (non menos de %{count} carácteres)" wrong_length: "non ten a lonxitude correcta (debe ser de %{count} carácteres)" @@ -426,7 +426,7 @@ gl: label_f_hour: "%{value} hora" label_f_hour_plural: "%{value} horas" label_feed_plural: Feeds - label_feeds_access_key_created_on: "Clave de acceso por Atom creada hai %{value}" + label_feeds_access_key_created_on: "Chave de acceso por Atom creada hai %{value}" label_file_added: Ficheiro engadido label_file_plural: Ficheiros label_filter_add: Engadir o filtro @@ -435,7 +435,7 @@ gl: label_follows: posterior a label_gantt: Gantt label_general: Xeral - label_generate_key: Xerar clave + label_generate_key: Xerar chave label_help: Axuda label_history: Histórico label_home: Inicio @@ -648,7 +648,7 @@ gl: notice_email_error: "Ocorreu un error enviando o correo (%{value})" notice_email_sent: "Enviouse un correo a %{value}" notice_failed_to_save_issues: "Imposible gravar %{count} petición(s) de %{total} seleccionada(s): %{ids}." - notice_feeds_access_key_reseted: A súa clave de acceso para Atom reiniciouse. + notice_feeds_access_key_reseted: A súa chave de acceso para Atom reiniciouse. notice_file_not_found: A páxina á que tenta acceder non existe. notice_locking_conflict: Os datos modificáronse por outro usuario. notice_no_issue_selected: "Ningunha petición seleccionada. Por favor, comprobe a petición que quere modificar" @@ -720,8 +720,8 @@ gl: setting_autofetch_changesets: Autorechear as remisións do repositorio setting_autologin: "Identificarse automaticamente." setting_bcc_recipients: Ocultar as copias de carbón (bcc) - setting_commit_fix_keywords: Palabras clave para a corrección - setting_commit_ref_keywords: Palabras clave para a referencia + setting_commit_fix_keywords: Palabras chave para a corrección + setting_commit_ref_keywords: Palabras chave para a referencia setting_cross_project_issue_relations: Permitir relacionar peticións de distintos proxectos setting_date_format: Formato da data setting_default_language: Idioma predeterminado @@ -738,7 +738,7 @@ gl: setting_login_required: Requírese identificación setting_mail_from: Correo dende o que enviar mensaxes setting_mail_handler_api_enabled: Activar o programa para mensaxes entrantes - setting_mail_handler_api_key: Clave da API + setting_mail_handler_api_key: Chave da API setting_per_page_options: Obxectos por páxina setting_plain_text_mail: só texto plano (non HTML) setting_protocol: Protocolo @@ -1121,118 +1121,118 @@ gl: setting_link_copied_issue: "Ligar aos tíckets ao copialos" label_link_copied_issue: "Ligar ao tícket copiado" label_ask: "Preguntar" - label_search_attachments_yes: Search attachment filenames and descriptions - label_search_attachments_no: Do not search attachments - label_search_attachments_only: Search attachments only - label_search_open_issues_only: Open issues only + label_search_attachments_yes: Buscar nomes e descricións dos ficheiros + label_search_attachments_no: Non buscar ficheiros + label_search_attachments_only: Buscar só ficheiros + label_search_open_issues_only: Só peticións abertas field_address: Correo electrónico - setting_max_additional_emails: Maximum number of additional email addresses - label_email_address_plural: Emails - label_email_address_add: Add email address - label_enable_notifications: Enable notifications - label_disable_notifications: Disable notifications - setting_search_results_per_page: Search results per page - label_blank_value: blank - permission_copy_issues: Copy issues - error_password_expired: Your password has expired or the administrator requires you - to change it. - field_time_entries_visibility: Time logs visibility - setting_password_max_age: Require password change after - label_parent_task_attributes: Parent tasks attributes - label_parent_task_attributes_derived: Calculated from subtasks - label_parent_task_attributes_independent: Independent of subtasks - label_time_entries_visibility_all: All time entries - label_time_entries_visibility_own: Time entries created by the user - label_member_management: Member management - label_member_management_all_roles: All roles - label_member_management_selected_roles_only: Only these roles - label_password_required: Confirm your password to continue + setting_max_additional_emails: Máximo número de enderezos de correo adicionais + label_email_address_plural: Correos + label_email_address_add: Engadir enderezo de correo + label_enable_notifications: Activar notificacións + label_disable_notifications: Desactivar notificacións + setting_search_results_per_page: Resultados da busca por páxina + label_blank_value: En branco + permission_copy_issues: Copiar peticións + error_password_expired: A túa contrasinal caducou ou o administrador obrígate + a cambiala. + field_time_entries_visibility: Visibilidade das entradas de tempo + setting_password_max_age: Obrigar a cambiar a contrasinal despois de + label_parent_task_attributes: Atributos da tarefa pai + label_parent_task_attributes_derived: Calculada a partir das subtarefas + label_parent_task_attributes_independent: Independente das subtarefas + label_time_entries_visibility_all: Todas as entradas de tempo + label_time_entries_visibility_own: Horas creadas polo usuario + label_member_management: Xestión de membros + label_member_management_all_roles: Todos os roles + label_member_management_selected_roles_only: Só estes roles + label_password_required: Confirma a túa contrasinal para continuar label_total_spent_time: "Tempo total empregado" - notice_import_finished: "%{count} items have been imported" - notice_import_finished_with_errors: "%{count} out of %{total} items could not be imported" - error_invalid_file_encoding: The file is not a valid %{encoding} encoded file - error_invalid_csv_file_or_settings: The file is not a CSV file or does not match the - settings below - error_can_not_read_import_file: An error occurred while reading the file to import - permission_import_issues: Import issues - label_import_issues: Import issues - label_select_file_to_import: Select the file to import - label_fields_separator: Field separator - label_fields_wrapper: Field wrapper - label_encoding: Encoding - label_comma_char: Comma - label_semi_colon_char: Semicolon - label_quote_char: Quote - label_double_quote_char: Double quote - label_fields_mapping: Fields mapping - label_file_content_preview: File content preview - label_create_missing_values: Create missing values - button_import: Import - field_total_estimated_hours: Total estimated time + notice_import_finished: "%{count} elementos foron importados" + notice_import_finished_with_errors: "%{count} dun total de %{total} elementos non puideron ser importados" + error_invalid_file_encoding: O ficheiro non é un ficheiro codificado %{encoding} válido + error_invalid_csv_file_or_settings: O ficheiro non é un arquivo CSV ou non coincide coas + opcións de abaixo + error_can_not_read_import_file: Aconteceu un erro lendo o ficheiro a importar + permission_import_issues: Importar peticións + label_import_issues: Importar peticións + label_select_file_to_import: Selecciona o ficheiro a importar + label_fields_separator: Separador dos campos + label_fields_wrapper: Envoltorio dos campos + label_encoding: Codificación + label_comma_char: Coma + label_semi_colon_char: Punto e coma + label_quote_char: Comilla simple + label_double_quote_char: Comilla dobre + label_fields_mapping: Mapeo de campos + label_file_content_preview: Vista previa do contido + label_create_missing_values: Crear valores non presentes + button_import: Importar + field_total_estimated_hours: Total de tempo estimado label_api: API - label_total_plural: Totals - label_assigned_issues: Assigned issues - label_field_format_enumeration: Key/value list + label_total_plural: Totais + label_assigned_issues: Peticións asignadas + label_field_format_enumeration: Listaxe chave/valor label_f_hour_short: '%{value} h' - field_default_version: Default version - error_attachment_extension_not_allowed: Attachment extension %{extension} is not allowed - setting_attachment_extensions_allowed: Allowed extensions - setting_attachment_extensions_denied: Disallowed extensions - label_any_open_issues: any open issues - label_no_open_issues: no open issues - label_default_values_for_new_users: Default values for new users - error_ldap_bind_credentials: Invalid LDAP Account/Password - setting_sys_api_key: Clave da API - setting_lost_password: "Esqueceu o contrasinal?" - mail_subject_security_notification: Security notification - mail_body_security_notification_change: ! '%{field} was changed.' - mail_body_security_notification_change_to: ! '%{field} was changed to %{value}.' - mail_body_security_notification_add: ! '%{field} %{value} was added.' - mail_body_security_notification_remove: ! '%{field} %{value} was removed.' - mail_body_security_notification_notify_enabled: Email address %{value} now receives - notifications. - mail_body_security_notification_notify_disabled: Email address %{value} no longer - receives notifications. - mail_body_settings_updated: ! 'The following settings were changed:' - field_remote_ip: IP address - label_wiki_page_new: New wiki page - label_relations: Relations - button_filter: Filter - mail_body_password_updated: Your password has been changed. - label_no_preview: No preview available - error_no_tracker_allowed_for_new_issue_in_project: The project doesn't have any trackers - for which you can create an issue - label_tracker_all: All trackers - label_new_project_issue_tab_enabled: Display the "New issue" tab - setting_new_item_menu_tab: Project menu tab for creating new objects - label_new_object_tab_enabled: Display the "+" drop-down - error_no_projects_with_tracker_allowed_for_new_issue: There are no projects with trackers - for which you can create an issue - field_textarea_font: Font used for text areas - label_font_default: Default font - label_font_monospace: Monospaced font - label_font_proportional: Proportional font - setting_timespan_format: Time span format - label_table_of_contents: Table of contents - setting_commit_logs_formatting: Apply text formatting to commit messages - setting_mail_handler_enable_regex_delimiters: Enable regular expressions - error_move_of_child_not_possible: 'Subtask %{child} could not be moved to the new - project: %{errors}' - error_cannot_reassign_time_entries_to_an_issue_about_to_be_deleted: Spent time cannot - be reassigned to an issue that is about to be deleted - setting_timelog_required_fields: Required fields for time logs + field_default_version: Versión predeterminada + error_attachment_extension_not_allowed: A extensión anexada %{extension} non é permitida + setting_attachment_extensions_allowed: Extensións permitidas + setting_attachment_extensions_denied: Extensións prohibidas + label_any_open_issues: Calquera petición aberta + label_no_open_issues: Peticións non abertas + label_default_values_for_new_users: Valor predeterminado para novos usuarios + error_ldap_bind_credentials: A conta/contrasinal do LDAP non é válida + setting_sys_api_key: Chave da API + setting_lost_password: Esqueceu a contrasinal? + mail_subject_security_notification: Notificación de seguridade + mail_body_security_notification_change: ! '%{field} modificado.' + mail_body_security_notification_change_to: ! '%{field} modificado por %{value}.' + mail_body_security_notification_add: ! '%{field} %{value} engadido.' + mail_body_security_notification_remove: ! '%{field} %{value} eliminado.' + mail_body_security_notification_notify_enabled: O correo electrónico %{value} agora recibirá + notificacións + mail_body_security_notification_notify_disabled: O correo electrónico %{value} deixará de recibir + notificacións + mail_body_settings_updated: ! 'As seguintes opcións foron actualizadas:' + field_remote_ip: Enderezo IP + label_wiki_page_new: Nova páxina wiki + label_relations: Relacións + button_filter: Filtro + mail_body_password_updated: A súa contrasinal foi cambiada. + label_no_preview: Non hai vista previa dispoñible + error_no_tracker_allowed_for_new_issue_in_project: O proxecto non ten ningún tipo + para que poida crear unha petición + label_tracker_all: Tódolos tipos + label_new_project_issue_tab_enabled: Amosar a lapela "Nova petición" + setting_new_item_menu_tab: Lapela no menú de cada proxecto para creación de novos obxectos + label_new_object_tab_enabled: Amosar a lista despregable "+" + error_no_projects_with_tracker_allowed_for_new_issue: Non hai proxectos con tipos + para as que poida crear unha petición + field_textarea_font: Fonte usada nas áreas de texto + label_font_default: Fonte por defecto + label_font_monospace: Fonte Monospaced + label_font_proportional: Fonte Proporcional + setting_timespan_format: Formato de período temporal + label_table_of_contents: Índice de contidos + setting_commit_logs_formatting: Aplicar formateo de texto para as mensaxes de commit + setting_mail_handler_enable_regex_delimiters: Activar expresións regulares + error_move_of_child_not_possible: 'A subtarefa %{child} non se pode mover ao novo + proxecto: %{errors}' + error_cannot_reassign_time_entries_to_an_issue_about_to_be_deleted: O tempo empregado non pode + ser reasignado a unha petición que vai ser eliminada + setting_timelog_required_fields: Campos obrigatorios para as imputacións de tempo label_attribute_of_object: '%{object_name}''s %{name}' - label_user_mail_option_only_assigned: Only for things I watch or I am assigned to - label_user_mail_option_only_owner: Only for things I watch or I am the owner of - warning_fields_cleared_on_bulk_edit: Changes will result in the automatic deletion - of values from one or more fields on the selected objects - field_updated_by: Updated by - field_last_updated_by: Last updated by - field_full_width_layout: Full width layout - label_last_notes: Last notes - field_digest: Checksum - field_default_assigned_to: Default assignee - setting_show_custom_fields_on_registration: Show custom fields on registration - permission_view_news: View news - label_no_preview_alternative_html: No preview available. %{link} the file instead. - label_no_preview_download: Download + label_user_mail_option_only_assigned: Só para as cousas que sigo ou que teño asignado + label_user_mail_option_only_owner: Só para as cousas que sigo ou son o propietario + warning_fields_cleared_on_bulk_edit: Os cambios provocarán o borrado automático + dos valores de un ou máis campos dos obxectos seleccionados + field_updated_by: Actualizado por + field_last_updated_by: Última actualización por + field_full_width_layout: Deseño para ancho completo + label_last_notes: Últimas notas + field_digest: Suma de verificación + field_default_assigned_to: Asignado por defecto + setting_show_custom_fields_on_registration: Amosar os campos personalizados no rexistro + permission_view_news: Ver noticias + label_no_preview_alternative_html: Non hai vista previa dispoñible. %{link} o ficheiro no seu lugar. + label_no_preview_download: Descargar diff --git a/config/locales/ja.yml b/config/locales/ja.yml index b274662..30b2f5c 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -618,7 +618,7 @@ ja: one: 1コメント other: "%{count}コメント" label_comment_add: コメント追加 - label_comment_added: 追加されたコメント + label_comment_added: コメントが追加されました label_comment_delete: コメント削除 label_query: カスタムクエリ label_query_plural: カスタムクエリ diff --git a/config/locales/lt.yml b/config/locales/lt.yml index ef3e8bf..b4ab1f5 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -890,7 +890,7 @@ lt: label_version_sharing_none: Nesidalinama label_version_sharing_descendants: Su sub-projektais label_version_sharing_hierarchy: Su projekto hierarchija - label_version_sharing_tree: WiSu projekto medžiu + label_version_sharing_tree: Su projekto medžiu label_version_sharing_system: Su visais projektais label_update_issue_done_ratios: Atnaujinti darbo atlikimo progresą label_copy_source: Šaltinis diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 47b2984..3f190ee 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -149,9 +149,9 @@ pt-BR: not_same_project: "não pertence ao mesmo projeto" circular_dependency: "Esta relação geraria uma dependência circular" cant_link_an_issue_with_a_descendant: "Uma tarefa não pode ser relacionada a uma de suas subtarefas" - earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" - not_a_regexp: "is not a valid regular expression" - open_issue_with_closed_parent: "An open issue cannot be attached to a closed parent task" + earlier_than_minimum_start_date: "não pode ser anterior a %{date} por causa de tarefas anteriores" + not_a_regexp: "não é uma expressão regular válida" + open_issue_with_closed_parent: "Uma tarefa aberta não pode ser associada à uma tarefa pai fechada" actionview_instancetag_blank_option: Selecione @@ -159,7 +159,7 @@ pt-BR: general_text_Yes: 'Sim' general_text_no: 'não' general_text_yes: 'sim' - general_lang_name: 'Portuguese/Brasil (Português/Brasil)' + general_lang_name: 'Portuguese/Brazil (Português/Brasil)' general_csv_separator: ';' general_csv_decimal_separator: ',' general_csv_encoding: ISO-8859-1 @@ -1125,7 +1125,7 @@ pt-BR: label_group_non_member: Usuários não membros label_add_projects: Adicionar projetos field_default_status: Situação padrão - text_subversion_repository_note: 'Examplos: file:///, http://, https://, svn://, svn+[tunnelscheme]://' + text_subversion_repository_note: 'Exemplos: file:///, http://, https://, svn://, svn+[tunnelscheme]://' field_users_visibility: Visibilidade do usuário label_users_visibility_all: Todos usuários ativos label_users_visibility_members_of_visible_projects: Membros de projetos visíveis diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 630d9c9..f98311a 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1166,7 +1166,7 @@ ru: label_cross_project_system: Со всеми проектами button_hide: Скрыть setting_non_working_week_days: Нерабочие дни - label_in_the_next_days: в средующие дни + label_in_the_next_days: в следующие дни label_in_the_past_days: в прошлые дни label_attribute_of_user: Пользователь %{name} text_turning_multiple_off: Если отключить множественные значения, лишние значения из списка будут удалены, чтобы осталось только по одному значению. diff --git a/config/locales/sv.yml b/config/locales/sv.yml index b6907bd..27abe16 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -134,7 +134,7 @@ sv: not_same_project: "tillhör inte samma projekt" circular_dependency: "Denna relation skulle skapa ett cirkulärt beroende" cant_link_an_issue_with_a_descendant: "Ett ärende kan inte länkas till ett av dess underärenden" - earlier_than_minimum_start_date: "kan inte vara tidigare än% {datum} på grund av föregående ärenden" + earlier_than_minimum_start_date: "kan inte vara tidigare än %{date} på grund av föregående ärenden" not_a_regexp: "is not a valid regular expression" open_issue_with_closed_parent: "An open issue cannot be attached to a closed parent task" diff --git a/config/locales/uk.yml b/config/locales/uk.yml index ac175d7..40e02bf 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -1215,12 +1215,12 @@ uk: field_updated_by: Оновлено field_last_updated_by: Востаннє оновлено field_full_width_layout: Макет на повну ширину - label_user_mail_option_only_assigned: Only for things I watch or I am assigned to - label_user_mail_option_only_owner: Only for things I watch or I am the owner of - label_last_notes: Last notes - field_digest: Checksum - field_default_assigned_to: Default assignee - setting_show_custom_fields_on_registration: Show custom fields on registration - permission_view_news: View news - label_no_preview_alternative_html: No preview available. %{link} the file instead. - label_no_preview_download: Download + label_user_mail_option_only_assigned: Лише для об'єктів за якими я спостерігаю або до яких прикріплений + label_user_mail_option_only_owner: Лише для об'єктів за якими я спостерігаю або є власником + label_last_notes: Останні коментарі + field_digest: Контрольна сума + field_default_assigned_to: Призначити по замовчуванню + setting_show_custom_fields_on_registration: Показувати додаткові поля при реєстрації + permission_view_news: Переглядати новини + label_no_preview_alternative_html: Попередній перегляд недоступний. %{link} файл натомість. + label_no_preview_download: Завантажити diff --git a/config/locales/zh.yml b/config/locales/zh.yml index d0624f5..ada1915 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -304,7 +304,7 @@ zh: field_comments_sorting: 显示注释 field_parent_title: 上级页面 field_editable: 可编辑 - field_watcher: 跟踪者 + field_watcher: 关注者 field_identity_url: OpenID URL field_content: 内容 field_group_by: 根据此条件分组 @@ -393,9 +393,9 @@ zh: permission_save_queries: 保存查询 permission_view_gantt: 查看甘特图 permission_view_calendar: 查看日历 - permission_view_issue_watchers: 查看跟踪者列表 - permission_add_issue_watchers: 添加跟踪者 - permission_delete_issue_watchers: 删除跟踪者 + permission_view_issue_watchers: 查看关注者列表 + permission_add_issue_watchers: 添加关注者 + permission_delete_issue_watchers: 删除关注者 permission_log_time: 登记工时 permission_view_time_entries: 查看耗时 permission_edit_time_entries: 编辑耗时 @@ -675,7 +675,7 @@ zh: label_options: 选项 label_copy_workflow_from: 从以下选项复制工作流程 label_permissions_report: 权限报表 - label_watched_issues: 跟踪的问题 + label_watched_issues: 关注的问题 label_related_issues: 相关的问题 label_applied_status: 应用后的状态 label_loading: 载入中... @@ -731,7 +731,7 @@ zh: label_user_mail_option_all: "收取我的项目的所有通知" label_user_mail_option_selected: "收取选中项目的所有通知..." label_user_mail_option_none: "不收取任何通知" - label_user_mail_option_only_my_events: "只收取我跟踪或参与的项目的通知" + label_user_mail_option_only_my_events: "只收取我关注或参与的项目的通知" label_user_mail_no_self_notified: "不要发送对我自己提交的修改的通知" label_registration_activation_by_email: 通过邮件认证激活帐号 label_registration_manual_activation: 手动激活帐号 @@ -751,7 +751,7 @@ zh: label_reverse_chronological_order: 按时间顺序(倒序) label_incoming_emails: 接收邮件 label_generate_key: 生成一个key - label_issue_watchers: 跟踪者 + label_issue_watchers: 关注者 label_example: 示例 label_display: 显示 label_sort: 排序 @@ -811,8 +811,8 @@ zh: button_sort: 排序 button_log_time: 登记工时 button_rollback: 恢复到这个版本 - button_watch: 跟踪 - button_unwatch: 取消跟踪 + button_watch: 关注 + button_unwatch: 取消关注 button_reply: 回复 button_archive: 存档 button_unarchive: 取消存档 @@ -866,7 +866,7 @@ zh: text_issue_category_destroy_question: "有一些问题(%{count} 个)属于此类别。您想进行哪种操作?" text_issue_category_destroy_assignments: 删除问题的所属类别(问题变为无类别) text_issue_category_reassign_to: 为问题选择其它类别 - text_user_mail_option: "对于没有选中的项目,您将只会收到您跟踪或参与的项目的通知(比如说,您是问题的报告者, 或被指派解决此问题)。" + text_user_mail_option: "对于没有选中的项目,您将只会收到您关注或参与的项目的通知(比如说,您是问题的报告者, 或被指派解决此问题)。" text_no_configuration_data: "角色、跟踪标签、问题状态和工作流程还没有设置。\n强烈建议您先载入默认设置,然后在此基础上进行修改。" text_load_default_configuration: 载入默认设置 text_status_changed_by_changeset: "已应用到变更列表 %{value}." @@ -1008,7 +1008,7 @@ zh: text_issue_conflict_resolution_cancel: 取消我所有的变更并重新刷新显示 %{link} 。 permission_manage_related_issues: 相关问题管理 field_auth_source_ldap_filter: LDAP 过滤器 - label_search_for_watchers: 通过查找方式添加跟踪者 + label_search_for_watchers: 通过查找方式添加关注者 notice_account_deleted: 您的账号已被永久删除(账号已无法恢复)。 setting_unsubscribe: 允许用户退订 button_delete_my_account: 删除我的账号 diff --git a/doc/CHANGELOG b/doc/CHANGELOG index bbd2d6a..e26763f 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -4,6 +4,340 @@ Redmine - project management software Copyright (C) 2006-2017 Jean-Philippe Lang http://www.redmine.org/ +== 2019-12-20 v3.4.13 + +=== [Attachments] + +* Defect #20277: "Couldn't find template for digesting" error in the log when sending a thumbnail or an attachment + +=== [Gems support] + +* Patch #32592: Require 'mocha/minitest' instead of deprecated 'mocha/setup' + +=== [Text formatting] + +* Patch #25742: Improper markup sanitization in user content for space separated attribute values and different quoting styles + +== 2019-10-19 v3.4.12 + +=== [Code cleanup/refactoring] + +* Defect #32022: IssueSubtaskingTest fails with high probability + +=== [Documentation] + +* Defect #32170: Text enclosed in pre tag in Wiki formatting reference is not displayed in monospaced font in Chrome +* Defect #32184: Incorrect headings example in Textile help + +=== [Gems support] + +* Defect #32300: Don't use sprockets 4.0.0 in order to avoid Sprockets::Railtie::ManifestNeededError +* Patch #32294: Update ruby-openid to 2.9.2 + +=== [Issues] + +* Defect #31778: Total estimated time issue query column and issue field might leak information + +=== [Issues list] + +* Defect #31779: Total estimated time column shown even when estimated time field is deactivated + +=== [Translations] + +* Defect #32290: Typo in Russian translation for label_in_the_next_days + +=== [UI] + +* Defect #32012: Broken JavaScript icon in the repository view +* Defect #32024: Broken gzip icon in the repository view + +== 2019-06-10 v3.4.11 + +=== [Administration] + +* Defect #31125: Don't output ImageMagick version information to stdout + +=== [Code cleanup/refactoring] + +* Defect #30811: "rake db:fixtures:load" does not work + +=== [Email receiving] + +* Defect #30457: MailHandler.safe_receive does not output any error log +* Defect #31503: Undefined local variable sender_email in MailHandler#receive_message_reply + +=== [Issues filter] + +* Patch #31276: Serialize group_by and totalable_names in Query#as_params + +=== [SCM] + +* Defect #31120: Garbage lines in the output of 'git branch' break git adapter + +=== [Security] + +* Defect #31520: Persistent XSS in textile formatting + +=== [Translations] + +* Defect #31264: Conflicting translation between "track" and "watch" in Simplified Chinese + +=== [UI - Responsive] + +* Defect #31153: Display horizontal scroll bar of files table when overflow occurs on small screen +* Defect #31311: admin/info page: text cut off in pre tag on mobile + +=== [Wiki] + +* Patch #31334: Do not lose content when updating a wiki page that has been renamed in the meantime + +== 2019-03-31 v3.4.10 + +=== [Administration] + +* Defect #30939: Timeout for "Check for updates" on Plugins page is too short + +=== [Files] + +* Defect #31087: Deleting a version silently deletes its attachments + +=== [Issues filter] + +* Defect #30367: "Last updated by" filter causes an SQL error with MariaDB + +=== [REST API] + +* Defect #29055: Searching for issue number with REST API redirects to issue HTML page + +=== [Rails support] + +* Feature #31027: Upgrade to Rails 4.2.11.1 + +=== [Search engine] + +* Defect #30923: Project search should select subprojects scope when the project has subprojects + +=== [UI] + +* Defect #30872: Copyright is outdated + +== 2019-02-21 v3.4.9 + +=== [Gems support] + +* Defect #30114: Installing xpath with Bundler fails in Ruby <=2.2 +* Patch #30821: Stay in RMagick 2.16.0 and don't update to 3.0.0 + +=== [UI] + +* Patch #30818: Issues autocomplete should respond with content type json + +== 2019-01-20 v3.4.8 + +=== [Code cleanup/refactoring] + +* Patch #30413: Add ".ruby-version" to svn:ignore, .git:ignore, and .hgignore + +=== [Database] + +* Defect #30171: Decrypting LDAP and SCM passwords fail if the plaintext password is longer than 31 bytes + +=== [Gems support] + +* Defect #30353: Installing rails with Bundler 2.0 fails in 3.x + +=== [Importers] + +* Patch #30412: Import UTF-8 issue CSV files with BOM and quoted strings + +=== [Translations] + +* Patch #30293: Ukrainian translation update for 3.4-stable + +=== [UI] + +* Defect #30426: Table rows are not highlighted on mouseover on some pages +* Patch #29951: Quick design fix/proposals for projects index page + +== 2018-12-09 v3.4.7 + +=== [Custom fields] + +* Defect #8317: Strip whitespace from integer custom field +* Defect #28925: Custom field values for enumerations not saved +* Patch #29674: Missing validation for custom field formats based on RecordList + +=== [Email receiving] + +* Defect #28576: Attachments are added even if validation fails when updating an issue via email +* Defect #29191: Cannot set no_notification option when receiving emails via IMAP or POP3 + +=== [Importers] + +* Defect #30001: CSV importer ignores shared version names of other projects + +=== [Issues] + +* Defect #28946: If assignee is locked subtasks don't get copied +* Defect #30009: Empty sort criteria for issue query gives error +* Defect #30027: Some styles (for ex: borders for tables) in a custom field with text formatting enabled are not displayed + +=== [Issues filter] + +* Defect #26785: Wrong columns after CSV export + +=== [PDF export] + +* Defect #28125: PNG images on a wiki page don't appear in exported PDF +* Defect #28565: PDF export has too many whitespaces + +=== [REST API] + +* Defect #20788: REST API with JSON content missing attributes with false values + +=== [Rails support] + +* Feature #30043: Update Rails to 4.2.11 + +=== [SCM] + +* Defect #29413: Mercurial 4.7 compatibility + +=== [Search engine] + +* Defect #28636: Cannot find an issue from a closed subproject when search scope is Project and its subprojects + +=== [Text formatting] + +* Defect #8395: Tags start with 'pre' are handled as 'pre' tag in Textile +* Defect #29038: Thumbnail macro causes attachment file not found and broken filename and link +* Defect #29247: Textile phrase modifiers break wiki macros +* Defect #29756: \f or \v character in Textile markup may cause RegexpError exception + +=== [Time tracking] + +* Patch #29308: Time entry creation: preserve 'spent on' value when using 'Create and Continue' + +=== [Translations] + +* Patch #29702: Brazilian wiki help translation update +* Patch #29703: Brazilian translation (jstoolbar-pt-br.js) update +* Patch #29718: Brazilian translation update for 3.4-stable +* Patch #29735: Galician translation fix for the words empty, blank, and key +* Patch #29736: Galician translation update for 3.4-stable + +=== [UI] + +* Defect #29918: Related issues section ignores the date format setting +* Defect #29950: Fix list rendering inside project description in projects#index + +=== [UI - Responsive] + +* Defect #24309: Setting page for repository does not scroll horizontally on small screens + +=== [Wiki] + +* Feature #29791: Hide "Files" section in wiki pages when printing + +== 2018-06-10 v3.4.6 + +=== [Issues] + +* Defect #27863: If version is closed or locked subtasks don't get copied +* Defect #28765: Copying an issue fails if the issue is watched by a locked user +* Patch #28649: Log automatic rescheduling of following issues to journal + +=== [Permissions and roles] + +* Defect #28693: Irrelevant permission is required to access some tabs in project settings page + +=== [Project settings] + +* Defect #27122: Filter for version name should be case-insensitive + +=== [SCM] + +* Defect #28725: Mercurial 4.6 compatibility + +=== [Text formatting] + +* Defect #28469: Syntax highlighter does not work if language name is single-quoted + +=== [Translations] + +* Patch #28881: Fix Japanese mistranslation for label_comment_added + +=== [UI] + +* Defect #22023: Issue id input should get focus after adding related issue + +=== [UI - Responsive] + +* Defect #28523: Display horizontal scroll bar of plugins table when overflow occurs on small screen + +=== [Wiki] + +* Patch #27090: Show the number of attachments on wiki pages + +== 2018-04-07 v3.4.5 + +=== [Custom fields] + +* Defect #28393: Sort issue custom fields by position in tracker UI + +=== [Email notifications] + +* Defect #28302: Security notification when changing password on password forgotten is empty + +=== [Gantt] + +* Defect #28204: Too large avatar breaks gantt when assignee is a group + +=== [Issues] + +* Defect #27862: Preformatted text overflows in preview +* Patch #28168: Allow context-menu edit of % done and priority of parent issues if the fields are not derived + +=== [Issues filter] + +* Defect #28180: Role-base cross-project issue query visibility calculated incorrectly + +=== [Plugin API] + +* Patch #27963: Remove 'unloadable' from bundled sample plugin + +=== [Security] + +* Defect #26857: Fix for CVE-2015-9251 in JQuery 1.11.1 + +=== [Text formatting] + +* Defect #27884: RTL wiki class broken in Redmine 3.2.6 +* Defect #28331: h4, h5 and h6 headings on wiki pages should have a paragraph mark +* Patch #28119: Enable lax_spacing for markdown formatting in order to allow markdown blocks not surrounded by empty lines + +=== [Time tracking] + +* Defect #28110: Don't allow reassigning reported hours to the project if issue is a required field for time logs + +=== [Translations] + +* Defect #28109: Incorrect interpolation in Swedish locale +* Defect #28113: Fix typo in German label_font_default +* Defect #28192: Fix typo in German label_font_monospace +* Patch #27994: Galician translation update (jstoolbar-gl.js) +* Patch #28102: Fix typo in Lithuanian label_version_sharing_tree + +=== [UI] + +* Defect #28079: The green tick is positioned after the label in the new member modals +* Defect #28208: Anonymous icon is wrongly displayed when assignee is a group +* Defect #28259: attachments_fields id to class change not properly reflected in all CSS + +=== [Wiki] + +* Defect #25299: Markdown pre-block could derive incorrect wiki sections + == 2018-01-08 v3.4.4 === [Accounts / authentication] diff --git a/extra/sample_plugin/app/controllers/example_controller.rb b/extra/sample_plugin/app/controllers/example_controller.rb index b85599a..d38d01d 100644 --- a/extra/sample_plugin/app/controllers/example_controller.rb +++ b/extra/sample_plugin/app/controllers/example_controller.rb @@ -1,7 +1,5 @@ # Sample plugin controller class ExampleController < ApplicationController - unloadable - layout 'base' before_action :find_project, :authorize menu_item :sample_plugin diff --git a/lib/plugins/acts_as_versioned/Rakefile b/lib/plugins/acts_as_versioned/Rakefile index 5bccb5d..46936b1 100644 --- a/lib/plugins/acts_as_versioned/Rakefile +++ b/lib/plugins/acts_as_versioned/Rakefile @@ -1,182 +1,182 @@ -require 'rubygems' - -Gem::manage_gems - -require 'rake/rdoctask' -require 'rake/packagetask' -require 'rake/gempackagetask' -require 'rake/testtask' -require 'rake/contrib/rubyforgepublisher' - -PKG_NAME = 'acts_as_versioned' -PKG_VERSION = '0.3.1' -PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" -PROD_HOST = "technoweenie@bidwell.textdrive.com" -RUBY_FORGE_PROJECT = 'ar-versioned' -RUBY_FORGE_USER = 'technoweenie' - -desc 'Default: run unit tests.' -task :default => :test - -desc 'Test the calculations plugin.' -Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.pattern = 'test/**/*_test.rb' - t.verbose = true -end - -desc 'Generate documentation for the calculations plugin.' -Rake::RDocTask.new(:rdoc) do |rdoc| - rdoc.rdoc_dir = 'rdoc' - rdoc.title = "#{PKG_NAME} -- Simple versioning with active record models" - rdoc.options << '--line-numbers --inline-source' - rdoc.rdoc_files.include('README', 'CHANGELOG', 'RUNNING_UNIT_TESTS') - rdoc.rdoc_files.include('lib/**/*.rb') -end - -spec = Gem::Specification.new do |s| - s.name = PKG_NAME - s.version = PKG_VERSION - s.platform = Gem::Platform::RUBY - s.summary = "Simple versioning with active record models" - s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG RUNNING_UNIT_TESTS) - s.files.delete "acts_as_versioned_plugin.sqlite.db" - s.files.delete "acts_as_versioned_plugin.sqlite3.db" - s.files.delete "test/debug.log" - s.require_path = 'lib' - s.autorequire = 'acts_as_versioned' - s.has_rdoc = true - s.test_files = Dir['test/**/*_test.rb'] - s.add_dependency 'activerecord', '>= 1.10.1' - s.add_dependency 'activesupport', '>= 1.1.1' - s.author = "Rick Olson" - s.email = "technoweenie@gmail.com" - s.homepage = "http://techno-weenie.net" -end - -Rake::GemPackageTask.new(spec) do |pkg| - pkg.need_tar = true -end - -desc "Publish the API documentation" -task :pdoc => [:rdoc] do - Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload -end - -desc 'Publish the gem and API docs' -task :publish => [:pdoc, :rubyforge_upload] - -desc "Publish the release files to RubyForge." -task :rubyforge_upload => :package do - files = %w(gem tgz).map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" } - - if RUBY_FORGE_PROJECT then - require 'net/http' - require 'open-uri' - - project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/" - project_data = open(project_uri) { |data| data.read } - group_id = project_data[/[?&]group_id=(\d+)/, 1] - raise "Couldn't get group id" unless group_id - - # This echos password to shell which is a bit sucky - if ENV["RUBY_FORGE_PASSWORD"] - password = ENV["RUBY_FORGE_PASSWORD"] - else - print "#{RUBY_FORGE_USER}@rubyforge.org's password: " - password = STDIN.gets.chomp - end - - login_response = Net::HTTP.start("rubyforge.org", 80) do |http| - data = [ - "login=1", - "form_loginname=#{RUBY_FORGE_USER}", - "form_pw=#{password}" - ].join("&") - http.post("/account/login.php", data) - end - - cookie = login_response["set-cookie"] - raise "Login failed" unless cookie - headers = { "Cookie" => cookie } - - release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}" - release_data = open(release_uri, headers) { |data| data.read } - package_id = release_data[/[?&]package_id=(\d+)/, 1] - raise "Couldn't get package id" unless package_id - - first_file = true - release_id = "" - - files.each do |filename| - basename = File.basename(filename) - file_ext = File.extname(filename) - file_data = File.open(filename, "rb") { |file| file.read } - - puts "Releasing #{basename}..." - - release_response = Net::HTTP.start("rubyforge.org", 80) do |http| - release_date = Time.now.strftime("%Y-%m-%d %H:%M") - type_map = { - ".zip" => "3000", - ".tgz" => "3110", - ".gz" => "3110", - ".gem" => "1400" - }; type_map.default = "9999" - type = type_map[file_ext] - boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor" - - query_hash = if first_file then - { - "group_id" => group_id, - "package_id" => package_id, - "release_name" => PKG_FILE_NAME, - "release_date" => release_date, - "type_id" => type, - "processor_id" => "8000", # Any - "release_notes" => "", - "release_changes" => "", - "preformatted" => "1", - "submit" => "1" - } - else - { - "group_id" => group_id, - "release_id" => release_id, - "package_id" => package_id, - "step2" => "1", - "type_id" => type, - "processor_id" => "8000", # Any - "submit" => "Add This File" - } - end - - query = "?" + query_hash.map do |(name, value)| - [name, URI.encode(value)].join("=") - end.join("&") - - data = [ - "--" + boundary, - "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"", - "Content-Type: application/octet-stream", - "Content-Transfer-Encoding: binary", - "", file_data, "" - ].join("\x0D\x0A") - - release_headers = headers.merge( - "Content-Type" => "multipart/form-data; boundary=#{boundary}" - ) - - target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php" - http.post(target + query, data, release_headers) - end - - if first_file then - release_id = release_response.body[/release_id=(\d+)/, 1] - raise("Couldn't get release id") unless release_id - end - - first_file = false - end - end +require 'rubygems' + +Gem::manage_gems + +require 'rake/rdoctask' +require 'rake/packagetask' +require 'rake/gempackagetask' +require 'rake/testtask' +require 'rake/contrib/rubyforgepublisher' + +PKG_NAME = 'acts_as_versioned' +PKG_VERSION = '0.3.1' +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" +PROD_HOST = "technoweenie@bidwell.textdrive.com" +RUBY_FORGE_PROJECT = 'ar-versioned' +RUBY_FORGE_USER = 'technoweenie' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the calculations plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the calculations plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = "#{PKG_NAME} -- Simple versioning with active record models" + rdoc.options << '--line-numbers --inline-source' + rdoc.rdoc_files.include('README', 'CHANGELOG', 'RUNNING_UNIT_TESTS') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +spec = Gem::Specification.new do |s| + s.name = PKG_NAME + s.version = PKG_VERSION + s.platform = Gem::Platform::RUBY + s.summary = "Simple versioning with active record models" + s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG RUNNING_UNIT_TESTS) + s.files.delete "acts_as_versioned_plugin.sqlite.db" + s.files.delete "acts_as_versioned_plugin.sqlite3.db" + s.files.delete "test/debug.log" + s.require_path = 'lib' + s.autorequire = 'acts_as_versioned' + s.has_rdoc = true + s.test_files = Dir['test/**/*_test.rb'] + s.add_dependency 'activerecord', '>= 1.10.1' + s.add_dependency 'activesupport', '>= 1.1.1' + s.author = "Rick Olson" + s.email = "technoweenie@gmail.com" + s.homepage = "http://techno-weenie.net" +end + +Rake::GemPackageTask.new(spec) do |pkg| + pkg.need_tar = true +end + +desc "Publish the API documentation" +task :pdoc => [:rdoc] do + Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload +end + +desc 'Publish the gem and API docs' +task :publish => [:pdoc, :rubyforge_upload] + +desc "Publish the release files to RubyForge." +task :rubyforge_upload => :package do + files = %w(gem tgz).map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" } + + if RUBY_FORGE_PROJECT then + require 'net/http' + require 'open-uri' + + project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/" + project_data = open(project_uri) { |data| data.read } + group_id = project_data[/[?&]group_id=(\d+)/, 1] + raise "Couldn't get group id" unless group_id + + # This echos password to shell which is a bit sucky + if ENV["RUBY_FORGE_PASSWORD"] + password = ENV["RUBY_FORGE_PASSWORD"] + else + print "#{RUBY_FORGE_USER}@rubyforge.org's password: " + password = STDIN.gets.chomp + end + + login_response = Net::HTTP.start("rubyforge.org", 80) do |http| + data = [ + "login=1", + "form_loginname=#{RUBY_FORGE_USER}", + "form_pw=#{password}" + ].join("&") + http.post("/account/login.php", data) + end + + cookie = login_response["set-cookie"] + raise "Login failed" unless cookie + headers = { "Cookie" => cookie } + + release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}" + release_data = open(release_uri, headers) { |data| data.read } + package_id = release_data[/[?&]package_id=(\d+)/, 1] + raise "Couldn't get package id" unless package_id + + first_file = true + release_id = "" + + files.each do |filename| + basename = File.basename(filename) + file_ext = File.extname(filename) + file_data = File.open(filename, "rb") { |file| file.read } + + puts "Releasing #{basename}..." + + release_response = Net::HTTP.start("rubyforge.org", 80) do |http| + release_date = Time.now.strftime("%Y-%m-%d %H:%M") + type_map = { + ".zip" => "3000", + ".tgz" => "3110", + ".gz" => "3110", + ".gem" => "1400" + }; type_map.default = "9999" + type = type_map[file_ext] + boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor" + + query_hash = if first_file then + { + "group_id" => group_id, + "package_id" => package_id, + "release_name" => PKG_FILE_NAME, + "release_date" => release_date, + "type_id" => type, + "processor_id" => "8000", # Any + "release_notes" => "", + "release_changes" => "", + "preformatted" => "1", + "submit" => "1" + } + else + { + "group_id" => group_id, + "release_id" => release_id, + "package_id" => package_id, + "step2" => "1", + "type_id" => type, + "processor_id" => "8000", # Any + "submit" => "Add This File" + } + end + + query = "?" + query_hash.map do |(name, value)| + [name, URI.encode(value)].join("=") + end.join("&") + + data = [ + "--" + boundary, + "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"", + "Content-Type: application/octet-stream", + "Content-Transfer-Encoding: binary", + "", file_data, "" + ].join("\x0D\x0A") + + release_headers = headers.merge( + "Content-Type" => "multipart/form-data; boundary=#{boundary}" + ) + + target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php" + http.post(target + query, data, release_headers) + end + + if first_file then + release_id = release_response.body[/release_id=(\d+)/, 1] + raise("Couldn't get release id") unless release_id + end + + first_file = false + end + end end \ No newline at end of file diff --git a/lib/redmine.rb b/lib/redmine.rb index 7773e38..75c455a 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -126,7 +126,7 @@ Redmine::AccessControl.map do |map| map.permission :log_time, {:timelog => [:new, :create]}, :require => :loggedin map.permission :edit_time_entries, {:timelog => [:edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin - map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member + map.permission :manage_project_activities, {:projects => :settings, :project_enumerations => [:update, :destroy]}, :require => :member end map.project_module :news do |map| @@ -164,7 +164,7 @@ Redmine::AccessControl.map do |map| map.permission :browse_repository, {:repositories => [:show, :browse, :entry, :raw, :annotate, :changes, :diff, :stats, :graph]}, :read => true map.permission :commit_access, {} map.permission :manage_related_issues, {:repositories => [:add_related_issue, :remove_related_issue]} - map.permission :manage_repository, {:repositories => [:new, :create, :edit, :update, :committers, :destroy]}, :require => :member + map.permission :manage_repository, {:projects => :settings, :repositories => [:new, :create, :edit, :update, :committers, :destroy]}, :require => :member end map.project_module :boards do |map| @@ -174,7 +174,7 @@ Redmine::AccessControl.map do |map| map.permission :edit_own_messages, {:messages => :edit, :attachments => :upload}, :require => :loggedin map.permission :delete_messages, {:messages => :destroy}, :require => :member map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin - map.permission :manage_boards, {:boards => [:new, :create, :edit, :update, :destroy]}, :require => :member + map.permission :manage_boards, {:projects => :settings, :boards => [:new, :create, :edit, :update, :destroy]}, :require => :member end map.project_module :calendar do |map| diff --git a/lib/redmine/ciphering.rb b/lib/redmine/ciphering.rb index 61f309b..ad30e03 100644 --- a/lib/redmine/ciphering.rb +++ b/lib/redmine/ciphering.rb @@ -33,7 +33,7 @@ module Redmine c.iv = iv e = c.update(text.to_s) e << c.final - "aes-256-cbc:" + [e, iv].map {|v| Base64.encode64(v).strip}.join('--') + "aes-256-cbc:" + [e, iv].map {|v| Base64.strict_encode64(v)}.join('--') end end diff --git a/lib/redmine/field_format.rb b/lib/redmine/field_format.rb index fade7c2..e4bad28 100644 --- a/lib/redmine/field_format.rb +++ b/lib/redmine/field_format.rb @@ -486,7 +486,7 @@ module Redmine def validate_single_value(custom_field, value, customized=nil) errs = super - errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value.to_s =~ /^[+-]?\d+$/ + errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value.to_s.strip =~ /^[+-]?\d+$/ errs end @@ -736,6 +736,16 @@ module Redmine options end + def validate_custom_value(custom_value) + values = Array.wrap(custom_value.value).reject {|value| value.to_s == ''} + invalid_values = values - possible_custom_value_options(custom_value).map(&:last) + if invalid_values.any? + [::I18n.t('activerecord.errors.messages.inclusion')] + else + [] + end + end + def order_statement(custom_field) if target_class.respond_to?(:fields_for_order_statement) target_class.fields_for_order_statement(value_join_alias(custom_field)) diff --git a/lib/redmine/scm/adapters/filesystem_adapter.rb b/lib/redmine/scm/adapters/filesystem_adapter.rb index 1373a93..a40465a 100644 --- a/lib/redmine/scm/adapters/filesystem_adapter.rb +++ b/lib/redmine/scm/adapters/filesystem_adapter.rb @@ -81,7 +81,7 @@ module Redmine # below : list unreadable files, but dont link them. :path => utf_8_path, :kind => (File.directory?(t1) ? 'dir' : 'file'), - :size => (File.directory?(t1) ? nil : [File.size(t1)].pack('l').unpack('L').first), + :size => (File.directory?(t1) ? nil : File.size(t1)), :lastrev => Revision.new({:time => (File.mtime(t1)) }) }) diff --git a/lib/redmine/scm/adapters/git_adapter.rb b/lib/redmine/scm/adapters/git_adapter.rb index fcc77f3..481d613 100644 --- a/lib/redmine/scm/adapters/git_adapter.rb +++ b/lib/redmine/scm/adapters/git_adapter.rb @@ -83,6 +83,7 @@ module Redmine io.each_line do |line| branch_rev = line.match('\s*(\*?)\s*(.*?)\s*([0-9a-f]{40}).*$') bran = GitBranch.new(branch_rev[2]) + next unless branch_rev bran.revision = branch_rev[3] bran.scmid = branch_rev[3] bran.is_default = ( branch_rev[1] == '*' ) diff --git a/lib/redmine/scm/adapters/mercurial/redminehelper.py b/lib/redmine/scm/adapters/mercurial/redminehelper.py index 27c30da..8097d90 100644 --- a/lib/redmine/scm/adapters/mercurial/redminehelper.py +++ b/lib/redmine/scm/adapters/mercurial/redminehelper.py @@ -46,14 +46,22 @@ Output example of rhmanifest:: """ import re, time, cgi, urllib -from mercurial import cmdutil, commands, node, error, hg +from mercurial import cmdutil, commands, node, error, hg, registrar cmdtable = {} -command = cmdutil.command(cmdtable) +command = registrar.command(cmdtable) if hasattr(registrar, 'command') else cmdutil.command(cmdtable) _x = cgi.escape _u = lambda s: cgi.escape(urllib.quote(s)) +def _changectx(repo, rev): + if isinstance(rev, str): + rev = repo.lookup(rev) + if hasattr(repo, 'changectx'): + return repo.changectx(rev) + else: + return repo[rev] + def _tip(ui, repo): # see mercurial/commands.py:tip def tiprev(): @@ -61,7 +69,7 @@ def _tip(ui, repo): return len(repo) - 1 except TypeError: # Mercurial < 1.1 return repo.changelog.count() - 1 - tipctx = repo.changectx(tiprev()) + tipctx = _changectx(repo, tiprev()) ui.write('\n' % (tipctx.rev(), _x(node.hex(tipctx.node())))) @@ -94,13 +102,18 @@ def _branches(ui, repo): return repo.branchheads(branch, closed=False) except TypeError: # Mercurial < 1.2 return repo.branchheads(branch) + def lookup(rev, n): + try: + return repo.lookup(rev) + except RuntimeError: + return n for t, n, r in sorted(iterbranches(), key=lambda e: e[2], reverse=True): - if repo.lookup(r) in branchheads(t): + if lookup(r, n) in branchheads(t): ui.write('\n' % (r, _x(node.hex(n)), _x(t))) def _manifest(ui, repo, path, rev): - ctx = repo.changectx(rev) + ctx = _changectx(repo, rev) ui.write('\n' % (ctx.rev(), _u(path))) @@ -155,7 +168,7 @@ def rhdiff(ui, repo, *pats, **opts): """diff repository (or selected files)""" change = opts.pop('change', None) if change: # add -c option for Mercurial<1.1 - base = repo.changectx(change).parents()[0].rev() + base = _changectx(repo, change).parents()[0].rev() opts['rev'] = [str(base), change] opts['nodates'] = True return commands.diff(ui, repo, *map(urllib.unquote_plus, pats), **opts) diff --git a/lib/redmine/sort_criteria.rb b/lib/redmine/sort_criteria.rb index 3d440f0..d8fa3ee 100644 --- a/lib/redmine/sort_criteria.rb +++ b/lib/redmine/sort_criteria.rb @@ -87,6 +87,7 @@ module Redmine private def normalize! + self.reject! {|s| s.first.blank? } self.collect! {|s| s = Array(s); [s.first, (s.last == false || s.last.to_s == 'desc') ? 'desc' : 'asc']} self.slice!(3) self diff --git a/lib/redmine/thumbnail.rb b/lib/redmine/thumbnail.rb index 5779551..e4ae768 100644 --- a/lib/redmine/thumbnail.rb +++ b/lib/redmine/thumbnail.rb @@ -49,7 +49,12 @@ module Redmine def self.convert_available? return @convert_available if defined?(@convert_available) - @convert_available = system("#{shell_quote CONVERT_BIN} -version") rescue false + begin + `#{shell_quote CONVERT_BIN} -version` + @convert_available = $?.success? + rescue + @convert_available = false + end logger.warn("Imagemagick's convert binary (#{CONVERT_BIN}) not available") unless @convert_available @convert_available end diff --git a/lib/redmine/version.rb b/lib/redmine/version.rb index 6474e85..1d50076 100644 --- a/lib/redmine/version.rb +++ b/lib/redmine/version.rb @@ -4,7 +4,7 @@ module Redmine module VERSION #:nodoc: MAJOR = 3 MINOR = 4 - TINY = 4 + TINY = 13 # Branch values: # * official release: nil diff --git a/lib/redmine/views/builders/structure.rb b/lib/redmine/views/builders/structure.rb index 5e59f89..7c79279 100644 --- a/lib/redmine/views/builders/structure.rb +++ b/lib/redmine/views/builders/structure.rb @@ -48,7 +48,7 @@ module Redmine end def method_missing(sym, *args, &block) - if args.any? + if args.count > 0 if args.first.is_a?(Hash) if @struct.last.is_a?(Array) @struct.last << args.first unless block diff --git a/lib/redmine/wiki_formatting/markdown/formatter.rb b/lib/redmine/wiki_formatting/markdown/formatter.rb index 82e3b6c..c7611d9 100644 --- a/lib/redmine/wiki_formatting/markdown/formatter.rb +++ b/lib/redmine/wiki_formatting/markdown/formatter.rb @@ -94,15 +94,13 @@ module Redmine i = 0 l = 1 inside_pre = false - @text.split(/(^(?:.+\r?\n\r?(?:\=+|\-+)|#+.+|~~~.*)\s*$)/).each do |part| + @text.split(/(^(?:.+\r?\n\r?(?:\=+|\-+)|#+.+|(?:~~~|```).*)\s*$)/).each do |part| level = nil - if part =~ /\A~{3,}(\S+)?\s*$/ - if $1 - if !inside_pre - inside_pre = true - end - else - inside_pre = !inside_pre + if part =~ /\A(~{3,}|`{3,})(\S+)?\s*$/ + if !inside_pre + inside_pre = true + elsif !$2 + inside_pre = false end elsif inside_pre # nop @@ -142,7 +140,8 @@ module Redmine :strikethrough => true, :superscript => true, :no_intra_emphasis => true, - :footnotes => true + :footnotes => true, + :lax_spacing => true ) end end diff --git a/lib/redmine/wiki_formatting/textile/formatter.rb b/lib/redmine/wiki_formatting/textile/formatter.rb index 63851a2..eef2253 100644 --- a/lib/redmine/wiki_formatting/textile/formatter.rb +++ b/lib/redmine/wiki_formatting/textile/formatter.rb @@ -120,9 +120,10 @@ module Redmine ## replace
 content
             text.gsub!(//) do
               content = @pre_list[$1.to_i]
-              if content.match(/\s?(.+)/m)
-                language = $1
-                text = $2
+              # This regex must match any data produced by RedCloth3#rip_offtags
+              if content.match(/\s?(.*)/m)
+                language = $1 || $2
+                text = $3
                 if Redmine::SyntaxHighlighting.language_supported?(language)
                   content = "" +
                     Redmine::SyntaxHighlighting.highlight_by_language(text, language)
diff --git a/lib/redmine/wiki_formatting/textile/redcloth3.rb b/lib/redmine/wiki_formatting/textile/redcloth3.rb
index 7ed29bb..5716e7d 100644
--- a/lib/redmine/wiki_formatting/textile/redcloth3.rb
+++ b/lib/redmine/wiki_formatting/textile/redcloth3.rb
@@ -343,7 +343,7 @@ class RedCloth3 < String
     A_VLGN = /[\-^~]/
     C_CLAS = '(?:\([^")]+\))'
     C_LNGE = '(?:\[[a-z\-_]+\])'
-    C_STYL = '(?:\{[^"}]+\})'
+    C_STYL = '(?:\{[^{][^"}]+\})'
     S_CSPN = '(?:\\\\\d+)'
     S_RSPN = '(?:/\d+)'
     A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
@@ -1034,7 +1034,7 @@ class RedCloth3 < String
     def flush_left( text )
         indt = 0
         if text =~ /^ /
-            while text !~ /^ {#{indt}}\S/
+            while text !~ /^ {#{indt}}[^ ]/
                 indt += 1
             end unless text.empty?
             if indt.nonzero?
@@ -1049,7 +1049,7 @@ class RedCloth3 < String
     end
     
     OFFTAGS = /(code|pre|kbd|notextile)/
-    OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }\W|\Z)/mi
+    OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }\b>)|(<#{ OFFTAGS }\b[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }\b\W|\Z)/mi
     OFFTAG_OPEN = /<#{ OFFTAGS }/
     OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
     HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
@@ -1213,7 +1213,13 @@ class RedCloth3 < String
     
     ALLOWED_TAGS = %w(redpre pre code kbd notextile)
     def escape_html_tags(text)
-      text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "<#{$1}#{'>' unless $3.blank?}" }
+        text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) do |m|
+            if ALLOWED_TAGS.include?($2) && $3.present?
+                "<#{$1}#{$3}"
+            else
+                "<#{$1}#{'>' unless $3.blank?}"
+            end
+        end
     end
 end
 
diff --git a/public/help/ar/wiki_syntax_detailed_textile.html b/public/help/ar/wiki_syntax_detailed_textile.html
index 78074b4..28e86db 100644
--- a/public/help/ar/wiki_syntax_detailed_textile.html
+++ b/public/help/ar/wiki_syntax_detailed_textile.html
@@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 
 
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/az/wiki_syntax_detailed_textile.html b/public/help/az/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/az/wiki_syntax_detailed_textile.html +++ b/public/help/az/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/bg/wiki_syntax_detailed_textile.html b/public/help/bg/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/bg/wiki_syntax_detailed_textile.html +++ b/public/help/bg/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/bs/wiki_syntax_detailed_textile.html b/public/help/bs/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/bs/wiki_syntax_detailed_textile.html +++ b/public/help/bs/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/ca/wiki_syntax_detailed_textile.html b/public/help/ca/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/ca/wiki_syntax_detailed_textile.html +++ b/public/help/ca/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/cs/wiki_syntax_detailed_textile.html b/public/help/cs/wiki_syntax_detailed_textile.html index f34379b..1cca1f4 100644 --- a/public/help/cs/wiki_syntax_detailed_textile.html +++ b/public/help/cs/wiki_syntax_detailed_textile.html @@ -209,7 +209,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Nadpis 1. úrovně
+
 h2. Nadpis 2. úrovně
+
 h3. Nadpis 3. úrovně
 
diff --git a/public/help/da/wiki_syntax_detailed_textile.html b/public/help/da/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/da/wiki_syntax_detailed_textile.html +++ b/public/help/da/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/de/wiki_syntax_detailed_textile.html b/public/help/de/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/de/wiki_syntax_detailed_textile.html +++ b/public/help/de/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/el/wiki_syntax_detailed_textile.html b/public/help/el/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/el/wiki_syntax_detailed_textile.html +++ b/public/help/el/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/en-gb/wiki_syntax_detailed_textile.html b/public/help/en-gb/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/en-gb/wiki_syntax_detailed_textile.html +++ b/public/help/en-gb/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/en/wiki_syntax_detailed_textile.html b/public/help/en/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/en/wiki_syntax_detailed_textile.html +++ b/public/help/en/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/es-pa/wiki_syntax_detailed_textile.html b/public/help/es-pa/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/es-pa/wiki_syntax_detailed_textile.html +++ b/public/help/es-pa/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/es/wiki_syntax_detailed_textile.html b/public/help/es/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/es/wiki_syntax_detailed_textile.html +++ b/public/help/es/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/et/wiki_syntax_detailed_textile.html b/public/help/et/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/et/wiki_syntax_detailed_textile.html +++ b/public/help/et/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/eu/wiki_syntax_detailed_textile.html b/public/help/eu/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/eu/wiki_syntax_detailed_textile.html +++ b/public/help/eu/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/fa/wiki_syntax_detailed_textile.html b/public/help/fa/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/fa/wiki_syntax_detailed_textile.html +++ b/public/help/fa/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/fi/wiki_syntax_detailed_textile.html b/public/help/fi/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/fi/wiki_syntax_detailed_textile.html +++ b/public/help/fi/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/fr/wiki_syntax_detailed_textile.html b/public/help/fr/wiki_syntax_detailed_textile.html index 1ee0d0b..9bafe46 100644 --- a/public/help/fr/wiki_syntax_detailed_textile.html +++ b/public/help/fr/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Titre
+
 h2. Sous-titre
+
 h3. Sous-sous-titre
 
diff --git a/public/help/gl/wiki_syntax_detailed_textile.html b/public/help/gl/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/gl/wiki_syntax_detailed_textile.html +++ b/public/help/gl/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/he/wiki_syntax_detailed_textile.html b/public/help/he/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/he/wiki_syntax_detailed_textile.html +++ b/public/help/he/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/hr/wiki_syntax_detailed_textile.html b/public/help/hr/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/hr/wiki_syntax_detailed_textile.html +++ b/public/help/hr/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/hu/wiki_syntax_detailed_textile.html b/public/help/hu/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/hu/wiki_syntax_detailed_textile.html +++ b/public/help/hu/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/id/wiki_syntax_detailed_textile.html b/public/help/id/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/id/wiki_syntax_detailed_textile.html +++ b/public/help/id/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/it/wiki_syntax_detailed_textile.html b/public/help/it/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/it/wiki_syntax_detailed_textile.html +++ b/public/help/it/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/ja/wiki_syntax_detailed_textile.html b/public/help/ja/wiki_syntax_detailed_textile.html index cb5f07d..3648270 100644 --- a/public/help/ja/wiki_syntax_detailed_textile.html +++ b/public/help/ja/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/ko/wiki_syntax_detailed_textile.html b/public/help/ko/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/ko/wiki_syntax_detailed_textile.html +++ b/public/help/ko/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/lt/wiki_syntax_detailed_textile.html b/public/help/lt/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/lt/wiki_syntax_detailed_textile.html +++ b/public/help/lt/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/lv/wiki_syntax_detailed_textile.html b/public/help/lv/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/lv/wiki_syntax_detailed_textile.html +++ b/public/help/lv/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/mk/wiki_syntax_detailed_textile.html b/public/help/mk/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/mk/wiki_syntax_detailed_textile.html +++ b/public/help/mk/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/mn/wiki_syntax_detailed_textile.html b/public/help/mn/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/mn/wiki_syntax_detailed_textile.html +++ b/public/help/mn/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/nl/wiki_syntax_detailed_textile.html b/public/help/nl/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/nl/wiki_syntax_detailed_textile.html +++ b/public/help/nl/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/no/wiki_syntax_detailed_textile.html b/public/help/no/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/no/wiki_syntax_detailed_textile.html +++ b/public/help/no/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/pl/wiki_syntax_detailed_textile.html b/public/help/pl/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/pl/wiki_syntax_detailed_textile.html +++ b/public/help/pl/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/pt-br/wiki_syntax_detailed_textile.html b/public/help/pt-br/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/pt-br/wiki_syntax_detailed_textile.html +++ b/public/help/pt-br/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/pt-br/wiki_syntax_markdown.html b/public/help/pt-br/wiki_syntax_markdown.html index 39f8a3c..dc2b418 100644 --- a/public/help/pt-br/wiki_syntax_markdown.html +++ b/public/help/pt-br/wiki_syntax_markdown.html @@ -2,51 +2,51 @@ -Wiki formatting +Formatação Wiki -

Wiki Syntax Quick Reference (Markdown)

+

Sintaxe Wiki - Referência Rápida (Markdown)

- - - - - + + + + + - + - - - - + + + + - - - - + + + + - + - - - + + + - + @@ -63,7 +63,7 @@
Font Styles
Strong**Strong**Strong
Italic*Italic*Italic
Deleted~~Deleted~~Deleted
Inline Code`Inline Code`Inline Code
Estilos de Fonte
Strong**Negrito**Negrito
Italic*Itálico*Itálico
Deleted~~Tachado~~Tachado
Inline Code`Código Inline`Código Inline
Preformatted text~~~
 lines
 of code
~~~
- lines
- of code
+ linhas
+ de código
 
Lists
Listas
Unordered list* Item 1
  * Sub
* Item 2
  • Item 1
    • Sub
  • Item 2
Ordered list1. Item 1
   1. Sub
2. Item 2
  1. Item 1
    1. Sub
  2. Item 2
Headings
Heading 1# Title 1

Title 1

Heading 2## Title 2

Title 2

Heading 3### Title 3

Title 3

Cabeçalhos
Heading 1# Título 1

Título 1

Heading 2## Título 2

Título 2

Heading 3### Título 3

Título 3

Links
http://foo.barhttp://foo.bar
[Foo](http://foo.bar)Foo
Redmine links
Link to a Wiki page[[Wiki page]]Wiki page
Issue #12Issue #12
Revision r43Revision r43
Links do Redmine
Link to a Wiki page[[Página Wiki]]Página Wiki
Tarefa #12Tarefa #12
Revisão r43Revisão r43
commit:f30e13e43f30e13e4
source:some/filesource:some/file
source:algum/arquivosource:algum/arquivo
Inline images
Image![](image_url)
![](attached_image)
Imagens inline
Image![](url_da_imagem)
![](imagem_anexada)
Tables
Tabelas
| A | B | C |
|---|---|---|
| A | B | C |
| D | E | F |
-

More Information

+

Mais Informações

diff --git a/public/help/pt-br/wiki_syntax_textile.html b/public/help/pt-br/wiki_syntax_textile.html index 6f544d2..71732a0 100644 --- a/public/help/pt-br/wiki_syntax_textile.html +++ b/public/help/pt-br/wiki_syntax_textile.html @@ -2,53 +2,53 @@ -Wiki formatting +Formatação Wiki -

Wiki Syntax Quick Reference

+

Sintaxe Wiki - Referência Rápida

- - - - - - - - + + + + + + + - + - - - - + + + + - - - - + + + + - + - - - + + + - + @@ -66,7 +66,7 @@
Font Styles
Strong*Strong*Strong
Italic_Italic_Italic
Underline+Underline+Underline
Deleted-Deleted-Deleted
??Quote??Quote
Inline Code@Inline Code@Inline Code
Preformatted text<pre>
 lines
 of code
</pre>
+
Estilos de Fonte
Strong*Negrito*Negrito
Italic_Itálico_Itálico
Underline+Sublinhado+Sublinhado
Deleted-Tachado-Tachado
??Citação??Citação
Inline Code@Código Inline@Código Inline
Preformatted text<pre>
 linhas
 de código
</pre>
- lines
- of code
+ linhas
+ de código
 
Lists
Listas
Unordered list* Item 1
** Sub
* Item 2
  • Item 1
    • Sub
  • Item 2
Ordered list# Item 1
## Sub
# Item 2
  1. Item 1
    1. Sub
  2. Item 2
Headings
Heading 1h1. Title 1

Title 1

Heading 2h2. Title 2

Title 2

Heading 3h3. Title 3

Title 3

Cabeçalhos
Heading 1h1. Título 1

Título 1

Heading 2h2. Título 2

Título 2

Heading 3h3. Título 3

Título 3

Links
http://foo.barhttp://foo.bar
"Foo":http://foo.barFoo
Redmine links
Link to a Wiki page[[Wiki page]]Wiki page
Issue #12Issue #12
Revision r43Revision r43
Links do Redmine
Link to a Wiki page[[Página Wiki]]Página Wiki
Tarefa #12Tarefa #12
Revisão r43Revisão r43
commit:f30e13e43f30e13e4
source:some/filesource:some/file
source:algum/arquivosource:algum/arquivo
Inline images
Image!image_url!
!attached_image!
Imagens inline
Image!url_da_imagem!
!imagem_anexada!
Tables
Tabelas
|_. A |_. B |_. C |
| A | B | C |
|/2. row span | B | C |
|\2. col span |
-

More Information

+

Mais Informações

diff --git a/public/help/pt/wiki_syntax_detailed_textile.html b/public/help/pt/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/pt/wiki_syntax_detailed_textile.html +++ b/public/help/pt/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/ro/wiki_syntax_detailed_textile.html b/public/help/ro/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/ro/wiki_syntax_detailed_textile.html +++ b/public/help/ro/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/ru/wiki_syntax_detailed_textile.html b/public/help/ru/wiki_syntax_detailed_textile.html index 32cdfad..f706ddf 100644 --- a/public/help/ru/wiki_syntax_detailed_textile.html +++ b/public/help/ru/wiki_syntax_detailed_textile.html @@ -230,7 +230,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Заголовок
+
 h2. Подзаголовок
+
 h3. Подзаголовок подзаголовка
 
diff --git a/public/help/sk/wiki_syntax_detailed_textile.html b/public/help/sk/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/sk/wiki_syntax_detailed_textile.html +++ b/public/help/sk/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/sl/wiki_syntax_detailed_textile.html b/public/help/sl/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/sl/wiki_syntax_detailed_textile.html +++ b/public/help/sl/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/sq/wiki_syntax_detailed_textile.html b/public/help/sq/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/sq/wiki_syntax_detailed_textile.html +++ b/public/help/sq/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/sr-yu/wiki_syntax_detailed_textile.html b/public/help/sr-yu/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/sr-yu/wiki_syntax_detailed_textile.html +++ b/public/help/sr-yu/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/sr/wiki_syntax_detailed_textile.html b/public/help/sr/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/sr/wiki_syntax_detailed_textile.html +++ b/public/help/sr/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/sv/wiki_syntax_detailed_textile.html b/public/help/sv/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/sv/wiki_syntax_detailed_textile.html +++ b/public/help/sv/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/th/wiki_syntax_detailed_textile.html b/public/help/th/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/th/wiki_syntax_detailed_textile.html +++ b/public/help/th/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/tr/wiki_syntax_detailed_textile.html b/public/help/tr/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/tr/wiki_syntax_detailed_textile.html +++ b/public/help/tr/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/uk/wiki_syntax_detailed_textile.html b/public/help/uk/wiki_syntax_detailed_textile.html index 1331bad..9756d6f 100644 --- a/public/help/uk/wiki_syntax_detailed_textile.html +++ b/public/help/uk/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Заголовок
+
 h2. Підзаголовок
+
 h3. Підзаголовок
 
diff --git a/public/help/vi/wiki_syntax_detailed_textile.html b/public/help/vi/wiki_syntax_detailed_textile.html index 78074b4..28e86db 100644 --- a/public/help/vi/wiki_syntax_detailed_textile.html +++ b/public/help/vi/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. Heading
+
 h2. Subheading
+
 h3. Subsubheading
 
diff --git a/public/help/wiki_syntax.css b/public/help/wiki_syntax.css index 4430ea0..8821f4b 100644 --- a/public/help/wiki_syntax.css +++ b/public/help/wiki_syntax.css @@ -1,5 +1,6 @@ h1 { font-family: Verdana, sans-serif; font-size: 14px; text-align: center; color: #444; } body { font-family: Verdana, sans-serif; font-size: 12px; color: #444; } +pre, code {font-family: Consolas, Menlo, "Liberation Mono", Courier, monospace; } table th { padding-top: 1em; } table td { vertical-align: top; background-color: #f5f5f5; height: 2em; vertical-align: middle;} table td code { font-size: 1.2em; } diff --git a/public/help/wiki_syntax_detailed.css b/public/help/wiki_syntax_detailed.css index 94fc9f8..5ec1dc3 100644 --- a/public/help/wiki_syntax_detailed.css +++ b/public/help/wiki_syntax_detailed.css @@ -1,6 +1,6 @@ body { font:80% Verdana,Tahoma,Arial,sans-serif; } h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; } -pre, code { font-size:120%; } +pre, code { font-size:120%; font-family: Consolas, Menlo, "Liberation Mono", Courier, monospace; } pre code { font-size:100%; } pre { margin: 1em 1em 1em 1.6em; diff --git a/public/help/zh-tw/wiki_syntax_detailed_textile.html b/public/help/zh-tw/wiki_syntax_detailed_textile.html index 103d46f..6afed51 100644 --- a/public/help/zh-tw/wiki_syntax_detailed_textile.html +++ b/public/help/zh-tw/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. 標題
+
 h2. 次標題
+
 h3. 次次標題
 
diff --git a/public/help/zh/wiki_syntax_detailed_textile.html b/public/help/zh/wiki_syntax_detailed_textile.html index 9d32657..1d502b1 100644 --- a/public/help/zh/wiki_syntax_detailed_textile.html +++ b/public/help/zh/wiki_syntax_detailed_textile.html @@ -200,7 +200,9 @@ http://www.redmine.org, someone@foo.bar
 h1. 一级标题
+
 h2. 二级标题
+
 h3. 三级标题
 
diff --git a/public/images/external.png b/public/images/external.png index fb4f1f4..77c2028 100644 Binary files a/public/images/external.png and b/public/images/external.png differ diff --git a/public/javascripts/application.js b/public/javascripts/application.js index b6352fd..4e1b40a 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -1,6 +1,13 @@ /* Redmine - project management software Copyright (C) 2006-2017 Jean-Philippe Lang */ +/* Fix for CVE-2015-9251, to be removed with JQuery >= 3.0 */ +$.ajaxPrefilter(function (s) { + if (s.crossDomain) { + s.contents.script = false; + } +}); + function checkAll(id, checked) { $('#'+id).find('input[type=checkbox]:enabled').prop('checked', checked); } diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-gl.js b/public/javascripts/jstoolbar/lang/jstoolbar-gl.js index e815cfa..aa5d222 100644 --- a/public/javascripts/jstoolbar/lang/jstoolbar-gl.js +++ b/public/javascripts/jstoolbar/lang/jstoolbar-gl.js @@ -7,7 +7,7 @@ jsToolBar.strings['Code'] = 'Código fonte'; jsToolBar.strings['Heading 1'] = 'Encabezado 1'; jsToolBar.strings['Heading 2'] = 'Encabezado 2'; jsToolBar.strings['Heading 3'] = 'Encabezado 3'; -jsToolBar.strings['Highlighted code'] = 'Highlighted code'; +jsToolBar.strings['Highlighted code'] = 'Código resaltado'; jsToolBar.strings['Unordered list'] = 'Lista sen ordenar'; jsToolBar.strings['Ordered list'] = 'Lista ordenada'; jsToolBar.strings['Quote'] = 'Citar'; diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-pt-br.js b/public/javascripts/jstoolbar/lang/jstoolbar-pt-br.js index a31ef62..70b4114 100644 --- a/public/javascripts/jstoolbar/lang/jstoolbar-pt-br.js +++ b/public/javascripts/jstoolbar/lang/jstoolbar-pt-br.js @@ -12,8 +12,8 @@ jsToolBar.strings['Heading 3'] = 'Cabeçalho 3'; jsToolBar.strings['Highlighted code'] = 'Highlighted code'; jsToolBar.strings['Unordered list'] = 'Lista não ordenada'; jsToolBar.strings['Ordered list'] = 'Lista ordenada'; -jsToolBar.strings['Quote'] = 'Quote'; -jsToolBar.strings['Unquote'] = 'Remove Quote'; +jsToolBar.strings['Quote'] = 'Identar'; +jsToolBar.strings['Unquote'] = 'Remover identação'; jsToolBar.strings['Preformatted text'] = 'Texto pré-formatado'; jsToolBar.strings['Wiki link'] = 'Link para uma página Wiki'; jsToolBar.strings['Image'] = 'Imagem'; diff --git a/public/javascripts/responsive.js b/public/javascripts/responsive.js index 4901434..ec16111 100644 --- a/public/javascripts/responsive.js +++ b/public/javascripts/responsive.js @@ -46,8 +46,8 @@ function setupFlyout() { /* only init mobile menu, if it hasn't been done yet */ if(!mobileInit) { - $('#wrapper-main-menu > ul').detach().appendTo('.js-project-menu'); - $('#wrapper-top-menu > ul:not(.social-menu)').detach().appendTo('.js-general-menu'); + $('#main-menu > ul').detach().appendTo('.js-project-menu'); + $('#top-menu > ul').detach().appendTo('.js-general-menu'); $('#sidebar > *').detach().appendTo('.js-sidebar'); $('#account > ul').detach().appendTo('.js-profile-menu'); @@ -59,8 +59,8 @@ function setupFlyout() { var _initDesktopMenu = function() { if(!desktopInit) { - $('.js-project-menu > ul').detach().appendTo('#wrapper-main-menu'); - $('.js-general-menu > ul').detach().appendTo('#wrapper-top-menu'); + $('.js-project-menu > ul').detach().appendTo('#main-menu'); + $('.js-general-menu > ul').detach().appendTo('#top-menu'); $('.js-sidebar > *').detach().appendTo('#sidebar'); $('.js-profile-menu > ul').detach().appendTo('#account'); diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 3bbc67d..ee5dc27 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -140,8 +140,6 @@ a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repea a#toggle-completed-versions {color:#999;} -a.toggle-checkboxes { margin-left: 5px; padding-left: 12px; background: url(../images/toggle_check.png) no-repeat 0% 50%; } - /***** Dropdown *****/ .drdn {position:relative;} .drdn-trigger { @@ -344,13 +342,15 @@ tr.group a.toggle-all { color: #aaa; font-size: 80%; display:none; float:right; tr.group:hover a.toggle-all { display:inline;} a.toggle-all:hover {text-decoration:none;} -table.list tbody tr:hover { background-color:#ffffdd; } table.list tbody tr.group:hover { background-color:inherit; } + table td {padding:2px;} table p {margin:0;} table.list:not(.odd-even) tbody tr:nth-child(odd), .odd, #issue-changesets div.changeset:nth-child(odd) { background-color:#f6f7f8; } table.list:not(.odd-even) tbody tr:nth-child(even), .even, #issue-changesets div.changeset:nth-child(even) { background-color: #fff; } +table.list:not(.odd-even) tbody tr:nth-child(odd):hover, .odd:hover, #issue-changesets div.changeset:nth-child(odd):hover, +table.list:not(.odd-even) tbody tr:nth-child(even):hover, .even:hover, #issue-changesets div.changeset:nth-child(even):hover { background-color:#ffffdd; } tr.builtin td.name {font-style:italic;} @@ -448,8 +448,8 @@ div.issue {background:#ffffdd; padding:6px; margin-bottom:6px; border: 1px solid p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;} p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; } p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; } -.ltr {direction:ltr !important; unicode-bidi:bidi-override;} -.rtl {direction:rtl !important; unicode-bidi:bidi-override;} +.wiki-class-ltr {direction:ltr !important;} +.wiki-class-rtl {direction:rtl !important;} div.issue div.subject div div { padding-left: 16px; } div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;} @@ -548,7 +548,8 @@ select#issue_done_ratio { width: 95px; } ul.projects {margin:0; padding-left:1em;} ul.projects ul {padding-left:1.6em;} ul.projects.root {margin:0; padding:0;} -ul.projects li {list-style-type:none;} +ul.projects li.root, ul.projects li.child {list-style-type:none;} +ul.projects div.description li {list-style-type:initial;} #projects-index { column-count: auto; @@ -560,10 +561,28 @@ ul.projects li {list-style-type:none;} -moz-column-width: 400px; -moz-column-gap : 0.5rem; } -#projects-index ul.projects li.root>ul.projects { border-left: 3px solid #e0e0e0; padding-left:1em;} -#projects-index ul.projects li.root {margin-bottom: 1em;} +#projects-index li.root ul.projects { border-left: 3px solid #e0e0e0; padding-left:1em;} +#projects-index ul.projects li.root { + margin-bottom: 1em; + padding: 15px 20px; + border: 1px solid #d7d7d7; + border-radius: 3px; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + break-inside: avoid-column; + -webkit-break-inside: avoid-column; + -moz-break-inside: avoid-column; + page-break-inside:avoid; + -webkit-column-break-inside: avoid; + -moz-column-break-inside: avoid; + width: 100%; +} #projects-index ul.projects li.child {margin-top: 1em;} #projects-index ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; } +#projects-index ul.projects div.description { + padding-top: 0.5em; +} #projects-index a.icon-fav {padding-left:0; padding-right:20px; background-position:98% 50%;} #notified-projects>ul, #tracker_project_ids>ul, #custom_field_project_ids>ul {max-height:250px; overflow-y:auto;} @@ -701,7 +720,7 @@ label.no-css { } input#time_entry_comments { width: 90%;} -#preview fieldset {margin-top: 1em; background: url(../images/draft.png)} +fieldset.preview {margin-top: 1em; min-width: inherit; background: url(../images/draft.png)} .tabular.settings p{ padding-left: 300px; } .tabular.settings label{ margin-left: -300px; width: 295px; } @@ -1107,7 +1126,7 @@ div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;} a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; } a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; } -h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; } +h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor, h4:hover a.wiki-anchor, h5:hover a.wiki-anchor, h6:hover a.wiki-anchor { display: inline; color: #ddd; } div.wiki img {vertical-align:middle; max-width:100%;} @@ -1290,7 +1309,7 @@ div.wiki img {vertical-align:middle; max-width:100%;} .icon-file.text-x-c { background-image: url(../images/files/c.png); } .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); } .icon-file.text-x-java { background-image: url(../images/files/java.png); } -.icon-file.text-x-javascript { background-image: url(../images/files/js.png); } +.icon-file.application-javascript { background-image: url(../images/files/js.png); } .icon-file.text-x-php { background-image: url(../images/files/php.png); } .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); } .icon-file.text-xml { background-image: url(../images/files/xml.png); } @@ -1302,7 +1321,7 @@ div.wiki img {vertical-align:middle; max-width:100%;} .icon-file.image-tiff { background-image: url(../images/files/image.png); } .icon-file.application-pdf { background-image: url(../images/files/pdf.png); } .icon-file.application-zip { background-image: url(../images/files/zip.png); } -.icon-file.application-x-gzip { background-image: url(../images/files/zip.png); } +.icon-file.application-gzip { background-image: url(../images/files/zip.png); } .sort-handle { width:16px; height:16px; background:url(../images/reorder.png) no-repeat 0 50%; cursor:move; } .sort-handle.ajax-loading { background-image: url(../images/loading.gif); } diff --git a/public/stylesheets/responsive.css b/public/stylesheets/responsive.css index 798b3a2..cf31825 100644 --- a/public/stylesheets/responsive.css +++ b/public/stylesheets/responsive.css @@ -816,19 +816,19 @@ } /* attachment upload form */ - #attachments_fields span { + .attachments_fields span { position: relative; clear: both; margin-bottom: 1em; white-space: normal; } - #attachments_fields span a.remove-upload { + .attachments_fields span a.remove-upload { position: absolute; top: 0; right: 0; } - #attachments_fields input.description { + .attachments_fields input.description { margin-left: 0; width: 100%; } diff --git a/public/stylesheets/rtl.css b/public/stylesheets/rtl.css index dc48502..36a1ca3 100644 --- a/public/stylesheets/rtl.css +++ b/public/stylesheets/rtl.css @@ -207,12 +207,12 @@ fieldset#notified_events .parent {padding-left:0px; padding-right:20px; } .check_box_group {padding:2px 2px 4px 4px;} .check_box_group label {margin-right: 0px !important; text-align: right;} -#attachments_fields input.description {margin-left:0px; margin-right:4px;} +.attachments_fields input.description {margin-left:0px; margin-right:4px;} -#attachments_fields input.filename {background:url(../images/attachment.png) no-repeat right 1px top 50%; padding-left:0px; padding-right:18px;} -#attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat right top 50%;} -#attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat right top 50%;} -#attachments_fields div.ui-progressbar {margin: 2px 8px -5px 0;} +.attachments_fields input.filename {background:url(../images/attachment.png) no-repeat right 1px top 50%; padding-left:0px; padding-right:18px;} +.attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat right top 50%;} +.attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat right top 50%;} +.attachments_fields div.ui-progressbar {margin: 2px 8px -5px 0;} a.remove-upload {background: url(../images/delete.png) no-repeat right 1px top 50%; padding-left:0px; padding-right:16px;} diff --git a/test/fixtures/configuration/default.yml.example b/test/fixtures/configuration/default.yml.example new file mode 100644 index 0000000..89a60f1 --- /dev/null +++ b/test/fixtures/configuration/default.yml.example @@ -0,0 +1,8 @@ +default: + somesetting: foo + +production: + +development: + +test: diff --git a/test/fixtures/configuration/empty.yml.example b/test/fixtures/configuration/empty.yml.example new file mode 100644 index 0000000..f280431 --- /dev/null +++ b/test/fixtures/configuration/empty.yml.example @@ -0,0 +1,7 @@ +default: + +production: + +development: + +test: diff --git a/test/fixtures/configuration/no_default.yml.example b/test/fixtures/configuration/no_default.yml.example new file mode 100644 index 0000000..161224a --- /dev/null +++ b/test/fixtures/configuration/no_default.yml.example @@ -0,0 +1,8 @@ +default: + +production: + +development: + +test: + somesetting: foo diff --git a/test/fixtures/configuration/overrides.yml.example b/test/fixtures/configuration/overrides.yml.example new file mode 100644 index 0000000..d9be392 --- /dev/null +++ b/test/fixtures/configuration/overrides.yml.example @@ -0,0 +1,9 @@ +default: + somesetting: foo + +production: + +development: + +test: + somesetting: bar diff --git a/test/fixtures/files/2006/07/060719210727_changeset_utf8.diff b/test/fixtures/files/2006/07/060719210727_changeset_utf8.diff index e594f20..58c591f 100644 --- a/test/fixtures/files/2006/07/060719210727_changeset_utf8.diff +++ b/test/fixtures/files/2006/07/060719210727_changeset_utf8.diff @@ -1,13 +1,13 @@ -Index: trunk/app/controllers/issues_controller.rb -=================================================================== ---- trunk/app/controllers/issues_controller.rb (révision 1483) -+++ trunk/app/controllers/issues_controller.rb (révision 1484) -@@ -149,7 +149,7 @@ - attach_files(@issue, params[:attachments]) - flash[:notice] = 'Demande créée avec succès' - Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') -- redirect_to :controller => 'issues', :action => 'show', :id => @issue, :project_id => @project -+ redirect_to :controller => 'issues', :action => 'show', :id => @issue - return - end - end +Index: trunk/app/controllers/issues_controller.rb +=================================================================== +--- trunk/app/controllers/issues_controller.rb (révision 1483) ++++ trunk/app/controllers/issues_controller.rb (révision 1484) +@@ -149,7 +149,7 @@ + attach_files(@issue, params[:attachments]) + flash[:notice] = 'Demande créée avec succès' + Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') +- redirect_to :controller => 'issues', :action => 'show', :id => @issue, :project_id => @project ++ redirect_to :controller => 'issues', :action => 'show', :id => @issue + return + end + end diff --git a/test/fixtures/files/import_issues_utf8_with_bom.csv b/test/fixtures/files/import_issues_utf8_with_bom.csv new file mode 100644 index 0000000..1ab78ac --- /dev/null +++ b/test/fixtures/files/import_issues_utf8_with_bom.csv @@ -0,0 +1,4 @@ +"priority";subject;description;start_date;due_date;parent;private;progress;custom;version;category;user;estimated_hours;tracker;status +High;First;First description;2015-07-08;2015-08-25;;no;;PostgreSQL;;New category;dlopper;1;bug;new +Normal;Child 1;Child description;;;1;yes;10;MySQL;2.0;New category;;2;feature request;new +Normal;Child of existing issue;Child description;;;#2;no;20;;2.1;Printing;;3;bug;assigned diff --git a/test/fixtures/files/testfile.txt b/test/fixtures/files/testfile.txt index 84f601e..7463b5f 100644 --- a/test/fixtures/files/testfile.txt +++ b/test/fixtures/files/testfile.txt @@ -1,2 +1,2 @@ -this is a text file for upload tests -with multiple lines +this is a text file for upload tests +with multiple lines diff --git a/test/functional/auto_completes_controller_test.rb b/test/functional/auto_completes_controller_test.rb index f4f8b4f..4be1881 100644 --- a/test/functional/auto_completes_controller_test.rb +++ b/test/functional/auto_completes_controller_test.rb @@ -138,4 +138,14 @@ class AutoCompletesControllerTest < Redmine::ControllerTest assert_include "issue", response.body assert_not_include "Bug #12: Closed issue on a locked version", response.body end + + def test_auto_complete_should_return_json_content_type_response + get :issues, :params => { + :project_id => 'subproject1', + :q => '#13' + } + + assert_response :success + assert_include 'application/json', response.headers['Content-Type'] + end end diff --git a/test/functional/enumerations_controller_test.rb b/test/functional/enumerations_controller_test.rb index 1e0b75f..8e411c5 100644 --- a/test/functional/enumerations_controller_test.rb +++ b/test/functional/enumerations_controller_test.rb @@ -67,6 +67,21 @@ class EnumerationsControllerTest < Redmine::ControllerTest assert_not_nil e end + def test_create_with_custom_field_values + custom_field = TimeEntryActivityCustomField.generate! + assert_difference 'TimeEntryActivity.count' do + post :create, :params => { + :enumeration => { + :type => 'TimeEntryActivity', + :name => 'Sample', + :custom_field_values => {custom_field.id.to_s => "sample"} + } + } + end + assert_redirected_to '/enumerations' + assert_equal "sample", Enumeration.find_by(:name => 'Sample').custom_field_values.last.value + end + def test_create_with_failure assert_no_difference 'IssuePriority.count' do post :create, :params => { @@ -136,6 +151,20 @@ class EnumerationsControllerTest < Redmine::ControllerTest assert_equal 1, Enumeration.find(2).position end + def test_update_custom_field_values + custom_field = TimeEntryActivityCustomField.generate! + enumeration = Enumeration.find(9) + assert_nil enumeration.custom_field_values.last.value + put :update, :params => { + :id => enumeration.id, + :enumeration => { + :custom_field_values => {custom_field.id.to_s => "sample"} + } + } + assert_response 302 + assert_equal "sample", enumeration.reload.custom_field_values.last.value + end + def test_destroy_enumeration_not_in_use assert_difference 'IssuePriority.count', -1 do delete :destroy, :params => { diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 40055cb..14c0b5a 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -773,6 +773,25 @@ class IssuesControllerTest < Redmine::ControllerTest end end + def test_index_csv_should_not_change_selected_columns + get :index, :params => { + :set_filter => 1, + :c => ["subject", "due_date"], + :project_id => "ecookbook" + } + assert_response :success + assert_equal [:subject, :due_date], session[:issue_query][:column_names] + + get :index, :params => { + :set_filter => 1, + :c =>["all_inline"], + :project_id => "ecookbook", + :format => 'csv' + } + assert_response :success + assert_equal [:subject, :due_date], session[:issue_query][:column_names] + end + def test_index_pdf ["en", "zh", "zh-TW", "ja", "ko"].each do |lang| with_settings :default_language => lang do @@ -1754,6 +1773,21 @@ class IssuesControllerTest < Redmine::ControllerTest assert_response :success end + def test_show_should_format_related_issues_dates + with_settings :date_format => '%d/%m/%Y' do + issue = Issue.generate!(:start_date => '2018-11-29', :due_date => '2018-12-01') + IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => issue, :relation_type => 'relates') + + get :show, :params => { + :id => 1 + } + assert_response :success + + assert_select '#relations td.start_date', :text => '29/11/2018' + assert_select '#relations td.due_date', :text => '01/12/2018' + end + end + def test_show_should_not_disclose_relations_to_invisible_issues Setting.cross_project_issue_relations = '1' IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates') @@ -2093,6 +2127,25 @@ class IssuesControllerTest < Redmine::ControllerTest assert_select "div.description ~ div.attribute.cf_#{field.id} div.value", :text => 'This is a long text' end + def test_show_custom_fields_with_full_text_formatting_should_be_rendered_using_wiki_class + half_field = IssueCustomField.create!(:name => 'Half width field', :field_format => 'text', :tracker_ids => [1], + :is_for_all => true, :text_formatting => 'full') + full_field = IssueCustomField.create!(:name => 'Full width field', :field_format => 'text', :full_width_layout => '1', + :tracker_ids => [1], :is_for_all => true, :text_formatting => 'full') + + issue = Issue.find(1) + issue.custom_field_values = {full_field.id => 'This is a long text', half_field.id => 'This is a short text'} + issue.save! + + get :show, :params => { + :id => 1 + } + assert_response :success + + assert_select "div.attribute.cf_#{half_field.id} div.value div.wiki", 1 + assert_select "div.attribute.cf_#{full_field.id} div.value div.wiki", 1 + end + def test_show_with_multi_user_custom_field field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true, :tracker_ids => [1], :is_for_all => true) @@ -3863,6 +3916,29 @@ class IssuesControllerTest < Redmine::ControllerTest assert_select 'input[type=hidden][name=?][value=?]', 'issue[watcher_user_ids][]', '', 1 end + def test_new_as_copy_should_not_propose_locked_watchers + @request.session[:user_id] = 2 + + issue = Issue.find(1) + user = User.generate! + user2 = User.generate! + + Watcher.create!(:watchable => issue, :user => user) + Watcher.create!(:watchable => issue, :user => user2) + + user2.status = User::STATUS_LOCKED + user2.save! + get :new, :params => { + :project_id => 1, + :copy_from => 1 + } + + assert_select 'input[type=checkbox][name=?][checked=checked]', 'issue[watcher_user_ids][]', 1 + assert_select 'input[type=checkbox][name=?][checked=checked][value=?]', 'issue[watcher_user_ids][]', user.id.to_s + assert_select 'input[type=checkbox][name=?][checked=checked][value=?]', 'issue[watcher_user_ids][]', user2.id.to_s, 0 + assert_select 'input[type=hidden][name=?][value=?]', 'issue[watcher_user_ids][]', '', 1 + end + def test_new_as_copy_with_invalid_issue_should_respond_with_404 @request.session[:user_id] = 2 get :new, :params => { @@ -6110,7 +6186,7 @@ class IssuesControllerTest < Redmine::ControllerTest end end - def test_bulk_copy_should_allow_copying_the_subtasks + test "bulk copy should allow copying the subtasks" do issue = Issue.generate_with_descendants! count = issue.descendants.count @request.session[:user_id] = 2 @@ -6130,10 +6206,9 @@ class IssuesControllerTest < Redmine::ControllerTest assert_equal count, copy.descendants.count end - def test_bulk_copy_should_allow_copying_the_subtasks + test "issue bulk copy copy watcher" do Watcher.create!(:watchable => Issue.find(1), :user => User.find(3)) @request.session[:user_id] = 2 - assert_difference 'Issue.count' do post :bulk_update, :params => { :ids => [1], @@ -6141,7 +6216,6 @@ class IssuesControllerTest < Redmine::ControllerTest :copy_watchers => '1', :issue => { :project_id => '' - } } end @@ -6212,6 +6286,27 @@ class IssuesControllerTest < Redmine::ControllerTest def test_destroy_issues_with_time_entries_should_show_the_reassign_form @request.session[:user_id] = 2 + with_settings :timelog_required_fields => [] do + assert_no_difference 'Issue.count' do + delete :destroy, :params => { + :ids => [1, 3] + } + end + end + assert_response :success + + assert_select 'form' do + assert_select 'input[name=_method][value=delete]' + assert_select 'input[name=todo][value=destroy]' + assert_select 'input[name=todo][value=nullify]' + assert_select 'input[name=todo][value=reassign]' + end + end + + def test_destroy_issues_with_time_entries_should_not_show_the_nullify_option_when_issue_is_required_for_time_entries + with_settings :timelog_required_fields => ['issue_id'] do + @request.session[:user_id] = 2 + assert_no_difference 'Issue.count' do delete :destroy, :params => { :ids => [1, 3] @@ -6221,6 +6316,10 @@ class IssuesControllerTest < Redmine::ControllerTest assert_select 'form' do assert_select 'input[name=_method][value=delete]' + assert_select 'input[name=todo][value=destroy]' + assert_select 'input[name=todo][value=nullify]', 0 + assert_select 'input[name=todo][value=reassign]' + end end end @@ -6259,6 +6358,7 @@ class IssuesControllerTest < Redmine::ControllerTest def test_destroy_issues_and_assign_time_entries_to_project @request.session[:user_id] = 2 + with_settings :timelog_required_fields => [] do assert_difference 'Issue.count', -2 do assert_no_difference 'TimeEntry.count' do delete :destroy, :params => { @@ -6267,6 +6367,7 @@ class IssuesControllerTest < Redmine::ControllerTest } end end + end assert_redirected_to :action => 'index', :project_id => 'ecookbook' assert !(Issue.find_by_id(1) || Issue.find_by_id(3)) assert_nil TimeEntry.find(1).issue_id @@ -6345,6 +6446,23 @@ class IssuesControllerTest < Redmine::ControllerTest assert_select '#flash_error', :text => I18n.t(:error_cannot_reassign_time_entries_to_an_issue_about_to_be_deleted) end + def test_destroy_issues_and_nullify_time_entries_should_fail_when_issue_is_required_for_time_entries + @request.session[:user_id] = 2 + + with_settings :timelog_required_fields => ['issue_id'] do + assert_no_difference 'Issue.count' do + assert_no_difference 'TimeEntry.count' do + delete :destroy, :params => { + :ids => [1, 3], + :todo => 'nullify' + } + end + end + end + assert_response :success + assert_select '#flash_error', :text => 'Issue cannot be blank' + end + def test_destroy_issues_from_different_projects @request.session[:user_id] = 2 diff --git a/test/functional/project_enumerations_controller_test.rb b/test/functional/project_enumerations_controller_test.rb index f98d859..d6d47e7 100644 --- a/test/functional/project_enumerations_controller_test.rb +++ b/test/functional/project_enumerations_controller_test.rb @@ -155,7 +155,7 @@ class ProjectEnumerationsControllerTest < Redmine::ControllerTest # All TimeEntries using project activity project_specific_activity = TimeEntryActivity.find_by_parent_id_and_project_id(9, 1) assert_equal 3, TimeEntry.where(:activity_id => project_specific_activity.id, - :project_id => 1).count + :project_id => 1).count, "No Time Entries assigned to the project activity" end @@ -185,11 +185,11 @@ class ProjectEnumerationsControllerTest < Redmine::ControllerTest # TimeEntries shouldn't have been reassigned on the failed record assert_equal 3, TimeEntry.where(:activity_id => 9, - :project_id => 1).count + :project_id => 1).count, "Time Entries are not assigned to system activities" # TimeEntries shouldn't have been reassigned on the saved record either assert_equal 1, TimeEntry.where(:activity_id => 10, - :project_id => 1).count + :project_id => 1).count, "Time Entries are not assigned to system activities" end diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 955ef2f..75752c7 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -661,6 +661,29 @@ class ProjectsControllerTest < Redmine::ControllerTest assert_select "tr#member-#{member.id}" end + def test_settings_should_show_tabs_depending_on_permission + @request.session[:user_id] = 3 + project = Project.find(1) + role = User.find(3).roles_for_project(project).first + + role.permissions = [] + role.save + get :settings, :params => { + :id => project.id + } + assert_response 403 + + role.add_permission! :manage_repository, :manage_boards, :manage_project_activities + get :settings, :params => { + :id => project.id + } + assert_response :success + assert_select 'a[id^=tab-]', 3 + assert_select 'a#tab-repositories' + assert_select 'a#tab-boards' + assert_select 'a#tab-activities' + end + def test_update @request.session[:user_id] = 2 # manager post :update, :params => { @@ -979,4 +1002,35 @@ class ProjectsControllerTest < Redmine::ControllerTest } assert_select 'body.project-ecookbook' end + + def test_default_search_scope_in_global_page + get :index + + assert_select 'div#quick-search form' do + assert_select 'input[name=scope][type=hidden]' + assert_select 'a[href=?]', '/search' + end + end + + def test_default_search_scope_for_project_without_subprojects + get :show, :params => { + :id => 4, + } + + assert_select 'div#quick-search form' do + assert_select 'input[name=scope][type=hidden]' + assert_select 'a[href=?]', '/projects/subproject2/search' + end + end + + def test_default_search_scope_for_project_with_subprojects + get :show, :params => { + :id => 1, + } + + assert_select 'div#quick-search form' do + assert_select 'input[name=scope][type=hidden][value=subprojects]' + assert_select 'a[href=?]', '/projects/ecookbook/search?scope=subprojects' + end + end end diff --git a/test/functional/search_controller_test.rb b/test/functional/search_controller_test.rb index 97075ea..c304295 100644 --- a/test/functional/search_controller_test.rb +++ b/test/functional/search_controller_test.rb @@ -338,6 +338,46 @@ class SearchControllerTest < Redmine::ControllerTest assert_response 404 end + def test_search_should_include_closed_projects + @request.session[:user_id] = 1 + + project = Project.find(5) + project.close + project.save + + # scope all + get :index, :params => {:q => 'Issue of a private subproject', :scope => 'all'} + assert_response :success + + assert_select '#search-results' do + assert_select 'dt.issue', :text => /Bug #6/ + end + + # scope my_projects + get :index, :params => {:q => 'Issue of a private subproject', :scope => 'my_projects'} + assert_response :success + + assert_select '#search-results' do + assert_select 'dt.issue', :text => /Bug #6/ + end + + # scope subprojects + get :index, :params => {:id => 1, :q => 'Issue of a private subproject', :scope => 'subprojects'} + assert_response :success + + assert_select '#search-results' do + assert_select 'dt.issue', :text => /Bug #6/ + end + + # scope project + get :index, :params => {:id => 5, :q => 'Issue of a private subproject'} + assert_response :success + + assert_select '#search-results' do + assert_select 'dt.issue', :text => /Bug #6/ + end + end + def test_quick_jump_to_issue # issue of a public project get :index, :params => {:q => "3"} diff --git a/test/functional/timelog_controller_test.rb b/test/functional/timelog_controller_test.rb index a0b3fad..d99e2c8 100644 --- a/test/functional/timelog_controller_test.rb +++ b/test/functional/timelog_controller_test.rb @@ -276,7 +276,7 @@ class TimelogControllerTest < Redmine::ControllerTest }, :continue => '1' } - assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=1' + assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=1&time_entry%5Bspent_on%5D=2008-03-14' end end @@ -293,7 +293,7 @@ class TimelogControllerTest < Redmine::ControllerTest }, :continue => '1' } - assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D=' + assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D=&time_entry%5Bspent_on%5D=2008-03-14' end end @@ -310,7 +310,7 @@ class TimelogControllerTest < Redmine::ControllerTest }, :continue => '1' } - assert_redirected_to '/projects/ecookbook/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=' + assert_redirected_to '/projects/ecookbook/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=&time_entry%5Bspent_on%5D=2008-03-14' end end @@ -327,7 +327,7 @@ class TimelogControllerTest < Redmine::ControllerTest }, :continue => '1' } - assert_redirected_to '/issues/1/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D=' + assert_redirected_to '/issues/1/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D=&time_entry%5Bspent_on%5D=2008-03-14' end end diff --git a/test/integration/api_test/search_test.rb b/test/integration/api_test/search_test.rb index 2c14ed4..354f5f2 100644 --- a/test/integration/api_test/search_test.rb +++ b/test/integration/api_test/search_test.rb @@ -89,4 +89,9 @@ class Redmine::ApiTest::SearchTest < Redmine::ApiTest::Base assert_equal 4, json['limit'] assert_equal issue[8..10], json['results'].map {|r| r['id']} end + + test "GET /search.xml should not quick jump to the issue with given id" do + get '/search.xml', :params => {:q => '3'} + assert_response :success + end end diff --git a/test/integration/wiki_test.rb b/test/integration/wiki_test.rb new file mode 100644 index 0000000..bbf8dba --- /dev/null +++ b/test/integration/wiki_test.rb @@ -0,0 +1,56 @@ +require File.expand_path('../../test_helper', __FILE__) + +class WikiIntegrationTest < Redmine::IntegrationTest + fixtures :projects, + :users, :email_addresses, + :roles, + :members, + :member_roles, + :trackers, + :projects_trackers, + :enabled_modules, + :wikis, + :wiki_pages, + :wiki_contents + + def test_updating_a_renamed_page + log_user('jsmith', 'jsmith') + + get '/projects/ecookbook/wiki' + assert_response :success + + get '/projects/ecookbook/wiki/Wiki/edit' + assert_response :success + + # this update should not end up with a loss of content + put '/projects/ecookbook/wiki/Wiki', params: { + content: { + text: "# Wiki\r\n\r\ncontent", comments:"" + }, + wiki_page: { parent_id: "" } + } + assert_redirected_to "/projects/ecookbook/wiki/Wiki" + follow_redirect! + assert_select 'div', /content/ + assert content = WikiContent.last + + # Let's assume somebody else, or the same user in another tab, renames the + # page while it is being edited. + post '/projects/ecookbook/wiki/Wiki/rename', params: { wiki_page: { title: "NewTitle" } } + assert_redirected_to "/projects/ecookbook/wiki/NewTitle" + + # this update should not end up with a loss of content + put '/projects/ecookbook/wiki/Wiki', params: { + content: { + version: content.version, text: "# Wiki\r\n\r\nnew content", comments:"" + }, + wiki_page: { parent_id: "" } + } + + assert_redirected_to "/projects/ecookbook/wiki/NewTitle" + follow_redirect! + assert_select 'div', /new content/ + end + +end + diff --git a/test/test_helper.rb b/test/test_helper.rb index 0066d38..270acb5 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -33,7 +33,7 @@ require File.expand_path(File.dirname(__FILE__) + '/object_helpers') include ObjectHelpers require 'net/ldap' -require 'mocha/setup' +require 'mocha/minitest' require 'fileutils' Redmine::SudoMode.disable! diff --git a/test/unit/activity_test.rb b/test/unit/activity_test.rb index a14203d..3c14021 100644 --- a/test/unit/activity_test.rb +++ b/test/unit/activity_test.rb @@ -23,6 +23,7 @@ class ActivityTest < ActiveSupport::TestCase :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions def setup + User.current = nil @project = Project.find(1) end diff --git a/test/unit/attachment_test.rb b/test/unit/attachment_test.rb index e55c60b..4093df7 100644 --- a/test/unit/attachment_test.rb +++ b/test/unit/attachment_test.rb @@ -262,6 +262,7 @@ class AttachmentTest < ActiveSupport::TestCase a = Attachment.new(:filename => "test.png", :description => "Cool image") assert_equal "test.png (Cool image)", a.title + assert_equal "test.png", a.filename end def test_new_attachment_should_be_editable_by_author diff --git a/test/unit/attachment_transaction_test.rb b/test/unit/attachment_transaction_test.rb index 44776c8..59f4067 100644 --- a/test/unit/attachment_transaction_test.rb +++ b/test/unit/attachment_transaction_test.rb @@ -26,6 +26,7 @@ class AttachmentTest < ActiveSupport::TestCase self.use_transactional_fixtures = false def setup + User.current = nil set_tmp_attachments_directory end diff --git a/test/unit/auth_source_ldap_test.rb b/test/unit/auth_source_ldap_test.rb index c7676f1..2765015 100644 --- a/test/unit/auth_source_ldap_test.rb +++ b/test/unit/auth_source_ldap_test.rb @@ -22,6 +22,7 @@ class AuthSourceLdapTest < ActiveSupport::TestCase fixtures :auth_sources def setup + User.current = nil end def test_initialize diff --git a/test/unit/board_test.rb b/test/unit/board_test.rb index c66e700..35c2da9 100644 --- a/test/unit/board_test.rb +++ b/test/unit/board_test.rb @@ -25,6 +25,7 @@ class BoardTest < ActiveSupport::TestCase include Redmine::I18n def setup + User.current = nil @project = Project.find(1) end diff --git a/test/unit/changeset_test.rb b/test/unit/changeset_test.rb index 8586d8f..482ec5b 100644 --- a/test/unit/changeset_test.rb +++ b/test/unit/changeset_test.rb @@ -32,6 +32,10 @@ class ChangesetTest < ActiveSupport::TestCase :trackers, :projects_trackers, :enabled_modules, :roles + def setup + User.current = nil + end + def test_ref_keywords_any ActionMailer::Base.deliveries.clear Setting.commit_ref_keywords = '*' diff --git a/test/unit/comment_test.rb b/test/unit/comment_test.rb index 4597fd9..f58c4fa 100644 --- a/test/unit/comment_test.rb +++ b/test/unit/comment_test.rb @@ -21,6 +21,7 @@ class CommentTest < ActiveSupport::TestCase fixtures :users, :email_addresses, :news, :comments, :projects, :enabled_modules def setup + User.current = nil @jsmith = User.find(2) @news = News.find(1) end diff --git a/test/unit/custom_field_test.rb b/test/unit/custom_field_test.rb index 3eae54e..b49501e 100644 --- a/test/unit/custom_field_test.rb +++ b/test/unit/custom_field_test.rb @@ -206,6 +206,7 @@ class CustomFieldTest < ActiveSupport::TestCase assert f.valid_field_value?('') assert !f.valid_field_value?(' ') assert f.valid_field_value?('123') + assert f.valid_field_value?(' 123 ') assert f.valid_field_value?('+123') assert f.valid_field_value?('-123') assert !f.valid_field_value?('6abc') @@ -219,6 +220,7 @@ class CustomFieldTest < ActiveSupport::TestCase assert f.valid_field_value?('') assert !f.valid_field_value?(' ') assert f.valid_field_value?('11.2') + assert f.valid_field_value?(' 11.2 ') assert f.valid_field_value?('-6.250') assert f.valid_field_value?('5') assert !f.valid_field_value?('6abc') diff --git a/test/unit/custom_field_user_format_test.rb b/test/unit/custom_field_user_format_test.rb index 8827e97..a3d161d 100644 --- a/test/unit/custom_field_user_format_test.rb +++ b/test/unit/custom_field_user_format_test.rb @@ -21,6 +21,7 @@ class CustomFieldUserFormatTest < ActiveSupport::TestCase fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues def setup + User.current = nil @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user') end diff --git a/test/unit/custom_field_version_format_test.rb b/test/unit/custom_field_version_format_test.rb index 761d0ab..4e09bed 100644 --- a/test/unit/custom_field_version_format_test.rb +++ b/test/unit/custom_field_version_format_test.rb @@ -21,6 +21,7 @@ class CustomFieldVersionFormatTest < ActiveSupport::TestCase fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues, :versions def setup + User.current = nil @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'version') end diff --git a/test/unit/custom_value_test.rb b/test/unit/custom_value_test.rb index b22cc42..14964ed 100644 --- a/test/unit/custom_value_test.rb +++ b/test/unit/custom_value_test.rb @@ -20,6 +20,10 @@ require File.expand_path('../../test_helper', __FILE__) class CustomValueTest < ActiveSupport::TestCase fixtures :custom_fields, :custom_values, :users + def setup + User.current = nil + end + def test_new_without_value_should_set_default_value field = CustomField.generate!(:default_value => 'Default string') diff --git a/test/unit/default_data_test.rb b/test/unit/default_data_test.rb index f7aea76..6a6a223 100644 --- a/test/unit/default_data_test.rb +++ b/test/unit/default_data_test.rb @@ -21,6 +21,10 @@ class DefaultDataTest < ActiveSupport::TestCase include Redmine::I18n fixtures :roles + def setup + User.current = nil + end + def test_no_data assert !Redmine::DefaultData::Loader::no_data? clear_data diff --git a/test/unit/document_category_test.rb b/test/unit/document_category_test.rb index 6a9eb09..305d827 100644 --- a/test/unit/document_category_test.rb +++ b/test/unit/document_category_test.rb @@ -20,6 +20,10 @@ require File.expand_path('../../test_helper', __FILE__) class DocumentCategoryTest < ActiveSupport::TestCase fixtures :enumerations, :documents, :issues + def setup + User.current = nil + end + def test_should_be_an_enumeration assert DocumentCategory.ancestors.include?(Enumeration) end diff --git a/test/unit/enabled_module_test.rb b/test/unit/enabled_module_test.rb index eae68fb..79995db 100644 --- a/test/unit/enabled_module_test.rb +++ b/test/unit/enabled_module_test.rb @@ -20,6 +20,10 @@ require File.expand_path('../../test_helper', __FILE__) class EnabledModuleTest < ActiveSupport::TestCase fixtures :projects, :trackers, :issue_statuses, :wikis + def setup + User.current = nil + end + def test_enabling_wiki_should_create_a_wiki CustomField.delete_all project = Project.create!(:name => 'Project with wiki', :identifier => 'wikiproject', :enabled_module_names => []) diff --git a/test/unit/enumeration_test.rb b/test/unit/enumeration_test.rb index 9360997..336283b 100644 --- a/test/unit/enumeration_test.rb +++ b/test/unit/enumeration_test.rb @@ -20,6 +20,10 @@ require File.expand_path('../../test_helper', __FILE__) class EnumerationTest < ActiveSupport::TestCase fixtures :enumerations, :issues, :custom_fields, :custom_values + def setup + User.current = nil + end + def test_objects_count # low priority assert_equal 6, Enumeration.find(4).objects_count diff --git a/test/unit/group_test.rb b/test/unit/group_test.rb index 8756f31..f706826 100644 --- a/test/unit/group_test.rb +++ b/test/unit/group_test.rb @@ -28,6 +28,10 @@ class GroupTest < ActiveSupport::TestCase include Redmine::I18n + def setup + User.current = nil + end + def test_create g = Group.new(:name => 'New group') assert g.save diff --git a/test/unit/helpers/application_helper_test.rb b/test/unit/helpers/application_helper_test.rb index 941ba7e..5065f5c 100644 --- a/test/unit/helpers/application_helper_test.rb +++ b/test/unit/helpers/application_helper_test.rb @@ -1328,7 +1328,6 @@ RAW end def test_avatar_enabled - tag_for_anonymous_re = %r{src="/images/anonymous.png(\?\d+)?"} with_settings :gravatar_enabled => '1' do assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo')) assert avatar('jsmith ').include?(Digest::MD5.hexdigest('jsmith@somenet.foo')) @@ -1340,10 +1339,13 @@ RAW # The default class of the img tag should be gravatar assert avatar('jsmith ').include?('class="gravatar"') assert !avatar('jsmith ', :class => 'picture').include?('class="gravatar"') - assert_match tag_for_anonymous_re, avatar('jsmith') - assert_match tag_for_anonymous_re, avatar(nil) + assert_nil avatar('jsmith') + assert_nil avatar(nil) # Avatar for anonymous user - assert_match tag_for_anonymous_re, avatar(User.anonymous) + assert_match %r{src="/images/anonymous.png(\?\d+)?"}, avatar(User.anonymous) + # No avatar for groups + assert_nil avatar(Group.first) + assert avatar(User.anonymous, :size => 24).include?('width="24" height="24"') end end diff --git a/test/unit/issue_category_test.rb b/test/unit/issue_category_test.rb index 1340ea8..2d03168 100644 --- a/test/unit/issue_category_test.rb +++ b/test/unit/issue_category_test.rb @@ -21,6 +21,7 @@ class IssueCategoryTest < ActiveSupport::TestCase fixtures :issue_categories, :issues, :users, :groups_users def setup + User.current = nil @category = IssueCategory.find(1) end diff --git a/test/unit/issue_custom_field_test.rb b/test/unit/issue_custom_field_test.rb index 107d47d..4ecce3b 100644 --- a/test/unit/issue_custom_field_test.rb +++ b/test/unit/issue_custom_field_test.rb @@ -22,6 +22,11 @@ class IssueCustomFieldTest < ActiveSupport::TestCase fixtures :roles + def setup + User.current = nil + @category = IssueCategory.find(1) + end + def test_custom_field_with_visible_set_to_false_should_validate_roles set_language_if_valid 'en' field = IssueCustomField.new(:name => 'Field', :field_format => 'string', :visible => false) diff --git a/test/unit/issue_import_test.rb b/test/unit/issue_import_test.rb index 9d87233..84f2dbb 100644 --- a/test/unit/issue_import_test.rb +++ b/test/unit/issue_import_test.rb @@ -35,6 +35,7 @@ class IssueImportTest < ActiveSupport::TestCase include Redmine::I18n def setup + User.current = nil set_language_if_valid 'en' end @@ -115,6 +116,15 @@ class IssueImportTest < ActiveSupport::TestCase assert_equal 2, issues[2].parent_id end + def test_import_utf8_with_bom + import = generate_import_with_mapping('import_issues_utf8_with_bom.csv') + import.settings.merge!('encoding' => 'UTF-8') + import.save + + issues = new_records(Issue,3) { import.run } + assert_equal 3, issues.count + end + def test_backward_and_forward_reference_to_parent_should_work import = generate_import('import_subtasks.csv') import.settings = { @@ -188,4 +198,16 @@ class IssueImportTest < ActiveSupport::TestCase import.run assert !File.exists?(file_path) end + + def test_run_should_consider_project_shared_versions + system_version = Version.generate!(:project_id => 2, :sharing => 'system', :name => '2.1') + system_version.save! + + import = generate_import_with_mapping + import.mapping.merge!('fixed_version' => '9') + import.save! + + issues = new_records(Issue, 3) { import.run } + assert [nil, 3, system_version.id], issues.map(&:fixed_version_id) + end end diff --git a/test/unit/issue_nested_set_concurrency_test.rb b/test/unit/issue_nested_set_concurrency_test.rb index de5a148..30cd5c4 100644 --- a/test/unit/issue_nested_set_concurrency_test.rb +++ b/test/unit/issue_nested_set_concurrency_test.rb @@ -28,6 +28,7 @@ class IssueNestedSetConcurrencyTest < ActiveSupport::TestCase def setup skip if sqlite? || mysql? + User.current = nil CustomField.delete_all end diff --git a/test/unit/issue_priority_test.rb b/test/unit/issue_priority_test.rb index 19ee807..68fd419 100644 --- a/test/unit/issue_priority_test.rb +++ b/test/unit/issue_priority_test.rb @@ -20,6 +20,10 @@ require File.expand_path('../../test_helper', __FILE__) class IssuePriorityTest < ActiveSupport::TestCase fixtures :enumerations, :issues + def setup + User.current = nil + end + def test_named_scope assert_equal Enumeration.find_by_name('Normal'), Enumeration.named('normal').first end diff --git a/test/unit/issue_relation_test.rb b/test/unit/issue_relation_test.rb index 585a829..6e63228 100644 --- a/test/unit/issue_relation_test.rb +++ b/test/unit/issue_relation_test.rb @@ -33,6 +33,10 @@ class IssueRelationTest < ActiveSupport::TestCase include Redmine::I18n + def setup + User.current = nil + end + def test_create from = Issue.find(1) to = Issue.find(2) diff --git a/test/unit/issue_scopes_test.rb b/test/unit/issue_scopes_test.rb index f8460b9..1925364 100644 --- a/test/unit/issue_scopes_test.rb +++ b/test/unit/issue_scopes_test.rb @@ -26,6 +26,10 @@ class IssueScopesTest < ActiveSupport::TestCase :issues, :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values + def setup + User.current = nil + end + def test_cross_project_scope_without_project_should_return_all_issues ids = Issue.cross_project_scope(nil).pluck(:id).sort assert_equal Issue.pluck(:id).sort, ids diff --git a/test/unit/issue_status_test.rb b/test/unit/issue_status_test.rb index 52b6590..2744136 100644 --- a/test/unit/issue_status_test.rb +++ b/test/unit/issue_status_test.rb @@ -28,6 +28,10 @@ class IssueStatusTest < ActiveSupport::TestCase :issues, :journals, :journal_details, :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values + def setup + User.current = nil + end + def test_create status = IssueStatus.new :name => "Assigned" assert !status.save diff --git a/test/unit/issue_subtasking_test.rb b/test/unit/issue_subtasking_test.rb index a50df02..38fa530 100644 --- a/test/unit/issue_subtasking_test.rb +++ b/test/unit/issue_subtasking_test.rb @@ -25,6 +25,10 @@ class IssueSubtaskingTest < ActiveSupport::TestCase :enabled_modules, :workflows + def setup + User.current = nil + end + def test_leaf_planning_fields_should_be_editable issue = Issue.generate! user = User.find(1) @@ -321,7 +325,7 @@ class IssueSubtaskingTest < ActiveSupport::TestCase end end - def test_parent_total_estimated_hours_should_be_sum_of_descendants + def test_parent_total_estimated_hours_should_be_sum_of_visible_descendants parent = Issue.generate! parent.generate_child!(:estimated_hours => nil) assert_equal 0, parent.reload.total_estimated_hours @@ -329,6 +333,9 @@ class IssueSubtaskingTest < ActiveSupport::TestCase assert_equal 5, parent.reload.total_estimated_hours parent.generate_child!(:estimated_hours => 7) assert_equal 12, parent.reload.total_estimated_hours + + parent.generate_child!(:estimated_hours => 9, :is_private => true) + assert_equal 12, parent.reload.total_estimated_hours end def test_open_issue_with_closed_parent_should_not_validate diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index 8297335..3a247f9 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -1375,6 +1375,63 @@ class IssueTest < ActiveSupport::TestCase assert_not_nil copied_closed.closed_on end + def test_copy_should_not_copy_locked_watchers + user = User.find(2) + user2 = User.find(3) + issue = Issue.find(8) + + Watcher.create!(:user => user, :watchable => issue) + Watcher.create!(:user => user2, :watchable => issue) + + user2.status = User::STATUS_LOCKED + user2.save! + + issue = Issue.new.copy_from(8) + + assert issue.save + assert issue.watched_by?(user) + assert !issue.watched_by?(user2) + end + + def test_copy_should_clear_subtasks_target_version_if_locked_or_closed + version = Version.new(:project => Project.find(1), :name => '2.1',) + version.save! + + parent = Issue.generate! + child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1', :fixed_version_id => 3) + child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2', :fixed_version_id => version.id) + + version.status = 'locked' + version.save! + + copy = parent.reload.copy + + assert_difference 'Issue.count', 3 do + assert copy.save + end + + assert_equal [3, nil], copy.children.map(&:fixed_version_id) + end + + def test_copy_should_clear_subtasks_assignee_if_is_locked + user = User.find(2) + + parent = Issue.generate! + child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1', :assigned_to_id => 3) + child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2', :assigned_to_id => user.id) + + user.status = User::STATUS_LOCKED + user.save! + + copy = parent.reload.copy + + assert_difference 'Issue.count', 3 do + assert copy.save + end + + assert_equal [3, nil], copy.children.map(&:assigned_to_id) + end + def test_should_not_call_after_project_change_on_creation issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Test', :author_id => 1) @@ -2089,6 +2146,32 @@ class IssueTest < ActiveSupport::TestCase assert_equal Date.parse('2012-09-21'), issue2.due_date end + def test_rescheduling_an_issue_to_a_different_due_date_should_add_journal_to_following_issue + with_settings :non_working_week_days => [] do + issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') + issue2 = Issue.generate!(:start_date => '2012-10-18', :due_date => '2012-10-20') + IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, + :relation_type => IssueRelation::TYPE_PRECEDES) + + assert_difference 'issue2.journals.count' do + issue1.reload + issue1.init_journal(User.find(3)) + issue1.due_date = '2012-10-23' + issue1.save! + end + journal = issue2.journals.order(:id).last + + start_date_detail = journal.details.find_by(:prop_key => 'start_date') + assert_equal '2012-10-18', start_date_detail.old_value + assert_equal '2012-10-24', start_date_detail.value + + due_date_detail = journal.details.find_by(:prop_key => 'due_date') + assert_equal '2012-10-20', due_date_detail.old_value + assert_equal '2012-10-26', due_date_detail.value + end + end + + def test_rescheduling_reschedule_following_issue_earlier_should_consider_other_preceding_issues issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') diff --git a/test/unit/issue_transaction_test.rb b/test/unit/issue_transaction_test.rb index bb30df5..9e7ccce 100644 --- a/test/unit/issue_transaction_test.rb +++ b/test/unit/issue_transaction_test.rb @@ -29,6 +29,10 @@ class IssueTransactionTest < ActiveSupport::TestCase self.use_transactional_fixtures = false + def setup + User.current = nil + end + def test_invalid_move_to_another_project lft1 = new_issue_lft parent1 = Issue.generate! diff --git a/test/unit/journal_observer_test.rb b/test/unit/journal_observer_test.rb index e9eac32..8c06af2 100644 --- a/test/unit/journal_observer_test.rb +++ b/test/unit/journal_observer_test.rb @@ -23,6 +23,7 @@ class JournalObserverTest < ActiveSupport::TestCase :users, :email_addresses, :roles def setup + User.current = nil ActionMailer::Base.deliveries.clear @journal = Journal.find 1 end diff --git a/test/unit/lib/redmine/ciphering_test.rb b/test/unit/lib/redmine/ciphering_test.rb index 76b0e37..38f87b4 100644 --- a/test/unit/lib/redmine/ciphering_test.rb +++ b/test/unit/lib/redmine/ciphering_test.rb @@ -21,8 +21,9 @@ class Redmine::CipheringTest < ActiveSupport::TestCase def test_password_should_be_encrypted Redmine::Configuration.with 'database_cipher_key' => 'secret' do - r = Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'svn') - assert_equal 'foo', r.password + plaintext_password = "THIS_IS_A_32_BYTES_LONG_PASSWORD" + r = Repository::Subversion.create!(:password => plaintext_password, :url => 'file:///tmp', :identifier => 'svn') + assert_equal plaintext_password, r.password assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/) end end diff --git a/test/unit/lib/redmine/configuration_test.rb b/test/unit/lib/redmine/configuration_test.rb index 896f4f1..ca879c0 100644 --- a/test/unit/lib/redmine/configuration_test.rb +++ b/test/unit/lib/redmine/configuration_test.rb @@ -23,26 +23,26 @@ class Redmine::ConfigurationTest < ActiveSupport::TestCase end def test_empty - assert_kind_of Hash, load_conf('empty.yml', 'test') + assert_kind_of Hash, load_conf('empty.yml.example', 'test') end def test_default - assert_kind_of Hash, load_conf('default.yml', 'test') + assert_kind_of Hash, load_conf('default.yml.example', 'test') assert_equal 'foo', @conf['somesetting'] end def test_no_default - assert_kind_of Hash, load_conf('no_default.yml', 'test') + assert_kind_of Hash, load_conf('no_default.yml.example', 'test') assert_equal 'foo', @conf['somesetting'] end def test_overrides - assert_kind_of Hash, load_conf('overrides.yml', 'test') + assert_kind_of Hash, load_conf('overrides.yml.example', 'test') assert_equal 'bar', @conf['somesetting'] end def test_with - load_conf('default.yml', 'test') + load_conf('default.yml.example', 'test') assert_equal 'foo', @conf['somesetting'] @conf.with 'somesetting' => 'bar' do assert_equal 'bar', @conf['somesetting'] diff --git a/test/unit/lib/redmine/field_format/attachment_format_visibility_test.rb b/test/unit/lib/redmine/field_format/attachment_format_visibility_test.rb index 4ce4062..8d883f9 100644 --- a/test/unit/lib/redmine/field_format/attachment_format_visibility_test.rb +++ b/test/unit/lib/redmine/field_format/attachment_format_visibility_test.rb @@ -26,6 +26,7 @@ class AttachmentFormatVisibilityTest < ActionView::TestCase :versions, :issues def setup + User.current = nil set_tmp_attachments_directory end diff --git a/test/unit/lib/redmine/field_format/bool_format_test.rb b/test/unit/lib/redmine/field_format/bool_format_test.rb index 9041c81..2458de1 100644 --- a/test/unit/lib/redmine/field_format/bool_format_test.rb +++ b/test/unit/lib/redmine/field_format/bool_format_test.rb @@ -23,6 +23,7 @@ class Redmine::BoolFieldFormatTest < ActionView::TestCase include Redmine::I18n def setup + User.current = nil set_language_if_valid 'en' end diff --git a/test/unit/lib/redmine/field_format/enumeration_format_test.rb b/test/unit/lib/redmine/field_format/enumeration_format_test.rb index bc9a498..9b977b2 100644 --- a/test/unit/lib/redmine/field_format/enumeration_format_test.rb +++ b/test/unit/lib/redmine/field_format/enumeration_format_test.rb @@ -22,6 +22,7 @@ class Redmine::EnumerationFieldFormatTest < ActionView::TestCase include ApplicationHelper def setup + User.current = nil set_language_if_valid 'en' @field = IssueCustomField.create!(:name => 'List', :field_format => 'enumeration', :is_required => false) @foo = CustomFieldEnumeration.new(:name => 'Foo') diff --git a/test/unit/lib/redmine/field_format/field_format_test.rb b/test/unit/lib/redmine/field_format/field_format_test.rb index 631ac31..38e6ea3 100644 --- a/test/unit/lib/redmine/field_format/field_format_test.rb +++ b/test/unit/lib/redmine/field_format/field_format_test.rb @@ -21,6 +21,7 @@ class Redmine::FieldFormatTest < ActionView::TestCase include ApplicationHelper def setup + User.current = nil set_language_if_valid 'en' end diff --git a/test/unit/lib/redmine/field_format/link_format_test.rb b/test/unit/lib/redmine/field_format/link_format_test.rb index 311e75a..04b8521 100644 --- a/test/unit/lib/redmine/field_format/link_format_test.rb +++ b/test/unit/lib/redmine/field_format/link_format_test.rb @@ -19,6 +19,11 @@ require File.expand_path('../../../../../test_helper', __FILE__) require 'redmine/field_format' class Redmine::LinkFieldFormatTest < ActionView::TestCase + + def setup + User.current = nil + end + def test_link_field_should_substitute_value field = IssueCustomField.new(:field_format => 'link', :url_pattern => 'http://foo/%value%') custom_value = CustomValue.new(:custom_field => field, :customized => Issue.new, :value => "bar") diff --git a/test/unit/lib/redmine/field_format/list_format_test.rb b/test/unit/lib/redmine/field_format/list_format_test.rb index 9c23fd0..cc08d81 100644 --- a/test/unit/lib/redmine/field_format/list_format_test.rb +++ b/test/unit/lib/redmine/field_format/list_format_test.rb @@ -23,6 +23,7 @@ class Redmine::ListFieldFormatTest < ActionView::TestCase include Redmine::I18n def setup + User.current = nil set_language_if_valid 'en' end @@ -37,6 +38,16 @@ class Redmine::ListFieldFormatTest < ActionView::TestCase assert group.valid? end + def test_non_existing_value_should_be_invalid + field = GroupCustomField.create!(:name => 'List', :field_format => 'list', :possible_values => ['Foo', 'Bar']) + group = Group.new(:name => 'Group') + group.custom_field_values = {field.id => 'Baz'} + + assert_not_include 'Baz', field.possible_custom_value_options(group.custom_value_for(field)) + assert_equal false, group.valid? + assert_include "List #{::I18n.t('activerecord.errors.messages.inclusion')}", group.errors.full_messages.first + end + def test_edit_tag_should_have_id_and_name field = IssueCustomField.new(:field_format => 'list', :possible_values => ['Foo', 'Bar'], :is_required => false) value = CustomFieldValue.new(:custom_field => field, :customized => Issue.new) diff --git a/test/unit/lib/redmine/field_format/numeric_format_test.rb b/test/unit/lib/redmine/field_format/numeric_format_test.rb index 6fdb9b2..0a91da0 100644 --- a/test/unit/lib/redmine/field_format/numeric_format_test.rb +++ b/test/unit/lib/redmine/field_format/numeric_format_test.rb @@ -1,31 +1,35 @@ -# Redmine - project management software -# Copyright (C) 2006-2017 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../../test_helper', __FILE__) -require 'redmine/field_format' - -class Redmine::NumericFieldFormatTest < ActionView::TestCase - include ApplicationHelper - - def test_integer_field_with_url_pattern_should_format_as_link - field = IssueCustomField.new(:field_format => 'int', :url_pattern => 'http://foo/%value%') - custom_value = CustomValue.new(:custom_field => field, :customized => Issue.new, :value => "3") - - assert_equal 3, field.format.formatted_custom_value(self, custom_value, false) - assert_equal '3', field.format.formatted_custom_value(self, custom_value, true) - end -end +# Redmine - project management software +# Copyright (C) 2006-2017 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../../test_helper', __FILE__) +require 'redmine/field_format' + +class Redmine::NumericFieldFormatTest < ActionView::TestCase + include ApplicationHelper + + def setup + User.current = nil + end + + def test_integer_field_with_url_pattern_should_format_as_link + field = IssueCustomField.new(:field_format => 'int', :url_pattern => 'http://foo/%value%') + custom_value = CustomValue.new(:custom_field => field, :customized => Issue.new, :value => "3") + + assert_equal 3, field.format.formatted_custom_value(self, custom_value, false) + assert_equal '3', field.format.formatted_custom_value(self, custom_value, true) + end +end diff --git a/test/unit/lib/redmine/field_format/user_field_format_test.rb b/test/unit/lib/redmine/field_format/user_field_format_test.rb index 802fa7a..68fdabd 100644 --- a/test/unit/lib/redmine/field_format/user_field_format_test.rb +++ b/test/unit/lib/redmine/field_format/user_field_format_test.rb @@ -24,6 +24,10 @@ class Redmine::UserFieldFormatTest < ActionView::TestCase :issue_statuses, :issue_categories, :issue_relations, :workflows, :enumerations + def setup + User.current = nil + end + def test_user_role_should_reject_blank_values field = IssueCustomField.new(:name => 'Foo', :field_format => 'user', :user_role => ["1", ""]) field.save! @@ -45,6 +49,21 @@ class Redmine::UserFieldFormatTest < ActionView::TestCase assert issue.valid? end + def test_non_existing_values_should_be_invalid + field = IssueCustomField.create!(:name => 'Foo', :field_format => 'user', :is_for_all => true, :trackers => Tracker.all) + project = Project.generate! + user = User.generate! + User.add_to_project(user, project, Role.find_by_name('Developer')) + + field.user_role = [Role.find_by_name('Manager').id] + field.save! + + issue = Issue.new(:project_id => project.id, :tracker_id => 1, :custom_field_values => {field.id => user.id}) + assert_not_include [user.name, user.id.to_s], field.possible_custom_value_options(issue.custom_value_for(field)) + assert_equal false, issue.valid? + assert_include "Foo #{::I18n.t('activerecord.errors.messages.inclusion')}", issue.errors.full_messages.first + end + def test_possible_values_options_should_return_project_members field = IssueCustomField.new(:field_format => 'user') project = Project.find(1) diff --git a/test/unit/lib/redmine/field_format/version_field_format_test.rb b/test/unit/lib/redmine/field_format/version_field_format_test.rb index 12de014..34e492b 100644 --- a/test/unit/lib/redmine/field_format/version_field_format_test.rb +++ b/test/unit/lib/redmine/field_format/version_field_format_test.rb @@ -49,6 +49,20 @@ class Redmine::VersionFieldFormatTest < ActionView::TestCase assert issue.valid? end + def test_not_existing_values_should_be_invalid + field = IssueCustomField.create!(:name => 'Foo', :field_format => 'version', :is_for_all => true, :trackers => Tracker.all) + project = Project.generate! + version = Version.generate!(:project => project, :status => 'closed') + + field.version_status = ["open"] + field.save! + + issue = Issue.new(:project_id => project.id, :tracker_id => 1, :custom_field_values => {field.id => version.id}) + assert_not_include [version.name, version.id.to_s], field.possible_custom_value_options(issue.custom_value_for(field)) + assert_equal false, issue.valid? + assert_include "Foo #{::I18n.t('activerecord.errors.messages.inclusion')}", issue.errors.full_messages.first + end + def test_possible_values_options_should_return_project_versions field = IssueCustomField.new(:field_format => 'version') project = Project.find(1) diff --git a/test/unit/lib/redmine/helpers/diff_test.rb b/test/unit/lib/redmine/helpers/diff_test.rb index 8768f79..fa05e41 100644 --- a/test/unit/lib/redmine/helpers/diff_test.rb +++ b/test/unit/lib/redmine/helpers/diff_test.rb @@ -1,37 +1,37 @@ -# Redmine - project management software -# Copyright (C) 2006-2017 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../../test_helper', __FILE__) - -class DiffTest < ActiveSupport::TestCase - def test_diff - diff = Redmine::Helpers::Diff.new("foo", "bar") - assert_not_nil diff - end - - def test_dont_double_escape - # 3 cases to test in the before: first word, last word, everything inbetween - before = " with html & special chars" - # all words in after are treated equal - after = "other stuff