Redmine 4.1.7
This commit is contained in:
parent
55458d3479
commit
3ca3c37487
103 changed files with 2426 additions and 431 deletions
|
@ -297,6 +297,7 @@ class AccountController < ApplicationController
|
|||
:value => token,
|
||||
:expires => 1.year.from_now,
|
||||
:path => (Redmine::Configuration['autologin_cookie_path'] || RedmineApp::Application.config.relative_url_root || '/'),
|
||||
:same_site => :lax,
|
||||
:secure => secure,
|
||||
:httponly => true
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ class ActivitiesController < ApplicationController
|
|||
@date_from = @date_to - @days
|
||||
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
|
||||
if params[:user_id].present?
|
||||
@author = User.active.find(params[:user_id])
|
||||
@author = User.visible.active.find(params[:user_id])
|
||||
end
|
||||
|
||||
@activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
|
||||
|
@ -55,7 +55,12 @@ class ActivitiesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
events = @activity.events(@date_from, @date_to)
|
||||
events =
|
||||
if params[:format] == 'atom'
|
||||
@activity.events(nil, nil, :limit => Setting.feeds_limit.to_i)
|
||||
else
|
||||
@activity.events(@date_from, @date_to)
|
||||
end
|
||||
|
||||
if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, events.first, events.size, User.current, current_language])
|
||||
respond_to do |format|
|
||||
|
|
|
@ -101,7 +101,7 @@ class AttachmentsController < ApplicationController
|
|||
return
|
||||
end
|
||||
|
||||
@attachment = Attachment.new(:file => request.raw_post)
|
||||
@attachment = Attachment.new(:file => raw_request_body)
|
||||
@attachment.author = User.current
|
||||
@attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
|
||||
@attachment.content_type = params[:content_type].presence
|
||||
|
@ -265,4 +265,14 @@ class AttachmentsController < ApplicationController
|
|||
def update_all_params
|
||||
params.permit(:attachments => [:filename, :description]).require(:attachments)
|
||||
end
|
||||
|
||||
# Get an IO-like object for the request body which is usable to create a new
|
||||
# attachment. We try to avoid having to read the whole body into memory.
|
||||
def raw_request_body
|
||||
if request.body.respond_to?(:size)
|
||||
request.body
|
||||
else
|
||||
request.raw_post
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class MailHandlerController < ActionController::Base
|
||||
include ActiveSupport::SecurityUtils
|
||||
|
||||
before_action :check_credential
|
||||
|
||||
# Displays the email submission form
|
||||
|
@ -39,7 +41,7 @@ class MailHandlerController < ActionController::Base
|
|||
|
||||
def check_credential
|
||||
User.current = nil
|
||||
unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key
|
||||
unless Setting.mail_handler_api_enabled? && secure_compare(params[:key].to_s, Setting.mail_handler_api_key.to_s)
|
||||
render :plain => 'Access denied. Incoming emails WS is disabled or key is invalid.', :status => 403
|
||||
end
|
||||
end
|
||||
|
|
|
@ -307,7 +307,7 @@ class RepositoriesController < ApplicationController
|
|||
render_404
|
||||
end
|
||||
|
||||
REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
|
||||
REV_PARAM_RE = %r{\A[a-f0-9]*\z}i
|
||||
|
||||
def find_project_repository
|
||||
@project = Project.find(params[:id])
|
||||
|
@ -318,14 +318,12 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
(render_404; return false) unless @repository
|
||||
@path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s
|
||||
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip
|
||||
@rev_to = params[:rev_to]
|
||||
|
||||
unless REV_PARAM_RE.match?(@rev.to_s) && REV_PARAM_RE.match?(@rev_to.to_s)
|
||||
if @repository.branches.blank?
|
||||
raise InvalidRevisionParam
|
||||
end
|
||||
end
|
||||
@rev = params[:rev].to_s.strip.presence || @repository.default_branch
|
||||
raise InvalidRevisionParam unless valid_name?(@rev)
|
||||
|
||||
@rev_to = params[:rev_to].to_s.strip.presence
|
||||
raise InvalidRevisionParam unless valid_name?(@rev_to)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
rescue InvalidRevisionParam
|
||||
|
@ -410,4 +408,11 @@ class RepositoriesController < ApplicationController
|
|||
'attachment'
|
||||
end
|
||||
end
|
||||
|
||||
def valid_name?(rev)
|
||||
return true if rev.nil?
|
||||
return true if REV_PARAM_RE.match?(rev)
|
||||
|
||||
@repository ? @repository.valid_name?(rev) : true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,7 +63,7 @@ class SearchController < ApplicationController
|
|||
@object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)}
|
||||
end
|
||||
|
||||
@scope = @object_types.select {|t| params[t]}
|
||||
@scope = @object_types.select {|t| params[t].present?}
|
||||
@scope = @object_types if @scope.empty?
|
||||
|
||||
fetcher = Redmine::Search::Fetcher.new(
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class SysController < ActionController::Base
|
||||
include ActiveSupport::SecurityUtils
|
||||
|
||||
before_action :check_enabled
|
||||
|
||||
def projects
|
||||
|
@ -76,7 +78,7 @@ class SysController < ActionController::Base
|
|||
|
||||
def check_enabled
|
||||
User.current = nil
|
||||
unless Setting.sys_api_enabled? && params[:key].to_s == Setting.sys_api_key
|
||||
unless Setting.sys_api_enabled? && secure_compare(params[:key].to_s, Setting.sys_api_key.to_s)
|
||||
render :plain => 'Access denied. Repository management WS is disabled or key is invalid.', :status => 403
|
||||
return false
|
||||
end
|
||||
|
|
|
@ -134,7 +134,9 @@ class WatchersController < ApplicationController
|
|||
|
||||
def find_objets_from_params
|
||||
klass = Object.const_get(params[:object_type].camelcase) rescue nil
|
||||
return unless klass && klass.respond_to?('watched_by')
|
||||
return unless klass && Class === klass # rubocop:disable Style/CaseEquality
|
||||
return unless klass < ActiveRecord::Base
|
||||
return unless klass < Redmine::Acts::Watchable::InstanceMethods
|
||||
|
||||
scope = klass.where(:id => Array.wrap(params[:object_id]))
|
||||
if klass.reflect_on_association(:project)
|
||||
|
|
|
@ -44,8 +44,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
|
||||
|
@ -91,7 +89,7 @@ class WikiController < ApplicationController
|
|||
end
|
||||
@content = @page.content_for_version(params[:version])
|
||||
if @content.nil?
|
||||
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
|
||||
if params[:version].blank? && User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
|
||||
edit
|
||||
render :action => 'edit'
|
||||
else
|
||||
|
@ -111,7 +109,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
|
||||
|
|
|
@ -1190,7 +1190,7 @@ module ApplicationHelper
|
|||
)|
|
||||
(
|
||||
(?<sep4>@)
|
||||
(?<identifier3>[A-Za-z0-9_\-@\.]*)
|
||||
(?<identifier3>[A-Za-z0-9_\-@\.]*?)
|
||||
)
|
||||
)
|
||||
(?=
|
||||
|
@ -1596,7 +1596,7 @@ module ApplicationHelper
|
|||
|
||||
# Returns the javascript tags that are included in the html layout head
|
||||
def javascript_heads
|
||||
tags = javascript_include_tag('jquery-2.2.4-ui-1.11.0-ujs-5.2.3', 'tribute-3.7.3.min', 'application', 'responsive')
|
||||
tags = javascript_include_tag('jquery-2.2.4-ui-1.11.0-ujs-5.2.4.5', 'tribute-3.7.3.min', 'application', 'responsive')
|
||||
unless User.current.pref.warn_on_leaving_unsaved == '0'
|
||||
tags << "\n".html_safe + javascript_tag("$(window).on('load', function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
|
||||
end
|
||||
|
|
|
@ -217,6 +217,11 @@ module IssuesHelper
|
|||
if issue.total_spent_hours == issue.spent_hours
|
||||
link_to(l_hours_short(issue.spent_hours), path)
|
||||
else
|
||||
# link to global time entries if cross-project subtasks are allowed
|
||||
# in order to show time entries from not descendents projects
|
||||
if %w(system tree hierarchy).include?(Setting.cross_project_subtasks)
|
||||
path = time_entries_path(:issue_id => "~#{issue.id}")
|
||||
end
|
||||
s = issue.spent_hours > 0 ? l_hours_short(issue.spent_hours) : ""
|
||||
s += " (#{l(:label_total)}: #{link_to l_hours_short(issue.total_spent_hours), path})"
|
||||
s.html_safe
|
||||
|
@ -551,7 +556,7 @@ module IssuesHelper
|
|||
unless no_html
|
||||
diff_link =
|
||||
link_to(
|
||||
'diff',
|
||||
l(:label_diff),
|
||||
diff_journal_url(detail.journal_id, :detail_id => detail.id,
|
||||
:only_path => options[:only_path]),
|
||||
:title => l(:label_view_diff))
|
||||
|
@ -577,6 +582,7 @@ module IssuesHelper
|
|||
end
|
||||
|
||||
# Find the name of an associated record stored in the field attribute
|
||||
# For project, return the associated record only if is visible for the current User
|
||||
def find_name_by_reflection(field, id)
|
||||
return nil if id.blank?
|
||||
@detail_value_name_by_reflection ||= Hash.new do |hash, key|
|
||||
|
@ -584,7 +590,7 @@ module IssuesHelper
|
|||
name = nil
|
||||
if association
|
||||
record = association.klass.find_by_id(key.last)
|
||||
if record
|
||||
if (record && !record.is_a?(Project)) || (record.is_a?(Project) && record.visible?)
|
||||
name = record.name.force_encoding('UTF-8')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,7 +22,7 @@ module JournalsHelper
|
|||
# Returns the attachments of a journal that are displayed as thumbnails
|
||||
def journal_thumbnail_attachments(journal)
|
||||
ids = journal.details.select {|d| d.property == 'attachment' && d.value.present?}.map(&:prop_key)
|
||||
ids.any? ? Attachment.where(:id => ids).select(&:thumbnailable?) : []
|
||||
ids.any? ? Attachment.where(:id => ids).select(&:thumbnailable?).sort_by{|a| ids.index(a.id.to_s)} : []
|
||||
end
|
||||
|
||||
# Returns the action links for an issue journal
|
||||
|
|
|
@ -167,7 +167,7 @@ module QueriesHelper
|
|||
def total_tag(column, value)
|
||||
label = content_tag('span', "#{column.caption}:")
|
||||
value =
|
||||
if [:hours, :spent_hours, :total_spent_hours, :estimated_hours].include? column.name
|
||||
if [:hours, :spent_hours, :total_spent_hours, :estimated_hours, :total_estimated_hours].include? column.name
|
||||
format_hours(value)
|
||||
else
|
||||
format_object(value)
|
||||
|
@ -238,7 +238,7 @@ module QueriesHelper
|
|||
'span',
|
||||
value.to_s(item) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
|
||||
:class => value.css_classes_for(item))
|
||||
when :hours, :estimated_hours
|
||||
when :hours, :estimated_hours, :total_estimated_hours
|
||||
format_hours(value)
|
||||
when :spent_hours
|
||||
link_to_if(value > 0, format_hours(value), project_time_entries_path(item.project, :issue_id => "#{item.id}"))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -44,7 +44,7 @@ module TimelogHelper
|
|||
|
||||
def user_collection_for_select_options(time_entry)
|
||||
collection = time_entry.assignable_users
|
||||
collection << time_entry.user unless time_entry.user.nil? && !collection.include?(time_entry.user)
|
||||
collection << time_entry.user if time_entry.user && !collection.include?(time_entry.user)
|
||||
principals_options_for_select(collection, time_entry.user_id.to_s)
|
||||
end
|
||||
|
||||
|
|
|
@ -29,7 +29,8 @@ class Attachment < ActiveRecord::Base
|
|||
validates_length_of :filename, :maximum => 255
|
||||
validates_length_of :disk_filename, :maximum => 255
|
||||
validates_length_of :description, :maximum => 255
|
||||
validate :validate_max_file_size, :validate_file_extension
|
||||
validate :validate_max_file_size
|
||||
validate :validate_file_extension, :if => :filename_changed?
|
||||
|
||||
acts_as_event :title => :filename,
|
||||
:url => Proc.new {|o| {:controller => 'attachments', :action => 'show', :id => o.id, :filename => o.filename}}
|
||||
|
@ -76,11 +77,9 @@ class Attachment < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def validate_file_extension
|
||||
if @temp_file
|
||||
extension = File.extname(filename)
|
||||
unless self.class.valid_extension?(extension)
|
||||
errors.add(:base, l(:error_attachment_extension_not_allowed, :extension => extension))
|
||||
end
|
||||
extension = File.extname(filename)
|
||||
unless self.class.valid_extension?(extension)
|
||||
errors.add(:base, l(:error_attachment_extension_not_allowed, :extension => extension))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -436,15 +435,15 @@ class Attachment < ActiveRecord::Base
|
|||
private
|
||||
|
||||
def reuse_existing_file_if_possible
|
||||
original_diskfile = nil
|
||||
original_diskfile = diskfile
|
||||
original_filename = disk_filename
|
||||
reused = with_lock do
|
||||
if existing = Attachment
|
||||
.where(digest: self.digest, filesize: self.filesize)
|
||||
.where('id <> ? and disk_filename <> ?',
|
||||
self.id, self.disk_filename)
|
||||
.first
|
||||
.where.not(disk_filename: original_filename)
|
||||
.order(:id)
|
||||
.last
|
||||
existing.with_lock do
|
||||
original_diskfile = self.diskfile
|
||||
existing_diskfile = existing.diskfile
|
||||
if File.readable?(original_diskfile) &&
|
||||
File.readable?(existing_diskfile) &&
|
||||
|
@ -455,7 +454,7 @@ class Attachment < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
end
|
||||
if reused
|
||||
if reused && Attachment.where(disk_filename: original_filename).none?
|
||||
File.delete(original_diskfile)
|
||||
end
|
||||
rescue ActiveRecord::StatementInvalid, ActiveRecord::RecordNotFound
|
||||
|
|
|
@ -201,6 +201,11 @@ class Issue < ActiveRecord::Base
|
|||
user_tracker_permission?(user, :delete_issues)
|
||||
end
|
||||
|
||||
# Overrides Redmine::Acts::Attachable::InstanceMethods#attachments_deletable?
|
||||
def attachments_deletable?(user=User.current)
|
||||
attributes_editable?(user)
|
||||
end
|
||||
|
||||
def initialize(attributes=nil, *args)
|
||||
super
|
||||
if new_record?
|
||||
|
@ -471,7 +476,6 @@ class Issue < ActiveRecord::Base
|
|||
'custom_field_values',
|
||||
'custom_fields',
|
||||
'lock_version',
|
||||
'notes',
|
||||
:if => lambda {|issue, user| issue.new_record? || issue.attributes_editable?(user)})
|
||||
safe_attributes(
|
||||
'notes',
|
||||
|
@ -722,7 +726,7 @@ class Issue < ActiveRecord::Base
|
|||
errors.add :start_date, :earlier_than_minimum_start_date, :date => format_date(soonest_start)
|
||||
end
|
||||
|
||||
if fixed_version
|
||||
if project && fixed_version
|
||||
if !assignable_versions.include?(fixed_version)
|
||||
errors.add :fixed_version_id, :inclusion
|
||||
elsif reopening? && fixed_version.closed?
|
||||
|
@ -737,7 +741,7 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
if assigned_to_id_changed? && assigned_to_id.present?
|
||||
if project && assigned_to_id_changed? && assigned_to_id.present?
|
||||
unless assignable_users.include?(assigned_to)
|
||||
errors.add :assigned_to_id, :invalid
|
||||
end
|
||||
|
@ -937,6 +941,8 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
# Users the issue can be assigned to
|
||||
def assignable_users
|
||||
return [] if project.nil?
|
||||
|
||||
users = project.assignable_users(tracker).to_a
|
||||
users << author if author && author.active?
|
||||
if assigned_to_id_was.present? && assignee = Principal.find_by_id(assigned_to_id_was)
|
||||
|
@ -948,6 +954,7 @@ class Issue < ActiveRecord::Base
|
|||
# Versions that the issue can be assigned to
|
||||
def assignable_versions
|
||||
return @assignable_versions if @assignable_versions
|
||||
return [] if project.nil?
|
||||
|
||||
versions = project.shared_versions.open.to_a
|
||||
if fixed_version
|
||||
|
@ -1704,12 +1711,12 @@ class Issue < ActiveRecord::Base
|
|||
if children.any?
|
||||
child_with_total_estimated_hours = children.select {|c| c.total_estimated_hours.to_f > 0.0}
|
||||
if child_with_total_estimated_hours.any?
|
||||
average = child_with_total_estimated_hours.map(&:total_estimated_hours).sum.to_f / child_with_total_estimated_hours.count
|
||||
average = child_with_total_estimated_hours.map(&:total_estimated_hours).sum.to_d / child_with_total_estimated_hours.count
|
||||
else
|
||||
average = 1.0
|
||||
average = 1.0.to_d
|
||||
end
|
||||
done = children.map {|c|
|
||||
estimated = c.total_estimated_hours.to_f
|
||||
estimated = (c.total_estimated_hours || 0.0).to_d
|
||||
estimated = average unless estimated > 0.0
|
||||
ratio = c.closed? ? 100 : (c.done_ratio || 0)
|
||||
estimated * ratio
|
||||
|
@ -1734,8 +1741,8 @@ class Issue < ActiveRecord::Base
|
|||
# a different project and that is not systemwide shared
|
||||
Issue.joins(:project, :fixed_version).
|
||||
where("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
|
||||
" AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
|
||||
" AND #{Version.table_name}.sharing <> 'system'").
|
||||
" AND #{Issue.table_name}.project_id <> #{::Version.table_name}.project_id" +
|
||||
" AND #{::Version.table_name}.sharing <> 'system'").
|
||||
where(conditions).each do |issue|
|
||||
next if issue.project.nil? || issue.fixed_version.nil?
|
||||
unless issue.project.shared_versions.include?(issue.fixed_version)
|
||||
|
@ -1756,7 +1763,7 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
# Callback on file attachment
|
||||
def attachment_added(attachment)
|
||||
if current_journal && !attachment.new_record?
|
||||
if current_journal && !attachment.new_record? && !copy?
|
||||
current_journal.journalize_attachment(attachment, :added)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -513,7 +513,8 @@ class IssueQuery < Query
|
|||
|
||||
def sql_for_fixed_version_status_field(field, operator, value)
|
||||
where = sql_for_field(field, operator, value, Version.table_name, "status")
|
||||
version_ids = versions(:conditions => [where]).map(&:id)
|
||||
version_id_scope = project ? project.shared_versions : Version.visible
|
||||
version_ids = version_id_scope.where(where).pluck(:id)
|
||||
|
||||
nl = operator == "!" ? "#{Issue.table_name}.fixed_version_id IS NULL OR" : ''
|
||||
"(#{nl} #{sql_for_field("fixed_version_id", "=", version_ids, Issue.table_name, "fixed_version_id")})"
|
||||
|
@ -521,7 +522,8 @@ class IssueQuery < Query
|
|||
|
||||
def sql_for_fixed_version_due_date_field(field, operator, value)
|
||||
where = sql_for_field(field, operator, value, Version.table_name, "effective_date")
|
||||
version_ids = versions(:conditions => [where]).map(&:id)
|
||||
version_id_scope = project ? project.shared_versions : Version.visible
|
||||
version_ids = version_id_scope.where(where).pluck(:id)
|
||||
|
||||
nl = operator == "!*" ? "#{Issue.table_name}.fixed_version_id IS NULL OR" : ''
|
||||
"(#{nl} #{sql_for_field("fixed_version_id", "=", version_ids, Issue.table_name, "fixed_version_id")})"
|
||||
|
|
7
app/models/mail_handler.rb
Executable file → Normal file
7
app/models/mail_handler.rb
Executable file → Normal file
|
@ -225,9 +225,8 @@ class MailHandler < ActionMailer::Base
|
|||
|
||||
# check permission
|
||||
unless handler_options[:no_permission_check]
|
||||
unless user.allowed_to?(:add_issue_notes, issue.project) ||
|
||||
user.allowed_to?(:edit_issues, issue.project)
|
||||
raise UnauthorizedAction, "not allowed to add notes on issues to project [#{project.name}]"
|
||||
unless issue.notes_addable?
|
||||
raise UnauthorizedAction, "not allowed to add notes on issues to project [#{issue.project.name}]"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -276,7 +275,7 @@ class MailHandler < ActionMailer::Base
|
|||
end
|
||||
|
||||
unless handler_options[:no_permission_check]
|
||||
raise UnauthorizedAction, "not allowed to add messages to project [#{project.name}]" unless user.allowed_to?(:add_messages, message.project)
|
||||
raise UnauthorizedAction, "not allowed to add messages to project [#{message.project.name}]" unless user.allowed_to?(:add_messages, message.project)
|
||||
end
|
||||
|
||||
if !message.locked?
|
||||
|
|
|
@ -883,6 +883,17 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# Overrides Redmine::Acts::Customizable::InstanceMethods#validate_custom_field_values
|
||||
# so that custom values that are not editable are not validated (eg. a custom field that
|
||||
# is marked as required should not trigger a validation error if the user is not allowed
|
||||
# to edit this field).
|
||||
def validate_custom_field_values
|
||||
user = User.current
|
||||
if new_record? || custom_field_values_changed?
|
||||
editable_custom_field_values(user).each(&:validate_value)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the custom_field_values that can be edited by the given user
|
||||
def editable_custom_field_values(user=nil)
|
||||
visible_custom_field_values(user)
|
||||
|
|
|
@ -70,7 +70,7 @@ class ProjectQuery < Query
|
|||
def available_columns
|
||||
return @available_columns if @available_columns
|
||||
@available_columns = self.class.available_columns.dup
|
||||
@available_columns += ProjectCustomField.visible.
|
||||
@available_columns += project_custom_fields.visible.
|
||||
map {|cf| QueryCustomFieldColumn.new(cf) }
|
||||
@available_columns
|
||||
end
|
||||
|
|
|
@ -51,7 +51,7 @@ class QueryColumn
|
|||
|
||||
# Returns true if the column is sortable, otherwise false
|
||||
def sortable?
|
||||
!@sortable.nil?
|
||||
@sortable.present?
|
||||
end
|
||||
|
||||
def sortable
|
||||
|
@ -501,7 +501,7 @@ class Query < ActiveRecord::Base
|
|||
if has_filter?(field) || !filter.remote
|
||||
options[:values] = filter.values
|
||||
if options[:values] && values_for(field)
|
||||
missing = Array(values_for(field)).select(&:present?) - options[:values].map(&:last)
|
||||
missing = Array(values_for(field)).select(&:present?) - options[:values].map{|v| v[1]}
|
||||
if missing.any? && respond_to?(method = "find_#{field}_filter_values")
|
||||
options[:values] += send(method, missing)
|
||||
end
|
||||
|
@ -609,13 +609,18 @@ class Query < ActiveRecord::Base
|
|||
if project
|
||||
project.rolled_up_custom_fields
|
||||
else
|
||||
IssueCustomField.all
|
||||
IssueCustomField.sorted
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a scope of project custom fields that are available as columns or filters
|
||||
def project_custom_fields
|
||||
ProjectCustomField.all
|
||||
ProjectCustomField.sorted
|
||||
end
|
||||
|
||||
# Returns a scope of time entry custom fields that are available as columns or filters
|
||||
def time_entry_custom_fields
|
||||
TimeEntryCustomField.sorted
|
||||
end
|
||||
|
||||
# Returns a scope of project statuses that are available as columns or filters
|
||||
|
@ -866,10 +871,10 @@ class Query < ActiveRecord::Base
|
|||
|
||||
def project_statement
|
||||
project_clauses = []
|
||||
active_subprojects_ids = []
|
||||
subprojects_ids = []
|
||||
|
||||
active_subprojects_ids = project.descendants.active.map(&:id) if project
|
||||
if active_subprojects_ids.any?
|
||||
subprojects_ids = project.descendants.where.not(status: Project::STATUS_ARCHIVED).ids if project
|
||||
if subprojects_ids.any?
|
||||
if has_filter?("subproject_id")
|
||||
case operator_for("subproject_id")
|
||||
when '='
|
||||
|
@ -878,7 +883,7 @@ class Query < ActiveRecord::Base
|
|||
project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
|
||||
when '!'
|
||||
# exclude the selected subprojects
|
||||
ids = [project.id] + active_subprojects_ids - values_for("subproject_id").map(&:to_i)
|
||||
ids = [project.id] + subprojects_ids - values_for("subproject_id").map(&:to_i)
|
||||
project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
|
||||
when '!*'
|
||||
# main project only
|
||||
|
|
|
@ -461,6 +461,10 @@ class Repository < ActiveRecord::Base
|
|||
scope
|
||||
end
|
||||
|
||||
def valid_name?(name)
|
||||
scm.valid_name?(name)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Validates repository url based against an optional regular expression
|
||||
|
|
|
@ -100,7 +100,8 @@ class Setting < ActiveRecord::Base
|
|||
v = read_attribute(:value)
|
||||
# Unserialize serialized settings
|
||||
if available_settings[name]['serialized'] && v.is_a?(String)
|
||||
v = YAML::load(v)
|
||||
# YAML.load works as YAML.safe_load if Psych >= 4.0 is installed
|
||||
v = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(v) : YAML.load(v)
|
||||
v = force_utf8_strings(v)
|
||||
end
|
||||
v = v.to_sym if available_settings[name]['format'] == 'symbol' && !v.blank?
|
||||
|
|
|
@ -113,6 +113,13 @@ class TimeEntry < ActiveRecord::Base
|
|||
self.project_id = issue.project_id
|
||||
end
|
||||
@invalid_issue_id = nil
|
||||
elsif user.allowed_to?(:log_time, issue.project) && issue.assigned_to_id_changed? && issue.previous_assignee == User.current
|
||||
current_assignee = issue.assigned_to
|
||||
issue.assigned_to = issue.previous_assignee
|
||||
unless issue.visible?(user)
|
||||
@invalid_issue_id = issue_id
|
||||
end
|
||||
issue.assigned_to = current_assignee
|
||||
else
|
||||
@invalid_issue_id = issue_id
|
||||
end
|
||||
|
@ -122,7 +129,14 @@ class TimeEntry < ActiveRecord::Base
|
|||
else
|
||||
@invalid_user_id = nil
|
||||
end
|
||||
|
||||
# Delete assigned custom fields not visible by the user
|
||||
editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s}
|
||||
self.custom_field_values.delete_if do |c|
|
||||
!editable_custom_field_ids.include?(c.custom_field.id.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
attrs
|
||||
end
|
||||
|
||||
|
@ -193,7 +207,7 @@ class TimeEntry < ActiveRecord::Base
|
|||
|
||||
# Returns the custom_field_values that can be edited by the given user
|
||||
def editable_custom_field_values(user=nil)
|
||||
visible_custom_field_values
|
||||
visible_custom_field_values(user)
|
||||
end
|
||||
|
||||
# Returns the custom fields that can be edited by the given user
|
||||
|
|
|
@ -89,7 +89,7 @@ class TimeEntryQuery < Query
|
|||
activities = (project ? project.activities : TimeEntryActivity.shared)
|
||||
add_available_filter(
|
||||
"activity_id",
|
||||
:type => :list, :values => activities.map {|a| [a.name, a.id.to_s]}
|
||||
:type => :list, :values => activities.map {|a| [a.name, (a.parent_id || a.id).to_s]}
|
||||
)
|
||||
add_available_filter(
|
||||
"project.status",
|
||||
|
@ -101,7 +101,7 @@ class TimeEntryQuery < Query
|
|||
add_available_filter "comments", :type => :text
|
||||
add_available_filter "hours", :type => :float
|
||||
|
||||
add_custom_fields_filters(TimeEntryCustomField)
|
||||
add_custom_fields_filters(time_entry_custom_fields)
|
||||
add_associations_custom_fields_filters :project
|
||||
add_custom_fields_filters(issue_custom_fields, :issue)
|
||||
add_associations_custom_fields_filters :user
|
||||
|
@ -110,11 +110,11 @@ class TimeEntryQuery < Query
|
|||
def available_columns
|
||||
return @available_columns if @available_columns
|
||||
@available_columns = self.class.available_columns.dup
|
||||
@available_columns += TimeEntryCustomField.visible.
|
||||
@available_columns += time_entry_custom_fields.visible.
|
||||
map {|cf| QueryCustomFieldColumn.new(cf) }
|
||||
@available_columns += issue_custom_fields.visible.
|
||||
map {|cf| QueryAssociationCustomFieldColumn.new(:issue, cf, :totalable => false) }
|
||||
@available_columns += ProjectCustomField.visible.
|
||||
@available_columns += project_custom_fields.visible.
|
||||
map {|cf| QueryAssociationCustomFieldColumn.new(:project, cf) }
|
||||
@available_columns
|
||||
end
|
||||
|
|
|
@ -115,11 +115,13 @@ class Token < ActiveRecord::Base
|
|||
return nil unless action.present? && /\A[a-z0-9]+\z/i.match?(key)
|
||||
|
||||
token = Token.find_by(:action => action, :value => key)
|
||||
if token && (token.action == action) && (token.value == key) && token.user
|
||||
if validity_days.nil? || (token.created_on > validity_days.days.ago)
|
||||
token
|
||||
end
|
||||
end
|
||||
return unless token
|
||||
return unless token.action == action
|
||||
return unless ActiveSupport::SecurityUtils.secure_compare(token.value.to_s, key)
|
||||
return unless token.user
|
||||
return unless validity_days.nil? || (token.created_on > validity_days.days.ago)
|
||||
|
||||
token
|
||||
end
|
||||
|
||||
def self.generate_token_value
|
||||
|
|
|
@ -155,11 +155,7 @@ class WikiPage < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def content_for_version(version=nil)
|
||||
if content
|
||||
result = content.versions.find_by_version(version.to_i) if version
|
||||
result ||= content
|
||||
result
|
||||
end
|
||||
(content && version) ? content.versions.find_by_version(version.to_i) : content
|
||||
end
|
||||
|
||||
def diff(version_to=nil, version_from=nil)
|
||||
|
|
|
@ -1,28 +1,27 @@
|
|||
<%= call_hook :view_account_login_top %>
|
||||
|
||||
<div id="login-form">
|
||||
<h2><%= l(:label_login) %></h2>
|
||||
<%= form_tag(signin_path, onsubmit: 'return keepAnchorOnSignIn(this);') do %>
|
||||
<%= back_url_hidden_field_tag %>
|
||||
|
||||
|
||||
<label for="username"><%=l(:field_login)%></label>
|
||||
<%= text_field_tag 'username', params[:username], :tabindex => '1' %>
|
||||
|
||||
|
||||
<label for="password">
|
||||
<%=l(:field_password)%>
|
||||
<%= link_to l(:label_password_lost), lost_password_path, :class => "lost_password" if Setting.lost_password? %>
|
||||
</label>
|
||||
<%= password_field_tag 'password', nil, :tabindex => '2' %>
|
||||
|
||||
|
||||
<% if Setting.openid? %>
|
||||
<label for="openid_url"><%=l(:field_identity_url)%></label>
|
||||
<%= text_field_tag "openid_url", nil, :tabindex => '3' %>
|
||||
<% end %>
|
||||
|
||||
|
||||
<% if Setting.autologin? %>
|
||||
<label for="autologin"><%= check_box_tag 'autologin', 1, false, :tabindex => 4 %> <%= l(:label_stay_logged_in) %></label>
|
||||
<% end %>
|
||||
|
||||
|
||||
<input type="submit" name="login" value="<%=l(:button_login)%>" tabindex="5" id="login-submit" />
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<p>
|
||||
<%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %>
|
||||
<%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15, :required => true %>
|
||||
<em class="info"><%= l(:text_custom_field_possible_values_info) %></em>
|
||||
</p>
|
||||
<p><%= f.text_field(:default_value) %></p>
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
<%= error_messages_for @document %>
|
||||
|
||||
<div class="box tabular">
|
||||
<p><%= f.select :category_id, DocumentCategory.active.collect {|c| [c.name, c.id]} %></p>
|
||||
<p><%= f.text_field :title, :required => true, :size => 60 %></p>
|
||||
<p><%= f.text_area :description, :cols => 60, :rows => 15, :class => 'wiki-edit',
|
||||
:data => {
|
||||
:auto_complete => true,
|
||||
:issues_url => auto_complete_issues_path(:project_id => @project, :q => '')
|
||||
}
|
||||
%></p>
|
||||
<p><%= f.select :category_id, DocumentCategory.active.collect {|c| [c.name, c.id]} %></p>
|
||||
<p><%= f.text_field :title, :required => true, :size => 60 %></p>
|
||||
<p><%= f.text_area :description, :cols => 60, :rows => 15, :class => 'wiki-edit',
|
||||
:data => {
|
||||
:auto_complete => true,
|
||||
:issues_url => auto_complete_issues_path(:project_id => @project, :q => '')
|
||||
}
|
||||
%></p>
|
||||
|
||||
<% @document.custom_field_values.each do |value| %>
|
||||
<p><%= custom_field_tag_with_label :document, value %></p>
|
||||
<% end %>
|
||||
<% @document.custom_field_values.each do |value| %>
|
||||
<p><%= custom_field_tag_with_label :document, value %></p>
|
||||
<% end %>
|
||||
|
||||
<% if @document.new_record? %>
|
||||
<p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form', :locals => {:container => @document} %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= wikitoolbar_for 'document_description' %>
|
||||
|
||||
<% if @document.new_record? %>
|
||||
<div class="box tabular">
|
||||
<p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form', :locals => {:container => @document} %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -177,14 +177,17 @@
|
|||
<% end %>
|
||||
<% end %>
|
||||
</td>
|
||||
<% @query.columns.each do |column| %>
|
||||
<% next if Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.include?(column.name) %>
|
||||
<td class="gantt_<%= column.name %>_column gantt_selected_column <%= 'last_gantt_selected_column' if @query.columns.last == column %>" id="<%= column.name %>">
|
||||
<%
|
||||
@query.columns.each do |column|
|
||||
next if Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.include?(column.name)
|
||||
column_name = column.name.to_s.tr('.', '_')
|
||||
%>
|
||||
<td class="gantt_<%= column_name %>_column gantt_selected_column <%= 'last_gantt_selected_column' if @query.columns.last == column %>" id="<%= column_name %>">
|
||||
<%
|
||||
style = "position: relative;"
|
||||
style += "height: #{t_height + 24}px;"
|
||||
%>
|
||||
<%= content_tag(:div, :style => style, :class => "gantt_#{column.name}_container gantt_selected_column_container") do %>
|
||||
<%= content_tag(:div, :style => style, :class => "gantt_#{column_name}_container gantt_selected_column_container") do %>
|
||||
<%
|
||||
style = "height: #{t_height}px;"
|
||||
style += 'overflow: hidden;'
|
||||
|
@ -195,7 +198,7 @@
|
|||
style += 'background: #eee;'
|
||||
%>
|
||||
<%= content_tag(:div, content_tag(:p, column.caption, :class => 'gantt_hdr_selected_column_name'), :style => style, :class => "gantt_hdr") %>
|
||||
<%= content_tag(:div, :class => "gantt_#{column.name} gantt_selected_column_content") do %>
|
||||
<%= content_tag(:div, :class => "gantt_#{column_name} gantt_selected_column_content") do %>
|
||||
<%= @gantt.selected_column_content({:column => column, :top => headers_height + 8, :zoom => zoom, :g_width => g_width}).html_safe %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<p><%= time_entry.text_field :comments, :size => 60 %></p>
|
||||
<% @time_entry.custom_field_values.each do |value| %>
|
||||
<% @time_entry.editable_custom_field_values.each do |value| %>
|
||||
<p><%= custom_field_tag_with_label :time_entry, value %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
<%= link_to_if_authorized l(:label_settings),
|
||||
{:controller => 'projects', :action => 'settings', :id => @project, :tab => 'issues'},
|
||||
:class => 'icon icon-settings' if User.current.allowed_to?(:manage_categories, @project) %>
|
||||
:class => 'icon icon-settings' if User.current.allowed_to?(:edit_project, @project) %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
:rev => changeset.identifier) %>)
|
||||
<% end %></p>
|
||||
|
||||
<div class="wiki changeset-comments"><%= format_changeset_comments changeset %></div>
|
||||
<div class="wiki changeset-comments">
|
||||
<%= format_changeset_comments changeset %>
|
||||
</div>
|
||||
</div>
|
||||
<%= call_hook(:view_issues_history_changeset_bottom, { :changeset => changeset }) %>
|
||||
<% end %>
|
||||
|
|
|
@ -2,27 +2,22 @@
|
|||
<html lang="<%= current_language %>">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<title><%= html_title %></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="<%= Redmine::Info.app_name %>" />
|
||||
<meta name="keywords" content="issue,bug,tracker" />
|
||||
<%= csrf_meta_tag %>
|
||||
<%= favicon %>
|
||||
<%= stylesheet_link_tag 'jquery/jquery-ui-1.11.0', 'cookieconsent.min', 'tribute-3.7.3', 'application', 'responsive', :media => 'all' %>
|
||||
<%= stylesheet_link_tag 'jquery/jquery-ui-1.11.0', 'tribute-3.7.3', '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 %>
|
||||
<script src="/themes/circlepro/javascripts/cookieconsent.min.js"></script>
|
||||
<%= heads_for_theme %>
|
||||
<%= call_hook :view_layouts_base_html_head %>
|
||||
<!-- page specific tags -->
|
||||
<%= yield :header_tags -%>
|
||||
</head>
|
||||
<body class="<%= body_css_classes %><%= ' is-preload' if is_welcome %>">
|
||||
<body class="<%= body_css_classes %>">
|
||||
<%= call_hook :view_layouts_base_body_top %>
|
||||
<div id="wrapper">
|
||||
|
||||
|
@ -65,31 +60,20 @@
|
|||
|
||||
<div id="wrapper2">
|
||||
<div id="wrapper3">
|
||||
|
||||
<div id="top-menu">
|
||||
<div id="wrapper-top-menu">
|
||||
<ul class="social-menu">
|
||||
<li class="social-link-blog"><a href="https://manuel.cillero.es" title="<%= l(:link_my_blog) %>" class="icon-blog"><span><%= l(:link_my_blog) %></span></a></li>
|
||||
<li class="social-link-mastodon"><a href="https://noc.social/@manuelcillero" title="Mastodon" target="_blank" class="icon-mastodon"><span>Mastodon</span></a></li>
|
||||
<li class="social-link-linkedin"><a href="https://es.linkedin.com/in/manuelcillero" title="Linkedin" target="_blank" class="icon-linkedin"><span>Linkedin</span></a></li>
|
||||
<li class="social-link-github"><a href="https://github.com/manuelcillero" title="Github" target="_blank" class="icon-github"><span>Github</span></a></li>
|
||||
<li class="social-link-mail"><a href="https://manuel.cillero.es/contacto/#suitepro" title="Mail" class="icon-mail"><span>Mail</span></a></li>
|
||||
</ul>
|
||||
<div id="account">
|
||||
<%= render_menu :account_menu -%>
|
||||
</div>
|
||||
<%= content_tag('div', "#{l(:label_logged_as)} #{link_to_user(User.current, :format => :username)}".html_safe, :id => 'loggedas') if User.current.logged? %>
|
||||
<%= render_menu :top_menu if User.current.logged? || !Setting.login_required? -%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="header">
|
||||
|
||||
<a href="#" class="mobile-toggle-button js-flyout-menu-toggle-button"></a>
|
||||
|
||||
<div id="wrapper-header">
|
||||
<% if User.current.logged? || !Setting.login_required? %>
|
||||
<div id="quick-search" class="hide-when-print">
|
||||
<div id="quick-search">
|
||||
<%= form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %>
|
||||
<%= 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 %>
|
||||
|
@ -107,17 +91,14 @@
|
|||
<% end %>
|
||||
|
||||
<h1><%= page_header_title %></h1>
|
||||
</div>
|
||||
|
||||
<% if display_main_menu?(@project) %>
|
||||
<div id="main-menu" class="tabs">
|
||||
<div id="wrapper-main-menu">
|
||||
<%= render_main_menu(@project) %>
|
||||
<div class="tabs-buttons" style="display:none;">
|
||||
<button class="tab-left" onclick="moveTabLeft(this); return false;"></button>
|
||||
<button class="tab-right" onclick="moveTabRight(this); return false;"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
@ -135,54 +116,16 @@
|
|||
<div style="clear:both;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- #wrapper3 -->
|
||||
|
||||
<a href="#" id="scrollup" class="hide-when-print" style="display: none;"><%=l(:label_sort_higher)%></a><%= javascript_tag "$('#scrollup').click(function(){$('html,body').animate({scrollTop:0},600);return false;});" %>
|
||||
<div id="footer">
|
||||
Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> © 2006-2022 Jean-Philippe Lang
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
|
||||
<div id="ajax-modal" style="display:none;"></div>
|
||||
|
||||
<div id="footer">
|
||||
<div id="wrapper-footer">
|
||||
|
||||
<ul class="social-menu">
|
||||
<li class="social-link-blog"><a href="https://manuel.cillero.es" title="<%= l(:link_my_blog) %>" class="icon-blog"><span><%= l(:link_my_blog) %></span></a></li>
|
||||
<li class="social-link-mastodon"><a href="https://noc.social/@manuelcillero" title="Mastodon" target="_blank" class="icon-mastodon"><span>Mastodon</span></a></li>
|
||||
<li class="social-link-linkedin"><a href="https://es.linkedin.com/in/manuelcillero" title="Linkedin" target="_blank" class="icon-linkedin"><span>Linkedin</span></a></li>
|
||||
<li class="social-link-github"><a href="https://github.com/manuelcillero" title="Github" target="_blank" class="icon-github"><span>Github</span></a></li>
|
||||
<li class="social-link-mail"><a href="https://manuel.cillero.es/contacto/#suitepro" title="Mail" class="icon-mail"><span>Mail</span></a></li>
|
||||
</ul>
|
||||
<div class="bgl"><div class="bgr">
|
||||
<%= Time.current.year %> © SuitePro (powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %>)
|
||||
<div id="legal">
|
||||
<span class="legal-legal"><a href="/projects/suitepro/wiki/Legal"><%= l(:label_legal) %></a></span>
|
||||
<span class="legal-terms"> | <a href="/projects/suitepro/wiki/Condiciones_de_uso"><%= l(:label_legal_terms) %></a></span>
|
||||
<span class="legal-privacy"> | <a href="/projects/suitepro/wiki/Política_de_privacidad"><%= l(:label_legal_privacy) %></a></span>
|
||||
<span class="legal-cookies"> | <a href="/projects/suitepro/wiki/Política_de_cookies"><%= l(:label_legal_cookies) %></a></span>
|
||||
</div>
|
||||
</div></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- #wrapper2 -->
|
||||
|
||||
</div> <!-- #wrapper -->
|
||||
|
||||
<%= call_hook :view_layouts_base_body_bottom %>
|
||||
|
||||
<script>
|
||||
//<![CDATA[
|
||||
window.addEventListener("load", function(){
|
||||
window.cookieconsent.initialise({
|
||||
"palette": { "popup": { "background": "rgba(20,20,20,0.8)" }, "button": { "background": "#fff" } },
|
||||
"theme": "classic",
|
||||
"position": "bottom-left",
|
||||
"content": { "message": "<a href='https://suitepro.cillero.es'>SuitePro</a> requiere el uso de cookies para ofrecer la mejor experiencia de acceso a sus contenidos. Puedes aceptar su uso o abandonar la página si lo deseas.", "dismiss": "ACEPTO SU USO", "link": "Más información", "href": "/projects/suitepro/wiki/Pol%C3%ADtica_de_cookies", "target": "_self" }
|
||||
})});
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<%= textilizable @project.description %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @project.homepage.present? || @project.visible_custom_field_values.any?(&:present?) %>
|
||||
<% if @project.homepage.present? || @project.visible_custom_field_values.any? { |o| o.value.present? } %>
|
||||
<ul>
|
||||
<% unless @project.homepage.blank? %>
|
||||
<li><span class="label"><%=l(:field_homepage)%>:</span> <%= link_to_if uri_with_safe_scheme?(@project.homepage), @project.homepage, @project.homepage %></li>
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
<%= javascript_tag do %>
|
||||
$(document).ready(function(){
|
||||
$('.query-columns').closest('form').submit(function(){
|
||||
$('#<%= selected_tag_id %> option').prop('selected', true);
|
||||
$('#<%= selected_tag_id %> option:not(:disabled)').prop('selected', true);
|
||||
});
|
||||
});
|
||||
<% end %>
|
||||
|
|
|
@ -25,10 +25,10 @@
|
|||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="issue-report-graph">
|
||||
<div class="issue-report-graph hide-when-print">
|
||||
<canvas id="issues_by_<%= params[:detail] %>"></canvas>
|
||||
</div>
|
||||
<div class="issue-report-graph">
|
||||
<div class="issue-report-graph hide-when-print">
|
||||
<canvas id="issues_by_status"></canvas>
|
||||
</div>
|
||||
<%= javascript_tag do %>
|
||||
|
|
|
@ -33,7 +33,9 @@
|
|||
|
||||
</div>
|
||||
|
||||
<div class="wiki changeset-comments"><%= format_changeset_comments @changeset %></div>
|
||||
<div class="wiki changeset-comments">
|
||||
<%= format_changeset_comments @changeset %>
|
||||
</div>
|
||||
|
||||
<% if @changeset.issues.visible.any? || User.current.allowed_to?(:manage_related_issues, @repository.project) %>
|
||||
<%= render :partial => 'related_issues' %>
|
||||
|
|
|
@ -32,13 +32,13 @@
|
|||
<% end %>
|
||||
<div id="password_fields" style="<%= 'display:none;' if @user.auth_source %>">
|
||||
<p>
|
||||
<%= f.password_field :password, :required => true, :size => 25 %>
|
||||
<%= f.password_field :password, :required => @user.new_record?, :size => 25 %>
|
||||
<em class="info"><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em>
|
||||
<% if Setting.password_required_char_classes.any? %>
|
||||
<em class="info"><%= l(:text_characters_must_contain, :character_classes => Setting.password_required_char_classes.collect{|c| l("label_password_char_class_#{c}")}.join(", ")) %></em>
|
||||
<% end %>
|
||||
</p>
|
||||
<p><%= f.password_field :password_confirmation, :required => true, :size => 25 %></p>
|
||||
<p><%= f.password_field :password_confirmation, :required => @user.new_record?, :size => 25 %></p>
|
||||
<p><%= f.check_box :generate_password %></p>
|
||||
<p><%= f.check_box :must_change_passwd %></p>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<%=
|
||||
page_title = title [l(:label_user_plural), users_path], @user.login
|
||||
page_title.insert(page_title.rindex(' ') + 1, avatar(@user))
|
||||
page_title.insert(page_title.rindex(' ') + 1, avatar(@user).to_s)
|
||||
%>
|
||||
|
||||
<%= render_tabs user_settings_tabs %>
|
||||
|
|
|
@ -10,3 +10,6 @@ Disallow: <%= url_for(issues_gantt_path) %>
|
|||
Disallow: <%= url_for(issues_calendar_path) %>
|
||||
Disallow: <%= url_for(activity_path) %>
|
||||
Disallow: <%= url_for(search_path) %>
|
||||
Disallow: <%= url_for(issues_path(:sort => '')) %>
|
||||
Disallow: <%= url_for(issues_path(:query_id => '')) %>
|
||||
Disallow: <%= url_for(issues_path) %>?*set_filter=
|
||||
|
|
|
@ -5,7 +5,7 @@ api.wiki_page do
|
|||
end
|
||||
api.text @content.text
|
||||
api.version @content.version
|
||||
api.author(:id => @content.author_id, :name => @content.author.name)
|
||||
api.author(:id => @content.author_id, :name => @content.author.name) unless @content.author_id.nil?
|
||||
api.comments @content.comments
|
||||
api.created_on @page.created_on
|
||||
api.updated_on @content.updated_on
|
||||
|
|
|
@ -61,8 +61,7 @@
|
|||
|
||||
<%= render(:partial => "wiki/content", :locals => {:content => @content}) %>
|
||||
|
||||
<% if @page.attachments.length > 0 || (@editable && authorize_for('wiki', 'add_attachment')) %>
|
||||
<fieldset class="collapsible collapsed<% if @page.attachments.length == 0 %> hide-when-print<% end %>">
|
||||
<fieldset class="collapsible collapsed hide-when-print">
|
||||
<legend onclick="toggleFieldset(this);" class="icon icon-collapsed"><%= l(:label_attachment_plural) %> (<%= @page.attachments.length %>)</legend>
|
||||
<div style="display: none;">
|
||||
|
||||
|
@ -82,7 +81,6 @@
|
|||
<% end %>
|
||||
</div>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
|
||||
<p class="wiki-update-info">
|
||||
<% if User.current.allowed_to?(:view_wiki_edits, @project) %>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<th>
|
||||
<%= link_to_function('', "toggleCheckboxesBySelector('table.transitions-#{name} input[type=checkbox]:not(:disabled)')",
|
||||
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}",
|
||||
:class => 'icon-only icon-checked') %>
|
||||
:class => 'no-tooltip icon-only icon-checked') %>
|
||||
<%=l(:label_current_status)%>
|
||||
</th>
|
||||
<th colspan="<%= @statuses.length %>"><%=l(:label_new_statuses_allowed)%></th>
|
||||
|
@ -15,7 +15,7 @@
|
|||
<td style="width:<%= 75 / @statuses.size %>%;">
|
||||
<%= link_to_function('', "toggleCheckboxesBySelector('table.transitions-#{name} input[type=checkbox]:not(:disabled).new-status-#{new_status.id}')",
|
||||
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}",
|
||||
:class => 'icon-only icon-checked') %>
|
||||
:class => 'no-tooltip icon-only icon-checked') %>
|
||||
<%= new_status.name %>
|
||||
</td>
|
||||
<% end %>
|
||||
|
@ -29,7 +29,7 @@
|
|||
<td class="name">
|
||||
<%= link_to_function('', "toggleCheckboxesBySelector('table.transitions-#{name} input[type=checkbox]:not(:disabled).old-status-#{old_status.try(:id) || 0}')",
|
||||
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}",
|
||||
:class => 'icon-only icon-checked') %>
|
||||
:class => 'no-tooltip icon-only icon-checked') %>
|
||||
<% if old_status %>
|
||||
<% old_status_name = old_status.name %>
|
||||
<%= old_status_name %>
|
||||
|
@ -40,7 +40,7 @@
|
|||
</td>
|
||||
<% for new_status in @statuses -%>
|
||||
<% checked = (old_status == new_status) || (transition_counts[[old_status, new_status]] > 0) %>
|
||||
<td class="<%= checked ? 'enabled' : '' %>" title="<%= old_status_name %> » <%= new_status.name %>">
|
||||
<td class="no-tooltip <%= checked ? 'enabled' : '' %>" title="<%= old_status_name %> » <%= new_status.name %>">
|
||||
<%= transition_tag transition_counts[[old_status, new_status]], old_status, new_status, name %>
|
||||
</td>
|
||||
<% end -%>
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
<% for status in @statuses -%>
|
||||
<td class="<%= @permissions[status.id][field].try(:join, ' ') %>" title="<%= name %> (<%= status.name %>)">
|
||||
<%= field_permission_tag(@permissions, status, field, @roles) %>
|
||||
<% unless status == @statuses.last %><a href="#" class="repeat-value">»</a><% end %>
|
||||
<% unless status == @statuses.last %><a href="#" class="repeat-value" title="<%= l(:button_copy) %>">»</a><% end %>
|
||||
</td>
|
||||
<% end -%>
|
||||
</tr>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue