From 3ca3c37487445996b8d11ef423355cc6e8dc3ffd Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 7 Jul 2023 08:08:27 +0200 Subject: [PATCH] Redmine 4.1.7 --- Gemfile | 14 +- Rakefile | 0 app/controllers/account_controller.rb | 1 + app/controllers/activities_controller.rb | 9 +- app/controllers/attachments_controller.rb | 12 +- app/controllers/mail_handler_controller.rb | 4 +- app/controllers/repositories_controller.rb | 21 +- app/controllers/search_controller.rb | 2 +- app/controllers/sys_controller.rb | 4 +- app/controllers/watchers_controller.rb | 4 +- app/controllers/wiki_controller.rb | 6 +- app/helpers/application_helper.rb | 4 +- app/helpers/issues_helper.rb | 10 +- app/helpers/journals_helper.rb | 2 +- app/helpers/queries_helper.rb | 4 +- app/helpers/search_helper.rb | 3 - app/helpers/timelog_helper.rb | 2 +- app/models/attachment.rb | 23 +- app/models/issue.rb | 25 +- app/models/issue_query.rb | 6 +- app/models/mail_handler.rb | 7 +- app/models/project.rb | 11 + app/models/project_query.rb | 2 +- app/models/query.rb | 21 +- app/models/repository.rb | 4 + app/models/setting.rb | 3 +- app/models/time_entry.rb | 16 +- app/models/time_entry_query.rb | 8 +- app/models/token.rb | 12 +- app/models/wiki_page.rb | 6 +- app/views/account/login.html.erb | 11 +- .../custom_fields/formats/_list.html.erb | 2 +- app/views/documents/_form.html.erb | 32 +- app/views/gantts/show.html.erb | 13 +- app/views/issues/_edit.html.erb | 2 +- app/views/issues/index.html.erb | 2 +- app/views/issues/tabs/_changesets.html.erb | 4 +- app/views/layouts/base.html.erb | 75 +- app/views/projects/show.html.erb | 2 +- app/views/queries/_columns.html.erb | 2 +- app/views/reports/_details.html.erb | 4 +- app/views/repositories/_changeset.html.erb | 4 +- app/views/users/_form.html.erb | 4 +- app/views/users/edit.html.erb | 2 +- app/views/welcome/robots.text.erb | 3 + app/views/wiki/show.api.rsb | 2 +- app/views/wiki/show.html.erb | 4 +- app/views/workflows/_form.html.erb | 8 +- app/views/workflows/permissions.html.erb | 2 +- config/application.rb | 3 +- config/initializers/10-patches.rb | 14 + config/locales/en.yml | 10 +- config/locales/es.yml | 203 +++-- config/locales/pt-BR.yml | 110 +-- .../20090503121505_populate_member_roles.rb | 2 +- doc/CHANGELOG | 275 ++++++- .../lib/acts_as_attachable.rb | 2 +- lib/redmine/ciphering.rb | 4 +- lib/redmine/helpers/gantt.rb | 16 +- lib/redmine/project_jump_box.rb | 1 + lib/redmine/scm/adapters/abstract_adapter.rb | 8 + lib/redmine/scm/adapters/git_adapter.rb | 12 + lib/redmine/scm/adapters/mercurial_adapter.rb | 9 + lib/redmine/thumbnail.rb | 9 +- lib/redmine/version.rb | 2 +- lib/tasks/load_default_data.rake | 1 - .../help/ru/wiki_syntax_detailed_textile.html | 4 +- public/javascripts/application.js | 49 +- public/javascripts/attachments.js | 16 +- public/javascripts/context_menu.js | 3 +- public/javascripts/gantt.js | 10 +- .../jquery-2.2.4-ui-1.11.0-ujs-5.2.4.5.js | 743 ++++++++++++++++++ public/javascripts/jstoolbar/jstoolbar.js | 2 +- public/stylesheets/application.css | 10 +- .../classic/stylesheets/application.css | 1 - test/functional/activities_controller_test.rb | 31 +- .../functional/attachments_controller_test.rb | 38 + test/functional/issues_controller_test.rb | 177 +++++ test/functional/queries_controller_test.rb | 24 + test/functional/search_controller_test.rb | 15 + test/functional/timelog_controller_test.rb | 2 +- test/functional/users_controller_test.rb | 15 + test/functional/wiki_controller_test.rb | 6 + test/functional/workflows_controller_test.rb | 2 +- test/helpers/application_helper_test.rb | 1 + test/helpers/issues_helper_test.rb | 30 + test/helpers/journals_helper_test.rb | 26 + test/integration/api_test/attachments_test.rb | 47 ++ test/integration/api_test/issues_test.rb | 28 + test/integration/api_test/wiki_pages_test.rb | 11 + test/integration/welcome_test.rb | 2 + test/system/inline_autocomplete_test.rb | 145 ++++ test/unit/attachment_test.rb | 30 + test/unit/issue_subtasking_test.rb | 10 + test/unit/issue_test.rb | 34 + test/unit/lib/redmine/ciphering_test.rb | 9 + test/unit/lib/redmine/helpers/gantt_test.rb | 32 + .../unit/lib/redmine/project_jump_box_test.rb | 15 +- test/unit/mail_handler_test.rb | 19 + test/unit/project_test.rb | 7 + test/unit/query_test.rb | 80 ++ test/unit/time_entry_custom_field_test.rb | 75 ++ test/unit/time_entry_test.rb | 3 +- 103 files changed, 2426 insertions(+), 431 deletions(-) mode change 100644 => 100755 Rakefile mode change 100755 => 100644 app/models/mail_handler.rb create mode 100644 public/javascripts/jquery-2.2.4-ui-1.11.0-ujs-5.2.4.5.js create mode 100644 test/system/inline_autocomplete_test.rb create mode 100644 test/unit/time_entry_custom_field_test.rb diff --git a/Gemfile b/Gemfile index f9a98ff..16becab 100644 --- a/Gemfile +++ b/Gemfile @@ -3,17 +3,18 @@ source 'https://rubygems.org' ruby '>= 2.3.0', '< 2.7.0' if Bundler::VERSION >= '1.12.0' gem "bundler", ">= 1.5.0" -gem 'rails', '5.2.4.2' +gem 'rails', '5.2.6.3' gem 'sprockets', '~> 3.7.2' if RUBY_VERSION < '2.5' +gem 'globalid', '~> 0.4.2' if Gem.ruby_version < Gem::Version.new('2.6.0') gem "rouge", "~> 3.12.0" gem "request_store", "~> 1.4.1" gem "mini_mime", "~> 1.0.1" gem "actionpack-xml_parser" gem "roadie-rails", (RUBY_VERSION < "2.5" ? "~> 1.3.0" : "~> 2.1.0") -gem "mimemagic" +gem 'marcel' gem "mail", "~> 2.7.1" -gem "csv", "~> 3.1.1" -gem "nokogiri", "~> 1.10.0" +gem 'csv', (RUBY_VERSION < '2.5' ? ['>= 3.1.1', '<= 3.1.5'] : '~> 3.1.1') +gem 'nokogiri', (RUBY_VERSION < '2.5' ? '~> 1.10.0' : '~> 1.11.1') gem "i18n", "~> 1.6.0" gem "rbpdf", "~> 1.20.0" @@ -38,7 +39,7 @@ end # Optional Markdown support, not for JRuby group :markdown do - gem "redcarpet", "~> 3.5.0" + gem 'redcarpet', '~> 3.5.1' end # Include database gems for the adapters found in the database @@ -47,7 +48,8 @@ require 'erb' require 'yaml' database_file = File.join(File.dirname(__FILE__), "config/database.yml") if File.exist?(database_file) - database_config = YAML::load(ERB.new(IO.read(database_file)).result) + yaml_config = ERB.new(IO.read(database_file)).result + database_config = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(yaml_config) : YAML.load(yaml_config) adapters = database_config.values.map {|c| c['adapter']}.compact.uniq if adapters.any? adapters.each do |adapter| diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 diff --git a/app/controllers/account_controller.rb b/app/controllers/account_controller.rb index ff8631e..6ec75ea 100644 --- a/app/controllers/account_controller.rb +++ b/app/controllers/account_controller.rb @@ -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 } diff --git a/app/controllers/activities_controller.rb b/app/controllers/activities_controller.rb index d809245..c45e467 100644 --- a/app/controllers/activities_controller.rb +++ b/app/controllers/activities_controller.rb @@ -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| diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb index 4a88079..366bad5 100644 --- a/app/controllers/attachments_controller.rb +++ b/app/controllers/attachments_controller.rb @@ -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 diff --git a/app/controllers/mail_handler_controller.rb b/app/controllers/mail_handler_controller.rb index 389cd6f..9b96c79 100644 --- a/app/controllers/mail_handler_controller.rb +++ b/app/controllers/mail_handler_controller.rb @@ -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 diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index ef903f2..69c3858 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -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 diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 2e81281..b200f2a 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -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( diff --git a/app/controllers/sys_controller.rb b/app/controllers/sys_controller.rb index f217ee2..2d3e748 100644 --- a/app/controllers/sys_controller.rb +++ b/app/controllers/sys_controller.rb @@ -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 diff --git a/app/controllers/watchers_controller.rb b/app/controllers/watchers_controller.rb index f0692e3..023f7e4 100644 --- a/app/controllers/watchers_controller.rb +++ b/app/controllers/watchers_controller.rb @@ -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) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 33fd849..0f90470 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -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 diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e8284a0..8b5d48c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1190,7 +1190,7 @@ module ApplicationHelper )| ( (?@) - (?[A-Za-z0-9_\-@\.]*) + (?[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 diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 46256e8..f2f4065 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -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 diff --git a/app/helpers/journals_helper.rb b/app/helpers/journals_helper.rb index 391a432..9d3edf1 100644 --- a/app/helpers/journals_helper.rb +++ b/app/helpers/journals_helper.rb @@ -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 diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb index 991ef93..a4e4749 100644 --- a/app/helpers/queries_helper.rb +++ b/app/helpers/queries_helper.rb @@ -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}")) diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 5fe4946..5ba88a6 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/helpers/timelog_helper.rb b/app/helpers/timelog_helper.rb index f8f36fb..49d78fd 100644 --- a/app/helpers/timelog_helper.rb +++ b/app/helpers/timelog_helper.rb @@ -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 diff --git a/app/models/attachment.rb b/app/models/attachment.rb index 627c1a1..ae39bfa 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -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 diff --git a/app/models/issue.rb b/app/models/issue.rb index efb55fa..edd77b4 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -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 diff --git a/app/models/issue_query.rb b/app/models/issue_query.rb index c33caef..d334b23 100644 --- a/app/models/issue_query.rb +++ b/app/models/issue_query.rb @@ -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")})" diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb old mode 100755 new mode 100644 index eccc93a..b88b1f3 --- a/app/models/mail_handler.rb +++ b/app/models/mail_handler.rb @@ -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? diff --git a/app/models/project.rb b/app/models/project.rb index d4af06c..4b9812b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -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) diff --git a/app/models/project_query.rb b/app/models/project_query.rb index 55c785e..4f48893 100644 --- a/app/models/project_query.rb +++ b/app/models/project_query.rb @@ -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 diff --git a/app/models/query.rb b/app/models/query.rb index 0dec7d2..9d5a5c7 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -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 diff --git a/app/models/repository.rb b/app/models/repository.rb index 089027c..4aca619 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -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 diff --git a/app/models/setting.rb b/app/models/setting.rb index 384bb90..763f83f 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -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? diff --git a/app/models/time_entry.rb b/app/models/time_entry.rb index 1f3b0a7..c12a051 100644 --- a/app/models/time_entry.rb +++ b/app/models/time_entry.rb @@ -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 diff --git a/app/models/time_entry_query.rb b/app/models/time_entry_query.rb index a516016..b4d4237 100644 --- a/app/models/time_entry_query.rb +++ b/app/models/time_entry_query.rb @@ -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 diff --git a/app/models/token.rb b/app/models/token.rb index 8e93918..55fded6 100644 --- a/app/models/token.rb +++ b/app/models/token.rb @@ -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 diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index c4da80f..e1765a2 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -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) diff --git a/app/views/account/login.html.erb b/app/views/account/login.html.erb index 3dd7240..1440e32 100644 --- a/app/views/account/login.html.erb +++ b/app/views/account/login.html.erb @@ -1,28 +1,27 @@ <%= 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 %>
diff --git a/app/views/custom_fields/formats/_list.html.erb b/app/views/custom_fields/formats/_list.html.erb index 675bbbf..f615e50 100644 --- a/app/views/custom_fields/formats/_list.html.erb +++ b/app/views/custom_fields/formats/_list.html.erb @@ -1,5 +1,5 @@

