...
 
Commits (93)
......@@ -3,6 +3,7 @@
/.loadpath
/.powrc
/.rvmrc
/.ruby-version
/config/additional_environment.rb
/config/configuration.yml
/config/database.yml
......
......@@ -5,6 +5,7 @@ syntax: glob
.loadpath
.powrc
.rvmrc
.ruby-version
config/additional_environment.rb
config/configuration.yml
config/database.yml
......
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"
gem "rails", "5.2.2"
gem "rails", "5.2.3"
gem "rouge", "~> 3.3.0"
gem "request_store", "1.0.5"
gem "mini_mime", "~> 1.0.1"
......@@ -14,11 +12,9 @@ gem "mimemagic"
gem "mail", "~> 2.7.1"
gem "csv", "~> 3.0.1" if RUBY_VERSION >= "2.3" && RUBY_VERSION < "2.6"
gem "nokogiri", "~> 1.8.0"
gem "nokogiri", (RUBY_VERSION >= "2.3" ? "~> 1.10.0" : "~> 1.9.1")
gem "i18n", "~> 0.7.0"
# Request at least rails-html-sanitizer 1.0.3 because of security advisories
gem "rails-html-sanitizer", ">= 1.0.3"
gem "xpath", "< 3.2.0" if RUBY_VERSION < "2.3"
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :x64_mingw, :mswin]
......@@ -38,7 +34,7 @@ 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
......@@ -61,7 +57,7 @@ if File.exist?(database_file)
when 'mysql2'
gem "mysql2", "~> 0.5.0", :platforms => [:mri, :mingw, :x64_mingw]
when /postgresql/
gem "pg", "~> 1.0.0", :platforms => [:mri, :mingw, :x64_mingw]
gem "pg", "~> 1.1.4", :platforms => [:mri, :mingw, :x64_mingw]
when /sqlite3/
gem "sqlite3", "~>1.3.12", :platforms => [:mri, :mingw, :x64_mingw]
when /sqlserver/
......@@ -79,7 +75,6 @@ else
end
group :development do
gem "rdoc"
gem "yard"
end
......
......@@ -237,7 +237,7 @@ class AttachmentsController < ApplicationController
if content_type.blank? || content_type == "application/octet-stream"
content_type = Redmine::MimeType.of(attachment.filename)
end
content_type.to_s
content_type.presence || "application/octet-stream"
end
def disposition(attachment)
......
......@@ -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
......@@ -43,6 +43,8 @@ class ContextMenusController < ApplicationController
@priorities = IssuePriority.active.reverse
@back = back_url
@columns = params[:c]
@options_by_custom_field = {}
if @can[:edit]
custom_fields = @issues.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?).select {|field| field.format.bulk_edit_supported}
......
......@@ -63,6 +63,7 @@ class MessagesController < ApplicationController
if @message.save
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
render_attachment_warning_if_needed(@message)
flash[:notice] = l(:notice_successful_create)
redirect_to board_message_path(@board, @message)
end
end
......@@ -80,6 +81,7 @@ class MessagesController < ApplicationController
attachments = Attachment.attach_files(@reply, params[:attachments])
render_attachment_warning_if_needed(@reply)
end
flash[:notice] = l(:notice_successful_update)
redirect_to board_message_path(@board, @topic, :r => @reply)
end
......@@ -101,6 +103,7 @@ class MessagesController < ApplicationController
(render_403; return false) unless @message.destroyable_by?(User.current)
r = @message.to_param
@message.destroy
flash[:notice] = l(:notice_successful_delete)
if @message.parent
redirect_to board_message_path(@board, @message.parent, :r => r)
else
......
......@@ -255,12 +255,13 @@ class RepositoriesController < ApplicationController
Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
unless read_fragment(@cache_key)
@diff = @repository.diff(@path, @rev, @rev_to)
show_error_not_found unless @diff
(show_error_not_found; return) unless @diff
end
@changeset = @repository.find_changeset_by_name(@rev)
@changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
@diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
render :diff, :formats => :html
end
end
......
......@@ -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
......
......@@ -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
......@@ -150,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]
......
......@@ -760,7 +760,7 @@ module ApplicationHelper
attachments += obj.attachments if obj.respond_to?(:attachments)
if attachments.present?
text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
filename, ext, alt, alttext = $1.downcase, $2, $3, $4
filename, ext, alt, alttext = $1, $2, $3, $4
# search for the picture in attachments
if found = Attachment.latest_attach(attachments, CGI.unescape(filename))
image_url = download_named_attachment_url(found, found.filename, :only_path => only_path)
......@@ -787,7 +787,7 @@ module ApplicationHelper
# [[project:mypage]]
# [[project:mypage|mytext]]
def parse_wiki_links(text, project, obj, attr, only_path, options)
text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
text.gsub!(/(!)?(\[\[([^\n\|]+?)(\|([^\n\|]+?))?\]\])/) do |m|
link_project = project
esc, all, page, title = $1, $2, $3, $5
if esc.nil?
......
......@@ -235,7 +235,7 @@ class Attachment < ActiveRecord::Base
end
def is_text?
Redmine::MimeType.is_type?('text', filename)
Redmine::MimeType.is_type?('text', filename) || Redmine::SyntaxHighlighting.filename_supported?(filename)
end
def is_image?
......
......@@ -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
......
......@@ -379,7 +379,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)}))"
......
......@@ -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
......@@ -91,7 +91,8 @@ class MailHandler < ActionMailer::Base
@handler_options = options
sender_email = email.from.to_a.first.to_s.strip
# Ignore emails received from the application emission address to avoid hell cycles
if sender_email.casecmp(Setting.mail_from.to_s.strip) == 0
emission_address = Setting.mail_from.to_s.gsub(/(?:.*<|>.*|\(.*\))/, '').strip
if sender_email.casecmp(emission_address) == 0
if logger
logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
end
......@@ -237,7 +238,7 @@ class MailHandler < ActionMailer::Base
end
# ignore CLI-supplied defaults for new issues
handler_options[:issue].clear
handler_options[:issue] = {}
journal = issue.init_journal(user)
if from_journal && from_journal.private_notes?
......@@ -286,7 +287,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
......
......@@ -539,7 +539,7 @@ class Mailer < ActionMailer::Base
@issues = issues
@days = days
@issues_url = url_for(:controller => 'issues', :action => 'index',
:set_filter => 1, :assigned_to_id => user.id,
:set_filter => 1, :assigned_to_id => 'me',
:sort => 'due_date:asc')
mail :to => user,
:subject => l(:mail_subject_reminder, :count => issues.size, :days => days)
......
......@@ -24,12 +24,6 @@ class Project < ActiveRecord::Base
STATUS_CLOSED = 5
STATUS_ARCHIVED = 9
LABEL_BY_STATUS = {
1 => l(:project_status_active),
5 => l(:project_status_closed),
9 => l(:project_status_archived),
}
# Maximum length for project identifiers
IDENTIFIER_MAX_LENGTH = 100
......
......@@ -401,6 +401,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
......@@ -581,10 +583,10 @@ class Query < ActiveRecord::Base
# Returns a scope of project statuses that are available as columns or filters
def project_statuses_values
project_statuses = Project::LABEL_BY_STATUS
# Remove archived status from filters
project_statuses.delete(9)
project_statuses.stringify_keys.invert.to_a
[
[l(:project_status_active), "#{Project::STATUS_ACTIVE}"],
[l(:project_status_closed), "#{Project::STATUS_CLOSED}"]
]
end
# Adds available filters
......
......@@ -15,104 +15,106 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Version < ActiveRecord::Base
include Redmine::SafeAttributes
after_update :update_issues_from_sharing_change
after_save :update_default_project_version
before_destroy :nullify_projects_default_version
module FixedIssuesExtension
# Returns the total estimated time for this version
# (sum of leaves estimated_hours)
def estimated_hours
@estimated_hours ||= sum(:estimated_hours).to_f
end
#
# Returns the total amount of open issues for this version.
def open_count
load_counts
@open_count
end
belongs_to :project
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify do
# Returns the total estimated time for this version
# (sum of leaves estimated_hours)
def estimated_hours
@estimated_hours ||= sum(:estimated_hours).to_f
end
#
# Returns the total amount of open issues for this version.
def open_count
load_counts
@open_count
end
# Returns the total amount of closed issues for this version.
def closed_count
load_counts
@closed_count
end
# Returns the total amount of closed issues for this version.
def closed_count
load_counts
@closed_count
# Returns the completion percentage of this version based on the amount of open/closed issues
# and the time spent on the open issues.
def completed_percent
if count == 0
0
elsif open_count == 0
100
else
issues_progress(false) + issues_progress(true)
end
end
# Returns the completion percentage of this version based on the amount of open/closed issues
# and the time spent on the open issues.
def completed_percent
if count == 0
0
elsif open_count == 0
100
else
issues_progress(false) + issues_progress(true)
end
# Returns the percentage of issues that have been marked as 'closed'.
def closed_percent
if count == 0
0
else
issues_progress(false)
end
end
# Returns the percentage of issues that have been marked as 'closed'.
def closed_percent
if count == 0
0
else
issues_progress(false)
end
end
private
private
def load_counts
unless @open_count
@open_count = 0
@closed_count = 0
self.group(:status).count.each do |status, count|
if status.is_closed?
@closed_count += count
else
@open_count += count
end
def load_counts
unless @open_count
@open_count = 0
@closed_count = 0
self.group(:status).count.each do |status, count|
if status.is_closed?
@closed_count += count
else
@open_count += count
end
end
end
end
# Returns the average estimated time of assigned issues
# or 1 if no issue has an estimated time
# Used to weight unestimated issues in progress calculation
def estimated_average
if @estimated_average.nil?
average = average(:estimated_hours).to_f
if average == 0
average = 1
end
@estimated_average = average
# Returns the average estimated time of assigned issues
# or 1 if no issue has an estimated time
# Used to weight unestimated issues in progress calculation
def estimated_average
if @estimated_average.nil?
average = average(:estimated_hours).to_f
if average == 0
average = 1
end
@estimated_average
@estimated_average = average
end
# Returns the total progress of open or closed issues. The returned percentage takes into account
# the amount of estimated time set for this version.
#
# Examples:
# issues_progress(true) => returns the progress percentage for open issues.
# issues_progress(false) => returns the progress percentage for closed issues.
def issues_progress(open)
@issues_progress ||= {}
@issues_progress[open] ||= begin
progress = 0
if count > 0
ratio = open ? 'done_ratio' : 100
done = open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
progress = done / (estimated_average * count)
end
progress
@estimated_average
end
# Returns the total progress of open or closed issues. The returned percentage takes into account
# the amount of estimated time set for this version.
#
# Examples:
# issues_progress(true) => returns the progress percentage for open issues.
# issues_progress(false) => returns the progress percentage for closed issues.
def issues_progress(open)
@issues_progress ||= {}
@issues_progress[open] ||= begin
progress = 0
if count > 0
ratio = open ? 'done_ratio' : 100
done = open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
progress = done / (estimated_average * count)
end
progress
end
end
end
class Version < ActiveRecord::Base
include Redmine::SafeAttributes
after_update :update_issues_from_sharing_change
after_save :update_default_project_version
before_destroy :nullify_projects_default_version
belongs_to :project
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify, :extend => FixedIssuesExtension
acts_as_customizable
acts_as_attachable :view_permission => :view_files,
......@@ -349,7 +351,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
......
......@@ -11,7 +11,7 @@
<% end %>
</table>
<br />
<div class="box">
<div class="box autoscroll">
<pre><%= Redmine::Info.environment %></pre>
</div>
......
......@@ -29,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();
},
......
......@@ -134,7 +134,7 @@
<% end %>
<% unless @issue %>
<li><%= context_menu_link l(:button_filter), _project_issues_path(@project, :set_filter => 1, :status_id => "*", :issue_id => @issue_ids.join(",")),
<li><%= context_menu_link l(:button_filter), _project_issues_path(@project, :set_filter => 1, :status_id => "*", :issue_id => @issue_ids.join(","), :c => @columns),
:class => 'icon icon-list' %></li>
<% end %>
......
......@@ -6,41 +6,43 @@
<% delete_allowed = User.current.allowed_to?(:manage_files, @project) %>
<table class="list files">
<thead><tr>
<%= 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') %>
<th><%= l(:field_digest) %></th>
<th></th>
</tr></thead>
<tbody>
<% @containers.each do |container| %>
<% next if container.attachments.empty? -%>
<% if container.is_a?(Version) -%>
<tr>
<th colspan="6">
<%= link_to(container, {:controller => 'versions', :action => 'show', :id => container}, :class => "icon icon-package") %>
</th>
</tr>
<% end -%>
<% container.attachments.each do |file| %>
<tr class="file">
<td class="filename"><%= link_to_attachment file, :title => file.description -%></td>
<td class="created_on"><%= format_time(file.created_on) %></td>
<td class="filesize"><%= number_to_human_size(file.filesize) %></td>
<td class="downloads"><%= file.downloads %></td>
<td class="digest"><%= file.digest_type %>: <%= file.digest %></td>
<td class="buttons">
<%= 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 %>
</td>
</tr>
<div class="autoscroll">
<table class="list files">
<thead><tr>
<%= 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') %>
<th><%= l(:field_digest) %></th>
<th></th>
</tr></thead>
<tbody>
<% @containers.each do |container| %>
<% next if container.attachments.empty? -%>
<% if container.is_a?(Version) -%>
<tr>
<th colspan="6">
<%= link_to(container, {:controller => 'versions', :action => 'show', :id => container}, :class => "icon icon-package") %>
</th>
</tr>
<% end -%>
<% container.attachments.each do |file| %>
<tr class="file">
<td class="filename"><%= link_to_attachment file, :title => file.description -%></td>
<td class="created_on"><%= format_time(file.created_on) %></td>
<td class="filesize"><%= number_to_human_size(file.filesize) %></td>
<td class="downloads"><%= file.downloads %></td>
<td class="digest"><%= file.digest_type %>: <%= file.digest %></td>
<td class="buttons">
<%= 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 %>
</td>
</tr>
<% end %>
<% end %>
<% end %>
</tbody>
</table>
</tbody>
</table>
</div>
<% html_title(l(:label_attachment_plural)) -%>
......@@ -137,13 +137,14 @@
<%= content_tag(:div, :style => style, :class => "gantt_subjects_container") do %>
<%
style = ""
style += "width: #{subject_width}px;"
style += "width: #{subject_width + 1}px;"
style += "height: #{headers_height}px;"
style += 'background: #eee;'
%>
<%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %>
<%
style = ""
style += "z-index: 1;"
style += "width: #{subject_width}px;"
style += "height: #{t_height}px;"
style += 'border-left: 1px solid #c0c0c0;'
......
......@@ -8,10 +8,10 @@
</div>
</fieldset>
<div class="autoscroll">
<fieldset class="box">
<legend><%= l(:label_file_content_preview) %></legend>
<div class="autoscroll">
<table class="sample-data">
<% @import.first_rows.each do |row| %>
<tr>
......@@ -19,8 +19,8 @@
</tr>
<% end %>
</table>
</div>
</fieldset>
</div>
<p>
<%= button_tag("\xc2\xab " + l(:label_previous), :name => 'previous') %>
......
......@@ -3,6 +3,7 @@
<%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%>
<%= hidden_field_tag 'back_url', url_for(:params => request.query_parameters), :id => nil %>
<%= query_columns_hidden_tags(query) %>
<div class="autoscroll">
<table class="list issues odd-even <%= query.css_classes %>">
<thead>
......
......@@ -120,6 +120,8 @@ end %>
</div>
<%= render partial: 'action_menu_edit' if User.current.wants_comments_in_reverse_order? %>
<% if @changesets.present? %>
<div id="issue-changesets">
<h3><%=l(:label_associated_revisions)%></h3>
......@@ -127,8 +129,6 @@ end %>
</div>
<% end %>
<%= render partial: 'action_menu_edit' if User.current.wants_comments_in_reverse_order? %>
<% if @journals.present? %>
<div id="history">
<h3><%=l(:label_history)%></h3>
......
......@@ -78,7 +78,7 @@
<%= hidden_field_tag 'scope', default_search_project_scope, :id => nil %>
<%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %>
<label for='q'>
<%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project}, :accesskey => accesskey(:search) %>:
<%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project, :scope => default_search_project_scope}, :accesskey => accesskey(:search) %>:
</label>
<%= text_field_tag 'q', @question, :size => 20, :class => 'small', :accesskey => accesskey(:quick_search) %>
<% end %>
......@@ -119,7 +119,7 @@
<div id="footer">
<div class="bgl"><div class="bgr">
Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> &copy; 2006-2018 Jean-Philippe Lang
Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> &copy; 2006-2019 Jean-Philippe Lang
</div></div>
</div>
</div>
......
......@@ -29,7 +29,7 @@
<li><span class="label"><%=l(:field_homepage)%>:</span> <%= link_to_if uri_with_safe_scheme?(@project.homepage), @project.homepage, @project.homepage %></li>
<% end %>
<% render_custom_field_values(@project) do |custom_field, formatted| %>
<li><span class="label"><%= custom_field.name %>:</span> <%= formatted %></li>
<li class="cf_<%= custom_field.id %>"><span class="label"><%= custom_field.name %>:</span> <%= formatted %></li>
<% end %>
</ul>
<% end %>
......
......@@ -40,6 +40,8 @@
<% else %>
<fieldset id="options"><legend><%= l(:label_options) %></legend>
<p><label><%= l(:button_show) %></label>
<%= hidden_field_tag 'query[draw_relations]', '0' %>
<%= hidden_field_tag 'query[draw_progress_line]', '0' %>
<label class="inline"><%= check_box_tag "query[draw_relations]", "1", @query.draw_relations %> <%= l(:label_related_issues) %></label>
<label class="inline"><%= check_box_tag "query[draw_progress_line]", "1", @query.draw_progress_line %> <%= l(:label_gantt_progress_line) %></label>
</p>
......
......@@ -20,7 +20,7 @@ end %>
:repository_id => @repository.identifier_param, :path => to_path_param(path)},
:method => :get
) do %>
<% show_diff = revisions.size > 1 %>
<% show_diff = revisions.size > 1 && User.current.allowed_to?(:browse_repository, @repository.project) %>
<%= submit_tag(l(:label_view_diff), :name => nil) if show_diff %>
<table class="list changesets">
<thead><tr>
......
......@@ -11,11 +11,14 @@
<label><%= check_box_tag 'titles_only', 1, @titles_only %> <%= l(:label_search_titles_only) %></label>
</p>
<p id="search-types">
<% @object_types.each do |t| %>
<label><%= check_box_tag t, 1, @scope.include?(t) %> <%= link_to type_label(t), "#" %></label>
<% end %>
</p>
<fieldset class="box">
<legend><%= toggle_checkboxes_link('p#search-types input') %></legend>
<p id="search-types">
<% @object_types.each do |t| %>
<label><%= check_box_tag t, 1, @scope.include?(t) %> <%= link_to type_label(t), "#" %></label>
<% end %>
</p>
</fieldset>
<fieldset class="collapsible collapsed">
<legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
......
......@@ -88,8 +88,8 @@
$(document).ready(function(){
$('input#csv-export-button').click(function(){
$('form input#encoding').val($('select#encoding option:selected').val());
$('form#query_form').attr('action', '<%= report_time_entries_path(:format => 'csv') %>').submit();
$('form#query_form').attr('action', '<%= report_time_entries_path %>');
$('form#query_form').attr('action', '<%= _report_time_entries_path(@project, nil, :format => 'csv') %>').submit();
$('form#query_form').attr('action', '<%= _report_time_entries_path(@project, nil) %>');
hideModal(this);
});
});
......
......@@ -14,7 +14,7 @@
<% end %>
<% @user.visible_custom_field_values.each do |custom_value| %>
<% if !custom_value.value.blank? %>
<li><%= custom_value.custom_field.name %>: <%= show_value(custom_value) %></li>
<li class="cf_<%= custom_value.custom_field.id %>"><%= custom_value.custom_field.name %>: <%= show_value(custom_value) %></li>
<% end %>
<% end %>
<li><%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %></li>
......
......@@ -1231,4 +1231,4 @@ bg:
label_ldaps_verify_none: LDAP удостоверяване
label_ldaps_verify_peer: LDAPS
label_ldaps_warning: препоръчително е да се използва криптирана LDAPS връзка със сертификат за предотвратяване на намеса по време на процеса на удостоверяване.
error_token_expired: This password recovery link has expired, please try again.
error_token_expired: Връзката за възстановяване на паролата вече е невалидна. Опитайте отново.
......@@ -1262,4 +1262,4 @@ gl:
label_ldaps_warning: Se recomenda usar unha conexión LDAPS con comprobación de
certificado para evitar calquera manipulación durante o proceso de autenticación.
label_nothing_to_preview: Nada que previsualizar
error_token_expired: This password recovery link has expired, please try again.
error_token_expired: Esta ligazón para recuperar a contrasinal ten caducado, por favor, tente de novo.
......@@ -1211,41 +1211,36 @@ 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
setting_close_duplicate_issues: Close duplicate issues automatically
error_exceeds_maximum_hours_per_day: Cannot log more than %{max_hours} hours on the
same day (%{logged_hours} hours have already been logged)
setting_time_entry_list_defaults: Timelog list defaults
setting_timelog_accept_0_hours: Accept time logs with 0 hours
setting_timelog_max_hours_per_day: Maximum hours that can be logged per day and user
label_x_revisions: "%{count} revisions"
error_can_not_delete_auth_source: This authentication mode is in use and cannot be
deleted.
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: Завантажити
setting_close_duplicate_issues: Закривати дублі задач автоматично
error_exceeds_maximum_hours_per_day: Неможливо зареєструвати більше ніж %{max_hours} годин за один день
(%{logged_hours} годин вже зареєстровано)
setting_time_entry_list_defaults: Перелік трудозатрат по замовчуванню
setting_timelog_accept_0_hours: Приймати порожні трудозатрати із 0 годин
setting_timelog_max_hours_per_day: Максимальна кількість годин що можна зазвітувати користувачеві на дешь
label_x_revisions: "%{count} версій"
error_can_not_delete_auth_source: Цей метод аутентифікації використовується і не може бути видалений.
button_actions: Actions
mail_body_lost_password_validity: Please be aware that you may change the password
only once using this link.
text_login_required_html: When not requiring authentication, public projects and their
contents are openly available on the network. You can <a href="%{anonymous_role_path}">edit
the applicable permissions</a>.
label_login_required_yes: 'Yes'
label_login_required_no: No, allow anonymous access to public projects
text_project_is_public_non_member: Public projects and their contents are available
to all logged-in users.
text_project_is_public_anonymous: Public projects and their contents are openly available
on the network.
label_version_and_files: Versions (%{count}) and Files
mail_body_lost_password_validity: Ви можете змінити пароль лише один раз використовуючи це посилання.
text_login_required_html: Без вимоги аутентифікації, публічні проекти та їх вміст вільно доступні в мережі. Ви можете <a href="%{anonymous_role_path}">змінити
необхідні права</a>.
label_login_required_yes: 'Так'
label_login_required_no: Ні, дозволити анонімний доступ до публічних проектів
text_project_is_public_non_member: Публічні проекти та їх вміст доступні всім зареєстрованим користувачам.
text_project_is_public_anonymous: Публічні проекти та їх вміст вільно доступні в мережі.
label_version_and_files: Версій (%{count}) і Файлів
label_ldap: LDAP
label_ldaps_verify_none: LDAPS (without certificate check)
label_ldaps_verify_none: LDAPS (без перевірки сертифікату)
label_ldaps_verify_peer: LDAPS
label_ldaps_warning: It is recommended to use an encrypted LDAPS connection with certificate
check to prevent any manipulation during the authentication process.
label_nothing_to_preview: Nothing to preview
error_token_expired: This password recovery link has expired, please try again.
label_ldaps_warning: Рекомендовано використовувати шифроване LDAPS з'єднання з перевіркою сертифікату
щоби попередити маніпуляції під час процесу аутентифікації.
label_nothing_to_preview: Нічого не має для перегляду
error_token_expired: Це посилання відновлення паролю застаріло, буль ласка спробуйте заново.
......@@ -1309,9 +1309,8 @@
text_project_is_public_anonymous: 公開之專案及其內容, 在網路上是開放可用的.
label_version_and_files: 版本 (%{count}) 與檔案
label_ldap: LDAP
label_ldaps_verify_none: LDAPS (without certificate check)
label_ldaps_verify_none: LDAPS (不含 SSL 憑證檢查)
label_ldaps_verify_peer: LDAPS
label_ldaps_warning: It is recommended to use an encrypted LDAPS connection with certificate
check to prevent any manipulation during the authentication process.
label_nothing_to_preview: Nothing to preview
error_token_expired: This password recovery link has expired, please try again.
label_ldaps_warning: 建議使用包含 SSL 憑證檢查的加密 LADPS 協定,以防止驗證過程被竄改操縱。
label_nothing_to_preview: 沒有可供預覽的項目
error_token_expired: 此密碼復原連結已經過期, 請再試一次.
......@@ -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: 根据此条件分组
......@@ -391,9 +391,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: 编辑耗时
......@@ -673,7 +673,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: 载入中...
......@@ -729,7 +729,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: 手动激活帐号
......@@ -749,7 +749,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: 排序
......@@ -809,8 +809,8 @@ zh:
button_sort: 排序
button_log_time: 登记工时
button_rollback: 恢复到这个版本
button_watch: 跟踪
button_unwatch: 取消跟踪
button_watch: 关注
button_unwatch: 取消关注
button_reply: 回复
button_archive: 存档
button_unarchive: 取消存档
......@@ -864,7 +864,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}."
......@@ -1006,7 +1006,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: 删除我的账号
......
......@@ -246,7 +246,7 @@ Rails.application.routes.draw do
post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue'
delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions'
%w(browse show entry raw annotate diff).each do |action|
%w(browse show entry raw annotate).each do |action|
get "projects/:id/repository/:repository_id/revisions/:rev/#{action}(/*path)",
:controller => 'repositories',
:action => action,
......@@ -254,7 +254,7 @@ Rails.application.routes.draw do
:constraints => {:rev => /[a-z0-9\.\-_]+/, :path => /.*/}
end
%w(browse entry raw changes annotate diff).each do |action|
%w(browse entry raw changes annotate).each do |action|
get "projects/:id/repository/:repository_id/#{action}(/*path)",
:controller => 'repositories',
:action => action,
......@@ -262,6 +262,15 @@ Rails.application.routes.draw do
:constraints => {:path => /.*/}
end
get "projects/:id/repository/:repository_id/revisions/:rev/diff(/*path)",
:to => 'repositories#diff',
:format => false,
:constraints => {:rev => /[a-z0-9\.\-_]+/, :path => /.*/}
get "projects/:id/repository/:repository_id/diff(/*path)",
:to => 'repositories#diff',
:format => false,
:constraints => {:path => /.*/}
get 'projects/:id/repository/:repository_id/show/*path', :to => 'repositories#show', :format => 'html', :constraints => {:path => /.*/}
get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil
......
This diff is collapsed.
......@@ -7,7 +7,7 @@ http://www.redmine.org/
== Requirements
* Ruby >= 2.2.1
* Ruby >= 2.2.2
* RubyGems
* Bundler >= 1.5.0
......@@ -29,7 +29,7 @@ Optional:
2. Create an empty utf8 encoded database: "redmine" for example
3. Configure the database parameters in config/database.yml
for the "production" environment (default database is MySQL and ruby1.9)
for the "production" environment (default database is MySQL)
4. Install the required gems by running:
bundle install --without development test
......
......@@ -27,7 +27,7 @@ module Redmine
return if self.included_modules.include?(Redmine::Acts::Customizable::InstanceMethods)
cattr_accessor :customizable_options
self.customizable_options = options
has_many :custom_values, lambda {includes(:custom_field).order("#{CustomField.table_name}.position")},
has_many :custom_values, lambda {includes(:custom_field)},
:as => :customized,
:inverse_of => :customized,
:dependent => :delete_all,
......
......@@ -38,6 +38,7 @@ require 'redmine/activity/fetcher'
require 'redmine/ciphering'
require 'redmine/codeset_util'
require 'redmine/field_format'
require 'redmine/info'
require 'redmine/menu_manager'
require 'redmine/notifiable'
require 'redmine/platform'
......
<
......@@ -33,7 +33,7 @@ module Redmine