- <%= 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 %> <%= l(:text_custom_field_possible_values_info) %>

<%= f.text_field(:default_value) %>

diff --git a/app/views/documents/_form.html.erb b/app/views/documents/_form.html.erb index b8b8c84..71ef202 100644 --- a/app/views/documents/_form.html.erb +++ b/app/views/documents/_form.html.erb @@ -1,24 +1,22 @@ <%= error_messages_for @document %>
-

<%= f.select :category_id, DocumentCategory.active.collect {|c| [c.name, c.id]} %>

-

<%= f.text_field :title, :required => true, :size => 60 %>

-

<%= 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 => '') - } -%>

+

<%= f.select :category_id, DocumentCategory.active.collect {|c| [c.name, c.id]} %>

+

<%= f.text_field :title, :required => true, :size => 60 %>

+

<%= 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 => '') + } + %>

-<% @document.custom_field_values.each do |value| %> -

<%= custom_field_tag_with_label :document, value %>

-<% end %> + <% @document.custom_field_values.each do |value| %> +

<%= custom_field_tag_with_label :document, value %>

+ <% end %> + + <% if @document.new_record? %> +

<%= render :partial => 'attachments/form', :locals => {:container => @document} %>

+ <% end %>
<%= wikitoolbar_for 'document_description' %> - -<% if @document.new_record? %> -
-

<%= render :partial => 'attachments/form', :locals => {:container => @document} %>

-
-<% end %> diff --git a/app/views/gantts/show.html.erb b/app/views/gantts/show.html.erb index 348d04b..8265ccc 100644 --- a/app/views/gantts/show.html.erb +++ b/app/views/gantts/show.html.erb @@ -177,14 +177,17 @@ <% end %> <% end %> -<% @query.columns.each do |column| %> - <% next if Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.include?(column.name) %> - +<% + @query.columns.each do |column| + next if Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.include?(column.name) + column_name = column.name.to_s.tr('.', '_') +%> + <% 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 %> diff --git a/app/views/issues/_edit.html.erb b/app/views/issues/_edit.html.erb index 3552777..fa8486c 100644 --- a/app/views/issues/_edit.html.erb +++ b/app/views/issues/_edit.html.erb @@ -21,7 +21,7 @@

<%= time_entry.text_field :comments, :size => 60 %>

- <% @time_entry.custom_field_values.each do |value| %> + <% @time_entry.editable_custom_field_values.each do |value| %>

<%= custom_field_tag_with_label :time_entry, value %>

<% end %> <% end %> diff --git a/app/views/issues/index.html.erb b/app/views/issues/index.html.erb index 880c953..b05b6cc 100644 --- a/app/views/issues/index.html.erb +++ b/app/views/issues/index.html.erb @@ -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 %> diff --git a/app/views/issues/tabs/_changesets.html.erb b/app/views/issues/tabs/_changesets.html.erb index 5a1d012..f869a5d 100644 --- a/app/views/issues/tabs/_changesets.html.erb +++ b/app/views/issues/tabs/_changesets.html.erb @@ -16,7 +16,9 @@ :rev => changeset.identifier) %>) <% end %>

-
<%= format_changeset_comments changeset %>
+
+ <%= format_changeset_comments changeset %> +
<%= call_hook(:view_issues_history_changeset_bottom, { :changeset => changeset }) %> <% end %> diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb index ac84b3b..2f04a5a 100644 --- a/app/views/layouts/base.html.erb +++ b/app/views/layouts/base.html.erb @@ -2,27 +2,22 @@ - + <%= html_title %> - + <%= 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 %> - <%= heads_for_theme %> <%= call_hook :view_layouts_base_html_head %> <%= yield :header_tags -%> - + <%= call_hook :view_layouts_base_body_top %>
@@ -65,31 +60,20 @@
-
-
-
<%= render_menu :account_menu -%>
<%= 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? -%> -
- -
- -<%= javascript_tag "$('#scrollup').click(function(){$('html,body').animate({scrollTop:0},600);return false;});" %> + +
- - -
- - - <%= call_hook :view_layouts_base_body_bottom %> - - - diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index de99eea..c8745be 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -31,7 +31,7 @@ <%= textilizable @project.description %> <% 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? } %>
    <% unless @project.homepage.blank? %>
  • <%=l(:field_homepage)%>: <%= link_to_if uri_with_safe_scheme?(@project.homepage), @project.homepage, @project.homepage %>
  • diff --git a/app/views/queries/_columns.html.erb b/app/views/queries/_columns.html.erb index c5d1419..18443de 100644 --- a/app/views/queries/_columns.html.erb +++ b/app/views/queries/_columns.html.erb @@ -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 %> diff --git a/app/views/reports/_details.html.erb b/app/views/reports/_details.html.erb index 9b8f3b5..701591c 100644 --- a/app/views/reports/_details.html.erb +++ b/app/views/reports/_details.html.erb @@ -25,10 +25,10 @@ <% end %> -
    +
    -
    +
    <%= javascript_tag do %> diff --git a/app/views/repositories/_changeset.html.erb b/app/views/repositories/_changeset.html.erb index 9b18c02..393e03e 100644 --- a/app/views/repositories/_changeset.html.erb +++ b/app/views/repositories/_changeset.html.erb @@ -33,7 +33,9 @@
    -
    <%= 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/users/_form.html.erb b/app/views/users/_form.html.erb index bb20a4f..d423f61 100644 --- a/app/views/users/_form.html.erb +++ b/app/views/users/_form.html.erb @@ -32,13 +32,13 @@ <% end %>

    - <%= f.password_field :password, :required => true, :size => 25 %> + <%= f.password_field :password, :required => @user.new_record?, :size => 25 %> <%= l(:text_caracters_minimum, :count => Setting.password_min_length) %> <% if Setting.password_required_char_classes.any? %> <%= l(:text_characters_must_contain, :character_classes => Setting.password_required_char_classes.collect{|c| l("label_password_char_class_#{c}")}.join(", ")) %> <% end %>

    -

    <%= f.password_field :password_confirmation, :required => true, :size => 25 %>

    +

    <%= f.password_field :password_confirmation, :required => @user.new_record?, :size => 25 %>

    <%= f.check_box :generate_password %>

    <%= f.check_box :must_change_passwd %>

    diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index 2a8b670..7866308 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -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 %> diff --git a/app/views/welcome/robots.text.erb b/app/views/welcome/robots.text.erb index 6f66278..16acc81 100644 --- a/app/views/welcome/robots.text.erb +++ b/app/views/welcome/robots.text.erb @@ -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= diff --git a/app/views/wiki/show.api.rsb b/app/views/wiki/show.api.rsb index 8e082e1..6165a94 100644 --- a/app/views/wiki/show.api.rsb +++ b/app/views/wiki/show.api.rsb @@ -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 diff --git a/app/views/wiki/show.html.erb b/app/views/wiki/show.html.erb index 1c90103..a710496 100644 --- a/app/views/wiki/show.html.erb +++ b/app/views/wiki/show.html.erb @@ -61,8 +61,7 @@ <%= render(:partial => "wiki/content", :locals => {:content => @content}) %> -<% if @page.attachments.length > 0 || (@editable && authorize_for('wiki', 'add_attachment')) %> -
    + -<% end %>

    <% if User.current.allowed_to?(:view_wiki_edits, @project) %> diff --git a/app/views/workflows/_form.html.erb b/app/views/workflows/_form.html.erb index 3263975..d565a02 100644 --- a/app/views/workflows/_form.html.erb +++ b/app/views/workflows/_form.html.erb @@ -4,7 +4,7 @@ <%= 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)%> <%=l(:label_new_statuses_allowed)%> @@ -15,7 +15,7 @@ <%= 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 %> <% end %> @@ -29,7 +29,7 @@ <%= 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 @@ <% for new_status in @statuses -%> <% checked = (old_status == new_status) || (transition_counts[[old_status, new_status]] > 0) %> - + <%= transition_tag transition_counts[[old_status, new_status]], old_status, new_status, name %> <% end -%> diff --git a/app/views/workflows/permissions.html.erb b/app/views/workflows/permissions.html.erb index f2e9fff..66c8f28 100644 --- a/app/views/workflows/permissions.html.erb +++ b/app/views/workflows/permissions.html.erb @@ -67,7 +67,7 @@ <% for status in @statuses -%> <%= field_permission_tag(@permissions, status, field, @roles) %> - <% unless status == @statuses.last %>»<% end %> + <% unless status == @statuses.last %>»<% end %> <% end -%> diff --git a/config/application.rb b/config/application.rb index 86b695d..c40a580 100644 --- a/config/application.rb +++ b/config/application.rb @@ -79,7 +79,8 @@ module RedmineApp config.session_store :cookie_store, :key => '_redmine_session', - :path => config.relative_url_root || '/' + :path => config.relative_url_root || '/', + :same_site => :lax if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb')) instance_eval File.read(File.join(File.dirname(__FILE__), 'additional_environment.rb')) diff --git a/config/initializers/10-patches.rb b/config/initializers/10-patches.rb index 9af5956..204caba 100644 --- a/config/initializers/10-patches.rb +++ b/config/initializers/10-patches.rb @@ -213,3 +213,17 @@ module ActionView end end end + +# https://github.com/rack/rack/pull/1703 +# TODO: remove this when Rack is updated to 3.0.0 +require 'rack' +module Rack + class RewindableInput + unless method_defined?(:size) + def size + make_rewindable unless @rewindable_io + @rewindable_io.size + end + end + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 6dde0c6..607bf9f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -416,7 +416,7 @@ en: setting_timespan_format: Time span format setting_cross_project_issue_relations: Allow cross-project issue relations setting_cross_project_subtasks: Allow cross-project subtasks - setting_issue_list_default_columns: Isuses list defaults + setting_issue_list_default_columns: Issues list defaults setting_repositories_encodings: Attachments and repositories encodings setting_emails_header: Email header setting_emails_footer: Email footer @@ -1284,11 +1284,3 @@ en: 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_import_time_entries: Import time entries - - - 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 6f93a67..4f329be 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -507,7 +507,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 @@ -546,7 +546,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 @@ -1216,7 +1216,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. @@ -1231,121 +1231,114 @@ es: label_font_default: Fuente por defecto label_font_monospace: Fuente Monospaced label_font_proportional: Fuente Proportional - setting_timespan_format: Time span format - label_table_of_contents: Tabla de contenidos - setting_commit_logs_formatting: Apply text formatting to commit messages - setting_mail_handler_enable_regex: 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 + setting_timespan_format: Formato de timespan + label_table_of_contents: Índice de contenidos + setting_commit_logs_formatting: Aplicar formato de texto a los mensajes de commits + setting_mail_handler_enable_regex: Habilitar expresiones regulares + error_move_of_child_not_possible: 'Subtarea %{child} no ha podido ser movida al nuevo + proyecto: %{errors}' + error_cannot_reassign_time_entries_to_an_issue_about_to_be_deleted: El tiempo dedicado no puede + ser reasignado a una petición que va a ser borrada + setting_timelog_required_fields: Campos requeridos para imputación de tiempo 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 + label_user_mail_option_only_assigned: Sólo para asuntos que sigo o en los que estoy asignado + label_user_mail_option_only_owner: Sólo para asuntos que sigo o de los que soy propietario + warning_fields_cleared_on_bulk_edit: Los cambios conllevarán la eliminación automática + de valores de uno o más campos de los objetos seleccionados + field_updated_by: Actualizado por + field_last_updated_by: Última actualización de + field_full_width_layout: Diseño de ancho completo + label_last_notes: Últimas notas 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 hay vista previa disponible. %{link} el archivo. + field_default_assigned_to: Asignado por defecto + setting_show_custom_fields_on_registration: Mostrar campos personalizados en el registro + + permission_view_news: Ver noticias + label_no_preview_alternative_html: No hay vista previa disponible. %{link} el archivo en su lugar. label_no_preview_download: Descargar - 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: "Revisiones: %{count}" - error_can_not_delete_auth_source: This authentication mode is in use and cannot be - deleted. + setting_close_duplicate_issues: Cerrar peticiones duplicadas automáticamente + error_exceeds_maximum_hours_per_day: No se pueden registrar más de %{max_hours} horas en el + mismo día (se han registrado ya %{logged_hours} horas) + setting_time_entry_list_defaults: Listas por defecto del Timelog + setting_timelog_accept_0_hours: Aceptar registros de tiempo de 0 horas + setting_timelog_max_hours_per_day: Número de horas máximo que se puede imputar por día y usuario + label_x_revisions: "%{count} revisiones" + error_can_not_delete_auth_source: Este modo de autenticación está en uso y no puede ser + eliminado. button_actions: Acciones - 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 edit - the applicable permissions. - 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: Por favor, tenga en cuenta que sólo puede cambiar la contraseña + una vez usando este enlace. + text_login_required_html: Cuando no se requiera autenticación, los proyectos públicos y sus + contenidos están abiertos en la red. Puede editar + los permisos aplicables. + label_login_required_yes: 'Sí' + label_login_required_no: No, permitir acceso anónimo a los proyectos públicos + text_project_is_public_non_member: Los proyectos públicos y sus contenidos están disponibles + a todos los usuarios identificados. + text_project_is_public_anonymous: Los proyectos públicos y sus contenidos están disponibles libremente + en la red. + label_version_and_files: Versionees (%{count}) y Ficheros label_ldap: LDAP - label_ldaps_verify_none: LDAPS (without certificate check) + label_ldaps_verify_none: LDAPS (sin chequeo de certificado) 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. - error_spent_on_future_date: Cannot log time on a future date - setting_timelog_accept_future_dates: Accept time logs on future dates + label_ldaps_warning: Se recomienda usar una conexión LDAPS encriptada con chequeo de + certificado para prevenir cualquier manipulación durante el proceso de autenticación. + label_nothing_to_preview: Nada que previsualizar + error_token_expired: Este enlace de recuperación de contraseña ha expirado, por favor, inténtelo de nuevo. + error_spent_on_future_date: No se puede registrar tiempo en una fecha futura + setting_timelog_accept_future_dates: Aceptar registros de tiempo en fechas futuras label_delete_link_to_subtask: Eliminar relación - error_not_allowed_to_log_time_for_other_users: You are not allowed to log time - for other users - permission_log_time_for_other_users: Log spent time for other users - label_tomorrow: tomorrow - label_next_week: next week - label_next_month: next month - text_role_no_workflow: No workflow defined for this role - text_status_no_workflow: No tracker uses this status in the workflows - setting_mail_handler_preferred_body_part: Preferred part of multipart (HTML) emails - setting_show_status_changes_in_mail_subject: Show status changes in issue mail notifications - subject - label_inherited_from_parent_project: Inherited from parent project - label_inherited_from_group: Inherited from group %{name} - label_trackers_description: Trackers description - label_open_trackers_description: View all trackers description - label_preferred_body_part_text: Text + error_not_allowed_to_log_time_for_other_users: No está autorizado a registrar tiempo para + otros usuarios + permission_log_time_for_other_users: Registrar tiempo para otros usuarios + label_tomorrow: mañana + label_next_week: próxima semana + label_next_month: próximo mes + text_role_no_workflow: No se ha definido un flujo de trabajo para este perfil + text_status_no_workflow: Ningún tipo de petición utiliza este estado en los flujos de trabajo + setting_mail_handler_preferred_body_part: Parte preferida de los correos electrónicos multiparte (HTML) + setting_show_status_changes_in_mail_subject: Mostrar los cambios de estado en el asunto de las notificaciones de correo + electrónico de las peticiones + label_inherited_from_parent_project: Heredado del proyecto padre + label_inherited_from_group: Heredado del grupo %{name} + label_trackers_description: Descripción del tipo de petición + label_open_trackers_description: Ver todas las descripciones de los tipos de petición + label_preferred_body_part_text: Texto label_preferred_body_part_html: HTML (experimental) - field_parent_issue_subject: Parent task subject - permission_edit_own_issues: Edit own issues - text_select_apply_tracker: Select tracker - label_updated_issues: Updated issues - text_avatar_server_config_html: The current avatar server is %{url}. - You can configure it in config/configuration.yml. - setting_gantt_months_limit: Maximum number of months displayed on the gantt chart - permission_import_time_entries: Import time entries - label_import_notifications: Send email notifications during the import - text_gs_available: ImageMagick PDF support available (optional) - field_recently_used_projects: Number of recently used projects in jump box + field_parent_issue_subject: Asunto de la tarea padre + permission_edit_own_issues: Editar sus propias peticiones + text_select_apply_tracker: Seleccionar tipo de petición + label_updated_issues: Peticiones actualizadas + text_avatar_server_config_html: El servidor actual de avatares es %{url}. + Puede configurarlo en config/configuration.yml. + setting_gantt_months_limit: Máximo número de meses mostrados en el diagrama de Gantt + permission_import_time_entries: Importar registros de tiempo + label_import_notifications: Enviar notificaciones de correo electrónico durante la importación + text_gs_available: Disponible soporte ImageMagick PDF (opcional) + field_recently_used_projects: Número de proyectos recientemente usados en el selector label_optgroup_bookmarks: Marcadores label_optgroup_others: Otros proyectos - label_optgroup_recents: Accesos recientes + label_optgroup_recents: Utilizados recientemente button_project_bookmark: Añadir marcador button_project_bookmark_delete: Quitar marcador - field_history_default_tab: Issue's history default tab - label_issue_history_properties: Cambios de propiedad + field_history_default_tab: Pstaña por defecto del historial de la petición + label_issue_history_properties: Cambios de propiedades label_issue_history_notes: Notas - label_last_tab_visited: Last visited tab - field_unique_id: Unique ID - text_no_subject: no subject - setting_password_required_char_classes: Required character classes for passwords - label_password_char_class_uppercase: uppercase letters - label_password_char_class_lowercase: lowercase letters - label_password_char_class_digits: digits - label_password_char_class_special_chars: special characters - text_characters_must_contain: Must contain %{character_classes}. - label_starts_with: starts with - label_ends_with: ends with - label_issue_fixed_version_updated: Target version updated - setting_project_list_defaults: Projects list defaults + label_last_tab_visited: Última pestaña visitada + field_unique_id: ID único + text_no_subject: sin asunto + setting_password_required_char_classes: Clases de caracteres requeridos para las contraseñas + label_password_char_class_uppercase: mayúsculas + label_password_char_class_lowercase: minúsculas + label_password_char_class_digits: dígitos + label_password_char_class_special_chars: caracteres especiales + text_characters_must_contain: Debe contener %{character_classes}. + label_starts_with: empieza con + label_ends_with: termina con + label_issue_fixed_version_updated: Versión objetivo actualizada + setting_project_list_defaults: Por defecto para la lista de proyectos label_display_type: Mostrar resultados como label_display_type_list: Lista - label_display_type_board: Panel + label_display_type_board: Tablón label_my_bookmarks: Mis marcadores - label_import_time_entries: Import time entries - - - 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 + label_import_time_entries: Importar registros de tiempo diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 6ed5b67..8241001 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -1261,60 +1261,60 @@ pt-BR: label_ldaps_warning: É recomendado utilizar uma conexão LDAPS criptografada com verificação de certificado para evitar qualquer manipulação durante o processo de autenticação. label_nothing_to_preview: Nada para visualizar error_token_expired: Este link de recuperação de senha expirou. Por favor, tente novamente. - error_spent_on_future_date: Cannot log time on a future date - setting_timelog_accept_future_dates: Accept time logs on future dates + error_spent_on_future_date: Não é possível registrar o tempo em uma data futura + setting_timelog_accept_future_dates: Permitir registros de tempo em datas futuras label_delete_link_to_subtask: Excluir relação - error_not_allowed_to_log_time_for_other_users: You are not allowed to log time - for other users - permission_log_time_for_other_users: Log spent time for other users - label_tomorrow: tomorrow - label_next_week: next week - label_next_month: next month - text_role_no_workflow: No workflow defined for this role - text_status_no_workflow: No tracker uses this status in the workflows - setting_mail_handler_preferred_body_part: Preferred part of multipart (HTML) emails - setting_show_status_changes_in_mail_subject: Show status changes in issue mail notifications - subject - label_inherited_from_parent_project: Inherited from parent project - label_inherited_from_group: Inherited from group %{name} - label_trackers_description: Trackers description - label_open_trackers_description: View all trackers description - label_preferred_body_part_text: Text + error_not_allowed_to_log_time_for_other_users: Você não possuí permissão para registrar o tempo + para outros usuários + permission_log_time_for_other_users: Tempo de registro gasto para outros usuários + label_tomorrow: amanhã + label_next_week: próxima semana + label_next_month: próximo mês + text_role_no_workflow: Não foi definido fluxo de trabalho para este papel + text_status_no_workflow: Nenhum tipo de tarefa utiliza esta situação nos fluxos de trabalho + setting_mail_handler_preferred_body_part: Parte preferida de e-mails multipartes (HTML) + setting_show_status_changes_in_mail_subject: Exibir mudança de situação no título do email de notificação + data tarefa + label_inherited_from_parent_project: Herdado do projeto pai + label_inherited_from_group: Herdado do grupo %{name} + label_trackers_description: Descrição dos tipos de tarefa + label_open_trackers_description: Ver todas as descrições dos tipos de tarefas + label_preferred_body_part_text: Texto label_preferred_body_part_html: HTML (experimental) - field_parent_issue_subject: Parent task subject - permission_edit_own_issues: Edit own issues - text_select_apply_tracker: Select tracker - label_updated_issues: Updated issues - text_avatar_server_config_html: The current avatar server is %{url}. - You can configure it in config/configuration.yml. - setting_gantt_months_limit: Maximum number of months displayed on the gantt chart - permission_import_time_entries: Import time entries - label_import_notifications: Send email notifications during the import - text_gs_available: ImageMagick PDF support available (optional) - field_recently_used_projects: Number of recently used projects in jump box - label_optgroup_bookmarks: Bookmarks - label_optgroup_others: Other projects - label_optgroup_recents: Recently used - button_project_bookmark: Add bookmark - button_project_bookmark_delete: Remove bookmark - field_history_default_tab: Issue's history default tab - label_issue_history_properties: Property changes - label_issue_history_notes: Notes - label_last_tab_visited: Last visited tab - field_unique_id: Unique ID - text_no_subject: no subject - setting_password_required_char_classes: Required character classes for passwords - label_password_char_class_uppercase: uppercase letters - label_password_char_class_lowercase: lowercase letters - label_password_char_class_digits: digits - label_password_char_class_special_chars: special characters - text_characters_must_contain: Must contain %{character_classes}. - label_starts_with: starts with - label_ends_with: ends with - label_issue_fixed_version_updated: Target version updated - setting_project_list_defaults: Projects list defaults - label_display_type: Display results as - label_display_type_list: List - label_display_type_board: Board - label_my_bookmarks: My bookmarks - label_import_time_entries: Import time entries + field_parent_issue_subject: Assunto da tarefa pai + permission_edit_own_issues: Editar as próprias tarefas + text_select_apply_tracker: Selecione o tipo de tarefa + label_updated_issues: Tarefas atualizadas + text_avatar_server_config_html: O servidor de avatar atual é % {url} . + Você pode configurá-lo em config / configuration.yml. + setting_gantt_months_limit: Número máximo de meses exibidos no gráfico de Gantt + permission_import_time_entries: Importar tempo gasto + label_import_notifications: Enviar notificações por e-mail durante a importação + text_gs_available: Suporte ao ImageMagick PDF disponível (opcional) + field_recently_used_projects: Número de projetos usados recentemente no acesso rápido + label_optgroup_bookmarks: Marcadores + label_optgroup_others: Outros projetos + label_optgroup_recents: Recentes + button_project_bookmark: Adicionar marcador + button_project_bookmark_delete: Remover marcador + field_history_default_tab: Aba padrão no histórico das tarefas + label_issue_history_properties: Campos alterados + label_issue_history_notes: Notas + label_last_tab_visited: Última aba visitada + field_unique_id: ID único + text_no_subject: sem assunto + setting_password_required_char_classes: Classes de caracteres obrigatórias para as senhas + label_password_char_class_uppercase: Letras maiúsculas + label_password_char_class_lowercase: Letras minúsculas + label_password_char_class_digits: digitos + label_password_char_class_special_chars: caracteres especiais + text_characters_must_contain: Precisa conter %{character_classes}. + label_starts_with: começando com + label_ends_with: terminando com + label_issue_fixed_version_updated: Versão atualizada + setting_project_list_defaults: Padrão da lista de projetos + label_display_type: Exibir resultados como + label_display_type_list: Lista + label_display_type_board: Quadro + label_my_bookmarks: Meus marcaroes + label_import_time_entries: Importar tempo gasto diff --git a/db/migrate/20090503121505_populate_member_roles.rb b/db/migrate/20090503121505_populate_member_roles.rb index e5f246a..75ba261 100644 --- a/db/migrate/20090503121505_populate_member_roles.rb +++ b/db/migrate/20090503121505_populate_member_roles.rb @@ -2,7 +2,7 @@ class PopulateMemberRoles < ActiveRecord::Migration[4.2] def self.up MemberRole.delete_all Member.all.each do |member| - MemberRole.create!(:member_id => member.id, :role_id => member.role_id) + MemberRole.insert!({:member_id => member.id, :role_id => member.role_id}) end end diff --git a/doc/CHANGELOG b/doc/CHANGELOG index 0744890..90d51b5 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -1,8 +1,279 @@ == Redmine changelog Redmine - project management software -Copyright (C) 2006-2019 Jean-Philippe Lang -http://www.redmine.org/ +Copyright (C) 2006-2021 Jean-Philippe Lang +https://www.redmine.org/ + +== 2022-03-28 v4.1.7 + +=== [Attachments] + +* Defect #36013: Paste image mixed with other DataTransferItem + +=== [Database] + +* Defect #36766: Database migration from Redmine 0.8.7 or earlier fails + +=== [Documents] + +* Defect #36686: Allow pasting screenshots from clipboard in documents + +=== [Issues filter] + +* Defect #30924: Filter on Target version's Status in subproject doesn't work on version from top project + +=== [Projects] + +* Defect #36593: User without permissions to view required project custom fields cannot create new projects + +=== [Rails support] + +* Patch #36757: Update Rails to 5.2.6.3 + +== 2022-02-20 v4.1.6 + +=== [Gantt] + +* Defect #35027: Gantt PNG export ignores imagemagick_convert_command + +=== [Gems support] + +* Defect #35435: Psych 4: aliases in database.yml cause Psych::BadAlias exception +* Defect #36226: Psych 4: Psych::DisallowedClass exception when unserializing a setting value + +=== [Issues] + +* Defect #36455: Text custom field values are not aligned with their labels when text formatting is enabled + +=== [Rails support] + +* Patch #36633: Update Rails to 5.2.6.2 + +=== [UI] + +* Defect #35090: Permission check of the setting button on the issues page mismatches button semantics +* Defect #36363: Cannot select text in a table with a context menu available +* Patch #36378: Update copyright year in the footer to 2022 + +=== [Wiki] + +* Defect #36494: WikiContentVersion API returns 500 if author is nil +* Defect #36561: Wiki revision page does not return 404 if revision does not exist + +== 2021-10-10 v4.1.5 + +=== [Administration] + +* Defect #35731: Password and Confirmation fields are marked as required when editing a user + +=== [Attachments] + +* Defect #35715: File upload fails when run with uWSGI + +=== [Issues] + +* Defect #35642: Long text custom field values are not aligned with their labels + +=== [Issues planning] + +* Defect #35669: Prints of Issues Report details are messed-up due to the size of the graphs + +=== [Permissions and roles] + +* Defect #35634: Attachments deletable even though issue edit not permitted + +=== [Security] + +* Defect #35789: Redmine is leaking usernames on activities index view +* Patch #35463: Enforce stricter class filtering in WatchersController + +=== [UI] + +* Defect #34834: Line breaks in the description of a custom field are ignored in a tooltip + +== 2021-08-01 v4.1.4 + +=== [Accounts / authentication] + +* Defect #35226: Add SameSite=Lax to cookies to fix warnings in web browsers + +=== [Attachments] + +* Defect #33752: Uploading a big file fails with NoMemoryError + +=== [Gantt] + +* Defect #34694: Progress bar for a shared version on gantt disappears when the tree is collapsed and then expanded + +=== [Gems support] + +* Defect #35621: Bundler fails to install globalid when using Ruby < 2.6.0 + +=== [Issues] + +* Defect #35134: Change total spent time link to global time entries when issue has subtasks that can be on non descendent projects + +=== [Issues filter] + +* Defect #35201: Duplicate entries in issue filter values + +=== [Rails support] + +* Patch #35214: Update Rails to 5.2.6 + +=== [Time tracking] + +* Defect #34856: Time entry error on private issue + +== 2021-04-26 v4.1.3 + +=== [Activity view] + +* Defect #34933: Atom feed of the activity page does not contain items after the second page + +=== [Email receiving] + +* Defect #35100: MailHandler raises NameError exception when generating error message + +=== [Gems support] + +* Patch #34969: Remove dependency on MimeMagic + +=== [Issues] + +* Defect #34921: Do not journalize attachments that are added during a "Copy Issue" operation + +=== [Performance] + +* Patch #35034: Improve loading speed of workflow page + +=== [Rails support] + +* Patch #34966: Update Rails to 5.2.5 + +=== [Security] + +* Defect #34367: Allowed filename extensions of attachments can be circumvented +* Defect #34950: SysController and MailHandlerController are vulnerable to timing attack +* Defect #35045: Mail handler bypasses add_issue_notes permission +* Defect #35085: Arbitrary file read in Git adapter + +=== [Text formatting] + +* Defect #34894: User link using @ not working at the end of line + +=== [UI] + +* Patch #34955: Update copyright year in the footer to 2021 + +== 2021-03-21 v4.1.2 + +=== [Accounts / authentication] + +* Defect #33926: Rake tasks "db:encrypt" and "db:decrypt" may fail due to validation error + +=== [Administration] + +* Defect #33310: Warnings while running redmine:load_default_data rake task +* Defect #33339: Broken layout of the preview tab of "Welcome text" setting due to unexpectedly applied padding-left +* Defect #33355: TypeError when attempting to update a user with a blank email address +* Defect #34247: Web browser freezes when displaying workflow page with a large number of issue statuses +* Patch #32341: Show tooltip when hovering on repeat-value link in Field permission tab + +=== [Attachments] + +* Defect #33283: Thumbnail support for PDF attachments may not be detected +* Defect #33459: The order of thumbnails in journals does not match the order of file name list +* Defect #33639: Cannot paste image from clipboard when copying the image from web browsers or some apps +* Defect #33769: When creating more than two identical attachments in a single db transaction, the first one always ends up unreadable +* Patch #34479: Fix possible race condition with parallel, identical file uploads + +=== [Custom fields] + +* Defect #33275: Possible values field in list format custom field form is not marked as required +* Defect #33550: Per role visibility settings for spent time custom fields is not properly checked + +=== [Documentation] + +* Defect #33939: Unnecessary translation of {{toc}} macros in Russian Wiki formatting help + +=== [Filters] + +* Defect #33281: Totals of custom fields may not be sorted as configured +* Defect #34375: "is not" operator for Subproject filter incorrectly excludes closed subprojects + +=== [Gantt] + +* Defect #33140: Gantt bar is not displayed if the due date is the leftmost date or the start date is the rightmost date +* Defect #33175: Starting or ending marker is not displayed if they are on the leftmost or rightmost boundary of the gantt +* Defect #33220: Parent task subject column in gantt is not fully displayed when the column is widened +* Defect #33724: Selected gantt columns are not displayed with MS Edge Legacy + +=== [Gems support] + +* Defect #33206: Unable to autoload constant Version.table_name if gems uses Version class +* Defect #33768: Bundler may fail to install stringio if Ruby prior to 2.5 is used +* Patch #34461: Update Redcarpet to 3.5.1 +* Patch #34619: Update Nokogiri to 1.11 + +=== [I18n] + +* Defect #33452: Untranslated string "diff" in journal detail + +=== [Issues] + +* Defect #33338: Property changes tab does not show journals with both property changes and notes +* Defect #33576: Done ratio of a parent issue may be shown as 99% even though all subtasks are completed + +=== [Issues list] + +* Defect #33273: Total estimated time column shows up as decimal value regardless of time setting +* Defect #33548: Column header is clickable even when the column is not actually sortable +* Defect #34297: Subprojects issues are not displayed on main project when all subprojects are closed + +=== [Projects] + +* Defect #33889: Do not show list for custom fields without list entry on project overview +* Patch #34595: Filter list of recent projects in the project jump box + +=== [REST API] + +* Defect #33417: Updating an issue via REST API causes internal server error if invalid project id is specified +* Defect #34615: 'Search' falsy parameters are not respected + +=== [Security] + +* Defect #33846: Inline issue auto complete doesn't sanitize HTML tags + +=== [SEO] + +* Defect #6734: robots.txt: disallow crawling issues list with a query string + +=== [Security] + +* Defect #33360: Names of private projects are leaked by issue journal details that contain project_id changes +* Defect #33689: Issues API bypasses add_issue_notes permission +* Feature #33906: Upgrade Rails to 5.2.4.5 + +=== [Themes] + +* Defect #8251: Classic Theme: Missed base line + +=== [Time tracking] + +* Defect #33341: Time entry user is shown twice in the User drop-down when editing spent time + +=== [Translations] + +* Defect #34447: Typo in translation string 'setting_issue_list_default_columns': s//Isuses/Issues +* Patch #34200: Portuguese (Brazil) translation for 4.1-stable +* Patch #34439: Spanish translation update for 4.1-stable + +=== [UI] + +* Defect #33563: File selection buttons are not fully displayed with Google Chrome in some language +* Feature #34123: System tests for inline auto complete feature +* Patch #33958: Jump to end of line in editor when starting list or quote == 2020-04-06 v4.1.1 diff --git a/lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb b/lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb index 4df4b6a..e0969f2 100644 --- a/lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb +++ b/lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb @@ -107,7 +107,7 @@ module Redmine end next unless a a.description = attachment['description'].to_s.strip - if a.new_record? + if a.new_record? || a.invalid? unsaved_attachments << a else saved_attachments << a diff --git a/lib/redmine/ciphering.rb b/lib/redmine/ciphering.rb index eea7521..49b76b7 100644 --- a/lib/redmine/ciphering.rb +++ b/lib/redmine/ciphering.rb @@ -74,7 +74,7 @@ module Redmine all.each do |object| clear = object.send(attribute) object.send "#{attribute}=", clear - raise(ActiveRecord::Rollback) unless object.save(:validation => false) + raise(ActiveRecord::Rollback) unless object.save(validate: false) end end ? true : false end @@ -84,7 +84,7 @@ module Redmine all.each do |object| clear = object.send(attribute) object.send :write_attribute, attribute, clear - raise(ActiveRecord::Rollback) unless object.save(:validation => false) + raise(ActiveRecord::Rollback) unless object.save(validate: false) end end ? true : false end diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb index 4d984cf..1223c0f 100644 --- a/lib/redmine/helpers/gantt.rb +++ b/lib/redmine/helpers/gantt.rb @@ -341,6 +341,7 @@ module Redmine if options[:format] == :html data_options = {} data_options[:collapse_expand] = "issue-#{issue.id}" + data_options[:number_of_rows] = number_of_rows style = "position: absolute;top: #{options[:top]}px; font-size: 0.8em;" content = view.content_tag(:div, view.column_content(options[:column], issue), :style => style, :class => "issue_#{options[:column].name}", :id => "#{options[:column].name}_issue_#{issue.id}", :data => data_options) @columns[options[:column].name] << content if @columns.has_key?(options[:column].name) @@ -378,6 +379,9 @@ module Redmine unless Redmine::Configuration['rmagick_font_path'].nil? font_path = Redmine::Configuration['minimagick_font_path'].presence || Redmine::Configuration['rmagick_font_path'].presence img = MiniMagick::Image.create(".#{format}", false) + if Redmine::Configuration['imagemagick_convert_command'].present? + MiniMagick.cli_path = File.dirname(Redmine::Configuration['imagemagick_convert_command']) + end MiniMagick::Tool::Convert.new do |gc| gc.size('%dx%d' % [subject_width + g_width + 1, height]) gc.xc('white') @@ -623,14 +627,14 @@ module Redmine def coordinates(start_date, end_date, progress, zoom=nil) zoom ||= @zoom coords = {} - if start_date && end_date && start_date < self.date_to && end_date > self.date_from - if start_date > self.date_from + if start_date && end_date && start_date <= self.date_to && end_date >= self.date_from + if start_date >= self.date_from coords[:start] = start_date - self.date_from coords[:bar_start] = start_date - self.date_from else coords[:bar_start] = 0 end - if end_date < self.date_to + if end_date <= self.date_to coords[:end] = end_date - self.date_from + 1 coords[:bar_end] = end_date - self.date_from + 1 else @@ -768,6 +772,7 @@ module Redmine :top_increment => params[:top_increment], :obj_id => "#{object.class}-#{object.id}".downcase, }, + :number_of_rows => number_of_rows, } end if has_children @@ -823,7 +828,10 @@ module Redmine def html_task(params, coords, markers, label, object) output = +'' data_options = {} - data_options[:collapse_expand] = "#{object.class}-#{object.id}".downcase if object + if object + data_options[:collapse_expand] = "#{object.class}-#{object.id}".downcase + data_options[:number_of_rows] = number_of_rows + end css = "task " + case object when Project diff --git a/lib/redmine/project_jump_box.rb b/lib/redmine/project_jump_box.rb index d3116a3..b9f616f 100644 --- a/lib/redmine/project_jump_box.rb +++ b/lib/redmine/project_jump_box.rb @@ -35,6 +35,7 @@ module Redmine projects = projects.like(query) end projects. + visible. index_by(&:id). values_at(*project_ids). # sort according to stored order compact diff --git a/lib/redmine/scm/adapters/abstract_adapter.rb b/lib/redmine/scm/adapters/abstract_adapter.rb index 14f8f09..226c8a1 100644 --- a/lib/redmine/scm/adapters/abstract_adapter.rb +++ b/lib/redmine/scm/adapters/abstract_adapter.rb @@ -178,6 +178,14 @@ module Redmine (path[-1,1] == "/") ? path[0..-2] : path end + def valid_name?(name) + return true if name.nil? + return true if name.is_a?(Integer) && name > 0 + return true if name.is_a?(String) && name =~ /\A[0-9]*\z/ + + false + end + private def retrieve_root_url diff --git a/lib/redmine/scm/adapters/git_adapter.rb b/lib/redmine/scm/adapters/git_adapter.rb index ddb1055..d833369 100644 --- a/lib/redmine/scm/adapters/git_adapter.rb +++ b/lib/redmine/scm/adapters/git_adapter.rb @@ -388,6 +388,18 @@ module Redmine nil end + def valid_name?(name) + return false unless name.is_a?(String) + + return false if name.start_with?('-', '/', 'refs/heads/', 'refs/remotes/') + return false if name == 'HEAD' + + git_cmd ['show-ref', '--heads', '--tags', '--quiet', '--', name] + true + rescue ScmCommandAborted + false + end + class Revision < Redmine::Scm::Adapters::Revision # Returns the readable identifier def format_identifier diff --git a/lib/redmine/scm/adapters/mercurial_adapter.rb b/lib/redmine/scm/adapters/mercurial_adapter.rb index fd3c80d..1ef808f 100644 --- a/lib/redmine/scm/adapters/mercurial_adapter.rb +++ b/lib/redmine/scm/adapters/mercurial_adapter.rb @@ -291,6 +291,15 @@ module Redmine Annotate.new end + def valid_name?(name) + return false unless name.nil? || name.is_a?(String) + + # Mercurials names don't need to be checked further as its CLI + # interface is restrictive enough to reject any invalid names on its + # own. + true + end + class Revision < Redmine::Scm::Adapters::Revision # Returns the readable identifier def format_identifier diff --git a/lib/redmine/thumbnail.rb b/lib/redmine/thumbnail.rb index cc9656b..2e506dc 100644 --- a/lib/redmine/thumbnail.rb +++ b/lib/redmine/thumbnail.rb @@ -18,7 +18,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'fileutils' -require 'mimemagic' module Redmine module Thumbnail @@ -33,15 +32,11 @@ module Redmine return nil unless convert_available? return nil if is_pdf && !gs_available? unless File.exists?(target) - mime_type = File.open(source) {|f| MimeMagic.by_magic(f).try(:type) } - return nil if mime_type.nil? + # Make sure we only invoke Imagemagick if the file type is allowed + mime_type = File.open(source) {|f| Marcel::MimeType.for(f)} return nil if !ALLOWED_TYPES.include? mime_type return nil if is_pdf && mime_type != "application/pdf" - # Make sure we only invoke Imagemagick if the file type is allowed - unless File.open(source) {|f| ALLOWED_TYPES.include? MimeMagic.by_magic(f).try(:type) } - return nil - end directory = File.dirname(target) unless File.exists?(directory) FileUtils.mkdir_p directory diff --git a/lib/redmine/version.rb b/lib/redmine/version.rb index 18bf8ea..40b8da2 100644 --- a/lib/redmine/version.rb +++ b/lib/redmine/version.rb @@ -7,7 +7,7 @@ module Redmine module VERSION MAJOR = 4 MINOR = 1 - TINY = 1 + TINY = 7 # Branch values: # * official release: nil diff --git a/lib/tasks/load_default_data.rake b/lib/tasks/load_default_data.rake index bcfff7c..cc6774b 100644 --- a/lib/tasks/load_default_data.rake +++ b/lib/tasks/load_default_data.rake @@ -2,7 +2,6 @@ desc 'Load Redmine default configuration data. Language is chosen interactively namespace :redmine do task :load_default_data => :environment do - require 'custom_field' include Redmine::I18n set_language_if_valid('en') diff --git a/public/help/ru/wiki_syntax_detailed_textile.html b/public/help/ru/wiki_syntax_detailed_textile.html index 77f812b..3024690 100644 --- a/public/help/ru/wiki_syntax_detailed_textile.html +++ b/public/help/ru/wiki_syntax_detailed_textile.html @@ -295,8 +295,8 @@ bq. Rails - это полноценный, многоуровневый фрей

    Содержание

    -{{Содержание}} => содержание, выровненное по левому краю
    -{{>Содержание}} => содержание, выровненное по правому краю
    +{{toc}} => содержание, выровненное по левому краю
    +{{>toc}} => содержание, выровненное по правому краю
     

    Horizontal Rule

    diff --git a/public/javascripts/application.js b/public/javascripts/application.js index e4e902d..d023f5b 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -8,6 +8,12 @@ $.ajaxPrefilter(function (s) { } }); +function sanitizeHTML(string) { + var temp = document.createElement('span'); + temp.textContent = string; + return temp.innerHTML; +} + function checkAll(id, checked) { $('#'+id).find('input[type=checkbox]:enabled').prop('checked', checked); } @@ -371,15 +377,29 @@ function showIssueHistory(journal, url) { switch(journal) { case 'notes': + tab_content.find('.journal').show(); tab_content.find('.journal:not(.has-notes)').hide(); - tab_content.find('.journal.has-notes').show(); + tab_content.find('.journal .wiki').show(); + tab_content.find('.journal .contextual .journal-actions').show(); + + // always show thumbnails in notes tab + var thumbnails = tab_content.find('.journal .thumbnails'); + thumbnails.show(); + // show journals without notes, but with thumbnails + thumbnails.parents('.journal').show(); break; case 'properties': - tab_content.find('.journal.has-notes').hide(); - tab_content.find('.journal:not(.has-notes)').show(); + tab_content.find('.journal').show(); + tab_content.find('.journal:not(.has-details)').hide(); + tab_content.find('.journal .wiki').hide(); + tab_content.find('.journal .thumbnails').hide(); + tab_content.find('.journal .contextual .journal-actions').hide(); break; default: tab_content.find('.journal').show(); + tab_content.find('.journal .wiki').show(); + tab_content.find('.journal .thumbnails').show(); + tab_content.find('.journal .contextual .journal-actions').show(); } return false; @@ -933,7 +953,7 @@ $(document).ready(function(){ $('#history .tabs').on('click', 'a', function(e){ var tab = $(e.target).attr('id').replace('tab-',''); - document.cookie = 'history_last_tab=' + tab + document.cookie = 'history_last_tab=' + tab + '; SameSite=Lax' }); }); @@ -997,15 +1017,15 @@ function setupAttachmentDetail() { $(function () { - $('[title]').tooltip({ - show: { - delay: 400 - }, - position: { - my: "center bottom-5", - at: "center top" - } - }); + $("[title]:not(.no-tooltip)").tooltip({ + show: { + delay: 400 + }, + position: { + my: "center bottom-5", + at: "center top" + } + }); }); function inlineAutoComplete(element) { @@ -1048,6 +1068,9 @@ function inlineAutoComplete(element) { requireLeadingSpace: true, selectTemplate: function (issue) { return '#' + issue.original.id; + }, + menuItemTemplate: function (issue) { + return sanitizeHTML(issue.original.label); } }); diff --git a/public/javascripts/attachments.js b/public/javascripts/attachments.js index 39315d8..b5173c0 100644 --- a/public/javascripts/attachments.js +++ b/public/javascripts/attachments.js @@ -257,13 +257,12 @@ function copyImageFromClipboard(e) { if (!$(e.target).hasClass('wiki-edit')) { return; } var clipboardData = e.clipboardData || e.originalEvent.clipboardData if (!clipboardData) { return; } - if (clipboardData.types.some(function(t){ return /^text/.test(t); })) { return; } + if (clipboardData.types.some(function(t){ return /^text\/plain$/.test(t); })) { return; } - var items = clipboardData.items - for (var i = 0 ; i < items.length ; i++) { - var item = items[i]; - if (item.type.indexOf("image") != -1) { - var blob = item.getAsFile(); + var files = clipboardData.files + for (var i = 0 ; i < files.length ; i++) { + var file = files[i]; + if (file.type.indexOf("image") != -1) { var date = new Date(); var filename = 'clipboard-' + date.getFullYear() @@ -272,9 +271,8 @@ function copyImageFromClipboard(e) { + ('0'+date.getHours()).slice(-2) + ('0'+date.getMinutes()).slice(-2) + '-' + randomKey(5).toLocaleLowerCase() - + '.' + blob.name.split('.').pop(); - var file = new Blob([blob], {type: blob.type}); - file.name = filename; + + '.' + file.name.split('.').pop(); + var inputEl = $('input:file.filedrop').first() handleFileDropEvent.target = e.target; addFile(inputEl, file, true); diff --git a/public/javascripts/context_menu.js b/public/javascripts/context_menu.js index b978fe4..cfe1a10 100644 --- a/public/javascripts/context_menu.js +++ b/public/javascripts/context_menu.js @@ -46,6 +46,7 @@ function contextMenuClick(event) { } else { if (event.ctrlKey || event.metaKey) { contextMenuToggleSelection(tr); + contextMenuClearDocumentSelection(); } else if (event.shiftKey) { lastSelected = contextMenuLastSelected(); if (lastSelected.length) { @@ -53,6 +54,7 @@ function contextMenuClick(event) { $('.hascontextmenu').each(function(){ if (toggling || $(this).is(tr)) { contextMenuAddSelection($(this)); + contextMenuClearDocumentSelection(); } if ($(this).is(tr) || $(this).is(lastSelected)) { toggling = !toggling; @@ -191,7 +193,6 @@ function contextMenuToggleSelection(tr) { function contextMenuAddSelection(tr) { tr.addClass('context-menu-selection'); contextMenuCheckSelectionBox(tr, true); - contextMenuClearDocumentSelection(); } function contextMenuRemoveSelection(tr) { diff --git a/public/javascripts/gantt.js b/public/javascripts/gantt.js index efda080..502d7eb 100644 --- a/public/javascripts/gantt.js +++ b/public/javascripts/gantt.js @@ -253,13 +253,16 @@ ganttEntryClick = function(e){ subject.nextAll('div').each(function(_, element){ var el = $(element); var json = el.data('collapse-expand'); + var number_of_rows = el.data('number-of-rows'); + var el_task_bars = '#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"][data-number-of-rows="' + number_of_rows + '"]'; + var el_selected_columns = 'td.gantt_selected_column div[data-collapse-expand="' + json.obj_id + '"][data-number-of-rows="' + number_of_rows + '"]'; if(out_of_hierarchy || parseInt(el.css('left')) <= subject_left){ out_of_hierarchy = true; if(target_shown == null) return false; var new_top_val = parseInt(el.css('top')) + total_height * (target_shown ? -1 : 1); el.css('top', new_top_val); - $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"], td.gantt_selected_column div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, el){ + $([el_task_bars, el_selected_columns].join()).each(function(_, el){ $(el).css('top', new_top_val); }); return true; @@ -272,15 +275,14 @@ ganttEntryClick = function(e){ total_height = 0; } if(is_shown == target_shown){ - $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, task) { + $(el_task_bars).each(function(_, task) { var el_task = $(task); if(!is_shown) el_task.css('top', target_top + total_height); if(!el_task.hasClass('tooltip')) el_task.toggle(!is_shown); }); - $('td.gantt_selected_column div[data-collapse-expand="' + json.obj_id + '"]' - ).each(function (_, attr) { + $(el_selected_columns).each(function (_, attr) { var el_attr = $(attr); if (!is_shown) el_attr.css('top', target_top + total_height); diff --git a/public/javascripts/jquery-2.2.4-ui-1.11.0-ujs-5.2.4.5.js b/public/javascripts/jquery-2.2.4-ui-1.11.0-ujs-5.2.4.5.js new file mode 100644 index 0000000..0875a3c --- /dev/null +++ b/public/javascripts/jquery-2.2.4-ui-1.11.0-ujs-5.2.4.5.js @@ -0,0 +1,743 @@ +/*! jQuery v2.2.4 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c; +}catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("