diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2008-04-28 10:36:12 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2008-04-28 10:36:12 +0000 |
commit | 10191813ec9b5671ffd58008fcc832379f811009 (patch) | |
tree | 1238124192c80d6d53ccb907580394c5bd1b9f96 | |
parent | be071deae29935ebb4dbcd3a7e445e290061b883 (diff) | |
download | redmine-10191813ec9b5671ffd58008fcc832379f811009.tar.gz redmine-10191813ec9b5671ffd58008fcc832379f811009.zip |
Merged r1307 to r1369 from trunk.
git-svn-id: http://redmine.rubyforge.org/svn/branches/0.7-stable@1370 e93f8b46-1217-0410-a6f0-8f06a7374b81
139 files changed, 1907 insertions, 845 deletions
diff --git a/app/controllers/account_controller.rb b/app/controllers/account_controller.rb index e719e8c9b..b9224c158 100644 --- a/app/controllers/account_controller.rb +++ b/app/controllers/account_controller.rb @@ -56,6 +56,8 @@ class AccountController < ApplicationController flash.now[:error] = l(:notice_account_invalid_creditentials) end end + rescue User::OnTheFlyCreationFailure + flash.now[:error] = 'Redmine could not retrieve the required information from the LDAP to create your account. Please, contact your Redmine administrator.' end # Log out current user and redirect to welcome page diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 98cb4a827..abf621641 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -150,6 +150,7 @@ class ApplicationController < ActionController::Base def render_feed(items, options={}) @items = items || [] @items.sort! {|x,y| y.event_datetime <=> x.event_datetime } + @items = @items.slice(0, Setting.feeds_limit.to_i) @title = options[:title] || Setting.app_title render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml' end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index dbc3161d7..84b95741e 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -73,6 +73,8 @@ class IssuesController < ApplicationController # Send html if the query is not valid render(:template => 'issues/index.rhtml', :layout => !request.xhr?) end + rescue ActiveRecord::RecordNotFound + render_404 end def changes @@ -87,6 +89,8 @@ class IssuesController < ApplicationController end @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name) render :layout => false, :content_type => 'application/atom+xml' + rescue ActiveRecord::RecordNotFound + render_404 end def show @@ -136,7 +140,9 @@ class IssuesController < ApplicationController requested_status = IssueStatus.find_by_id(params[:issue][:status_id]) # Check that the user is allowed to apply the requested status @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status - @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) } + @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, + :customized => @issue, + :value => (params[:custom_fields] ? params[:custom_fields][x.id.to_s] : nil)) } @issue.custom_values = @custom_values if @issue.save attach_files(@issue, params[:attachments]) @@ -338,8 +344,8 @@ class IssuesController < ApplicationController end def preview - issue = @project.issues.find_by_id(params[:id]) - @attachements = issue.attachments if issue + @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? + @attachements = @issue.attachments if @issue @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil) render :partial => 'common/preview' end @@ -384,7 +390,10 @@ private # Retrieve query from session or build a new query def retrieve_query if !params[:query_id].blank? - @query = Query.find(params[:query_id], :conditions => {:project_id => (@project ? @project.id : nil)}) + cond = "project_id IS NULL" + cond << " OR project_id = #{@project.id}" if @project + @query = Query.find(params[:query_id], :conditions => cond) + @query.project = @project session[:query] = {:id => @query.id, :project_id => @query.project_id} else if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil) @@ -404,6 +413,7 @@ private else @query = Query.find_by_id(session[:query][:id]) if session[:query][:id] @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters]) + @query.project = @project end end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 199b2f0c5..b71ec1ecd 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -66,20 +66,20 @@ class ProjectsController < ApplicationController :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}", :order => 'name') @project = Project.new(params[:project]) - @project.enabled_module_names = Redmine::AccessControl.available_project_modules if request.get? @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project) } @project.trackers = Tracker.all @project.is_public = Setting.default_projects_public? + @project.enabled_module_names = Redmine::AccessControl.available_project_modules else @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids] @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) } @project.custom_values = @custom_values + @project.enabled_module_names = params[:enabled_modules] if @project.save - @project.enabled_module_names = params[:enabled_modules] flash[:notice] = l(:notice_successful_create) redirect_to :controller => 'admin', :action => 'projects' - end + end end end @@ -204,7 +204,10 @@ class ProjectsController < ApplicationController end def list_files - @versions = @project.versions.sort.reverse + sort_init "#{Attachment.table_name}.filename", "asc" + sort_update + @versions = @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse + render :layout => !request.xhr? end # Show changelog for @project @@ -338,8 +341,9 @@ class ProjectsController < ApplicationController :include => [:tracker, :status, :assigned_to, :priority, :project], :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?)) AND #{Issue.table_name}.tracker_id IN (#{@selected_tracker_ids.join(',')})", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt] ) unless @selected_tracker_ids.empty? + events += Version.find(:all, :include => :project, + :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt]) end - events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt]) @calendar.events = events render :layout => false if request.xhr? @@ -383,8 +387,9 @@ class ProjectsController < ApplicationController :include => [:tracker, :status, :assigned_to, :priority, :project], :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to] ) unless @selected_tracker_ids.empty? + @events += Version.find(:all, :include => :project, + :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to]) end - @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to]) @events.sort! {|x,y| x.start_date <=> y.start_date } if params[:format]=='pdf' diff --git a/app/controllers/queries_controller.rb b/app/controllers/queries_controller.rb index 0a762eee0..da2c4a2c8 100644 --- a/app/controllers/queries_controller.rb +++ b/app/controllers/queries_controller.rb @@ -18,19 +18,14 @@ class QueriesController < ApplicationController layout 'base' menu_item :issues - before_filter :find_project, :authorize - - def index - @queries = @project.queries.find(:all, - :order => "name ASC", - :conditions => ["is_public = ? or user_id = ?", true, (User.current.logged? ? User.current.id : 0)]) - end + before_filter :find_query, :except => :new + before_filter :find_optional_project, :only => :new def new @query = Query.new(params[:query]) - @query.project = @project + @query.project = params[:query_is_for_all] ? nil : @project @query.user = User.current - @query.is_public = false unless current_role.allowed_to?(:manage_public_queries) + @query.is_public = false unless (@query.project && current_role.allowed_to?(:manage_public_queries)) || User.current.admin? @query.column_names = nil if params[:default_columns] params[:fields].each do |field| @@ -52,7 +47,8 @@ class QueriesController < ApplicationController @query.add_filter(field, params[:operators][field], params[:values][field]) end if params[:fields] @query.attributes = params[:query] - @query.is_public = false unless current_role.allowed_to?(:manage_public_queries) + @query.project = nil if params[:query_is_for_all] + @query.is_public = false unless (@query.project && current_role.allowed_to?(:manage_public_queries)) || User.current.admin? @query.column_names = nil if params[:default_columns] if @query.save @@ -64,18 +60,21 @@ class QueriesController < ApplicationController def destroy @query.destroy if request.post? - redirect_to :controller => 'queries', :project_id => @project + redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 end private - def find_project - if params[:id] - @query = Query.find(params[:id]) - @project = @query.project - render_403 unless @query.editable_by?(User.current) - else - @project = Project.find(params[:project_id]) - end + def find_query + @query = Query.find(params[:id]) + @project = @query.project + render_403 unless @query.editable_by?(User.current) + rescue ActiveRecord::RecordNotFound + render_404 + end + + def find_optional_project + @project = Project.find(params[:project_id]) if params[:project_id] + User.current.allowed_to?(:save_queries, @project, :global => true) rescue ActiveRecord::RecordNotFound render_404 end diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 10c235d65..64eb05793 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -19,8 +19,8 @@ require 'SVG/Graph/Bar' require 'SVG/Graph/BarHorizontal' require 'digest/sha1' -class ChangesetNotFound < Exception -end +class ChangesetNotFound < Exception; end +class InvalidRevisionParam < Exception; end class RepositoriesController < ApplicationController layout 'base' @@ -51,8 +51,8 @@ class RepositoriesController < ApplicationController def show # check if new revisions have been committed in the repository @repository.fetch_changesets if Setting.autofetch_changesets? - # get entries for the browse frame - @entries = @repository.entries('') + # root entries + @entries = @repository.entries('', @rev) # latest changesets @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC") show_error_not_found unless @entries || @changesets.any? @@ -65,7 +65,8 @@ class RepositoriesController < ApplicationController if request.xhr? @entries ? render(:partial => 'dir_list_content') : render(:nothing => true) else - show_error_not_found unless @entries + show_error_not_found and return unless @entries + render :action => 'browse' end rescue Redmine::Scm::Adapters::CommandFailed => e show_error_command_failed(e.message) @@ -95,6 +96,12 @@ class RepositoriesController < ApplicationController end def entry + @entry = @repository.scm.entry(@path, @rev) + show_error_not_found and return unless @entry + + # If the entry is a dir, show the browser + browse and return if @entry.is_dir? + @content = @repository.scm.cat(@path, @rev) show_error_not_found and return unless @content if 'raw' == params[:format] || @content.is_binary_data? @@ -135,7 +142,6 @@ class RepositoriesController < ApplicationController end def diff - @rev_to = params[:rev_to] @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) @@ -180,6 +186,8 @@ private render_404 end + REV_PARAM_RE = %r{^[a-f0-9]*$} + def find_repository @project = Project.find(params[:id]) @repository = @project.repository @@ -187,8 +195,12 @@ private @path = params[:path].join('/') unless params[:path].nil? @path ||= '' @rev = params[:rev] + @rev_to = params[:rev_to] + raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE) rescue ActiveRecord::RecordNotFound render_404 + rescue InvalidRevisionParam + show_error_not_found end def show_error_not_found @@ -255,6 +267,9 @@ private commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10 + # Remove email adress in usernames + fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } + graph = SVG::Graph::BarHorizontal.new( :height => 300, :width => 500, diff --git a/app/controllers/timelog_controller.rb b/app/controllers/timelog_controller.rb index 8cfe225d1..29c2635d6 100644 --- a/app/controllers/timelog_controller.rb +++ b/app/controllers/timelog_controller.rb @@ -26,6 +26,8 @@ class TimelogController < ApplicationController include SortHelper helper :issues include TimelogHelper + helper :custom_fields + include CustomFieldsHelper def report @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id", @@ -45,37 +47,40 @@ class TimelogController < ApplicationController :label => :label_tracker}, 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id", :klass => Enumeration, - :label => :label_activity} + :label => :label_activity}, + 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id", + :klass => Issue, + :label => :label_issue} } + # Add list and boolean custom fields as available criterias + @project.all_custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf| + @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM custom_values c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = issues.id)", + :format => cf.field_format, + :label => cf.name} + end + @criterias = params[:criterias] || [] @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria} @criterias.uniq! @criterias = @criterias[0,3] - @columns = (params[:period] && %w(year month week).include?(params[:period])) ? params[:period] : 'month' + @columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month' - if params[:date_from] - begin; @date_from = params[:date_from].to_date; rescue; end - end - if params[:date_to] - begin; @date_to = params[:date_to].to_date; rescue; end - end - @date_from ||= Date.civil(Date.today.year, 1, 1) - @date_to ||= (Date.civil(Date.today.year, Date.today.month, 1) >> 1) - 1 + retrieve_date_range unless @criterias.empty? sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ') sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ') - sql = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours" + sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours" sql << " FROM #{TimeEntry.table_name}" sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id" sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id" sql << " WHERE (%s)" % @project.project_condition(Setting.display_subprojects_issues?) sql << " AND (%s)" % Project.allowed_to_condition(User.current, :view_time_entries) - sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@date_from.to_time), ActiveRecord::Base.connection.quoted_date(@date_to.to_time)] - sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek" + sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@from.to_time), ActiveRecord::Base.connection.quoted_date(@to.to_time)] + sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on" @hours = ActiveRecord::Base.connection.select_all(sql) @@ -87,90 +92,51 @@ class TimelogController < ApplicationController row['month'] = "#{row['tyear']}-#{row['tmonth']}" when 'week' row['week'] = "#{row['tyear']}-#{row['tweek']}" + when 'day' + row['day'] = "#{row['spent_on']}" end end @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f} - end - - @periods = [] - date_from = @date_from - # 100 columns max - while date_from < @date_to && @periods.length < 100 - case @columns - when 'year' - @periods << "#{date_from.year}" - date_from = date_from >> 12 - when 'month' - @periods << "#{date_from.year}-#{date_from.month}" - date_from = date_from >> 1 - when 'week' - @periods << "#{date_from.year}-#{date_from.cweek}" - date_from = date_from + 7 + + @periods = [] + # Date#at_beginning_of_ not supported in Rails 1.2.x + date_from = @from.to_time + # 100 columns max + while date_from <= @to.to_time && @periods.length < 100 + case @columns + when 'year' + @periods << "#{date_from.year}" + date_from = (date_from + 1.year).at_beginning_of_year + when 'month' + @periods << "#{date_from.year}-#{date_from.month}" + date_from = (date_from + 1.month).at_beginning_of_month + when 'week' + @periods << "#{date_from.year}-#{date_from.to_date.cweek}" + date_from = (date_from + 7.day).at_beginning_of_week + when 'day' + @periods << "#{date_from.to_date}" + date_from = date_from + 1.day + end end end - render :layout => false if request.xhr? + respond_to do |format| + format.html { render :layout => !request.xhr? } + format.csv { send_data(report_to_csv(@criterias, @periods, @hours).read, :type => 'text/csv; header=present', :filename => 'timelog.csv') } + end end def details sort_init 'spent_on', 'desc' sort_update - - @free_period = false - @from, @to = nil, nil - - if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?) - case params[:period].to_s - when 'today' - @from = @to = Date.today - when 'yesterday' - @from = @to = Date.today - 1 - when 'current_week' - @from = Date.today - (Date.today.cwday - 1)%7 - @to = @from + 6 - when 'last_week' - @from = Date.today - 7 - (Date.today.cwday - 1)%7 - @to = @from + 6 - when '7_days' - @from = Date.today - 7 - @to = Date.today - when 'current_month' - @from = Date.civil(Date.today.year, Date.today.month, 1) - @to = (@from >> 1) - 1 - when 'last_month' - @from = Date.civil(Date.today.year, Date.today.month, 1) << 1 - @to = (@from >> 1) - 1 - when '30_days' - @from = Date.today - 30 - @to = Date.today - when 'current_year' - @from = Date.civil(Date.today.year, 1, 1) - @to = Date.civil(Date.today.year, 12, 31) - end - elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?)) - begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end - begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end - @free_period = true - else - # default - end - - @from, @to = @to, @from if @from && @to && @from > @to cond = ARCondition.new cond << (@issue.nil? ? @project.project_condition(Setting.display_subprojects_issues?) : ["#{TimeEntry.table_name}.issue_id = ?", @issue.id]) - if @from - if @to - cond << ['spent_on BETWEEN ? AND ?', @from, @to] - else - cond << ['spent_on >= ?', @from] - end - elsif @to - cond << ['spent_on <= ?', @to] - end + retrieve_date_range + cond << ['spent_on BETWEEN ? AND ?', @from, @to] TimeEntry.visible_by(User.current) do respond_to do |format| @@ -185,6 +151,7 @@ class TimelogController < ApplicationController :limit => @entry_pages.items_per_page, :offset => @entry_pages.current.offset) @total_hours = TimeEntry.sum(:hours, :include => :project, :conditions => cond.conditions).to_f + render :layout => !request.xhr? } format.csv { @@ -205,7 +172,7 @@ class TimelogController < ApplicationController @time_entry.attributes = params[:time_entry] if request.post? and @time_entry.save flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'details', :project_id => @time_entry.project + redirect_to(params[:back_url] || {:action => 'details', :project_id => @time_entry.project}) return end @activities = Enumeration::get_values('ACTI') @@ -238,4 +205,50 @@ private rescue ActiveRecord::RecordNotFound render_404 end + + # Retrieves the date range based on predefined ranges or specific from/to param dates + def retrieve_date_range + @free_period = false + @from, @to = nil, nil + + if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?) + case params[:period].to_s + when 'today' + @from = @to = Date.today + when 'yesterday' + @from = @to = Date.today - 1 + when 'current_week' + @from = Date.today - (Date.today.cwday - 1)%7 + @to = @from + 6 + when 'last_week' + @from = Date.today - 7 - (Date.today.cwday - 1)%7 + @to = @from + 6 + when '7_days' + @from = Date.today - 7 + @to = Date.today + when 'current_month' + @from = Date.civil(Date.today.year, Date.today.month, 1) + @to = (@from >> 1) - 1 + when 'last_month' + @from = Date.civil(Date.today.year, Date.today.month, 1) << 1 + @to = (@from >> 1) - 1 + when '30_days' + @from = Date.today - 30 + @to = Date.today + when 'current_year' + @from = Date.civil(Date.today.year, 1, 1) + @to = Date.civil(Date.today.year, 12, 31) + end + elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?)) + begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end + begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end + @free_period = true + else + # default + end + + @from, @to = @to, @from if @from && @to && @from > @to + @from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today) - 1 + @to ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today) + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index ceb70ab92..48fc6fade 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -83,7 +83,8 @@ class UsersController < ApplicationController end if @user.update_attributes(params[:user]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'list' + # Give a string to redirect_to otherwise it would use status param as the response code + redirect_to(url_for(:action => 'list', :status => params[:status], :page => params[:page])) end end @auth_sources = AuthSource.find(:all) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 77019eba3..47a251053 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -207,8 +207,10 @@ module ApplicationHelper rf = Regexp.new(filename, Regexp::IGNORECASE) # search for the picture in attachments if found = attachments.detect { |att| att.filename =~ rf } - image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found.id - "!#{style}#{image_url}!" + image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found + desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1") + alt = desc.blank? ? nil : "(#{desc})" + "!#{style}#{image_url}#{alt}!" else "!#{style}#{filename}!" end @@ -425,6 +427,10 @@ module ApplicationHelper form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc) end + def back_url_hidden_field_tag + hidden_field_tag 'back_url', (params[:back_url] || request.env['HTTP_REFERER']) + end + def check_all_links(form_name) link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + " | " + @@ -463,9 +469,22 @@ module ApplicationHelper end def calendar_for(field_id) + include_calendar_headers_tags image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) + javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });") end + + def include_calendar_headers_tags + unless @calendar_headers_tags_included + @calendar_headers_tags_included = true + content_for :header_tags do + javascript_include_tag('calendar/calendar') + + javascript_include_tag("calendar/lang/calendar-#{current_language}.js") + + javascript_include_tag('calendar/calendar-setup') + + stylesheet_link_tag('calendar') + end + end + end def wikitoolbar_for(field_id) return '' unless Setting.text_formatting == 'textile' diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 17889fadd..6013f1ec8 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -32,6 +32,19 @@ module IssuesHelper "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" + "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}" end + + def sidebar_queries + unless @sidebar_queries + # User can see public queries and his own queries + visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)]) + # Project specific queries and global queries + visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]) + @sidebar_queries = Query.find(:all, + :order => "name ASC", + :conditions => visible.conditions) + end + @sidebar_queries + end def show_detail(detail, no_html=false) case detail.property diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index 31daf1bd8..22bdec9df 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -58,8 +58,11 @@ module RepositoriesHelper end def with_leading_slash(path) - path ||= '' - path.starts_with?('/') ? path : "/#{path}" + path.to_s.starts_with?('/') ? path : "/#{path}" + end + + def without_leading_slash(path) + path.gsub(%r{^/+}, '') end def subversion_field_tags(form, repository) diff --git a/app/helpers/timelog_helper.rb b/app/helpers/timelog_helper.rb index 05b55907c..db13556a1 100644 --- a/app/helpers/timelog_helper.rb +++ b/app/helpers/timelog_helper.rb @@ -76,4 +76,60 @@ module TimelogHelper export.rewind export end + + def format_criteria_value(criteria, value) + value.blank? ? l(:label_none) : ((k = @available_criterias[criteria][:klass]) ? k.find_by_id(value.to_i) : format_value(value, @available_criterias[criteria][:format])) + end + + def report_to_csv(criterias, periods, hours) + export = StringIO.new + CSV::Writer.generate(export, l(:general_csv_separator)) do |csv| + # Column headers + headers = criterias.collect {|criteria| l(@available_criterias[criteria][:label]) } + headers += periods + headers << l(:label_total) + csv << headers.collect {|c| to_utf8(c) } + # Content + report_criteria_to_csv(csv, criterias, periods, hours) + # Total row + row = [ l(:label_total) ] + [''] * (criterias.size - 1) + total = 0 + periods.each do |period| + sum = sum_hours(select_hours(hours, @columns, period.to_s)) + total += sum + row << (sum > 0 ? "%.2f" % sum : '') + end + row << "%.2f" %total + csv << row + end + export.rewind + export + end + + def report_criteria_to_csv(csv, criterias, periods, hours, level=0) + hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value| + hours_for_value = select_hours(hours, criterias[level], value) + next if hours_for_value.empty? + row = [''] * level + row << to_utf8(format_criteria_value(criterias[level], value)) + row += [''] * (criterias.length - level - 1) + total = 0 + periods.each do |period| + sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s)) + total += sum + row << (sum > 0 ? "%.2f" % sum : '') + end + row << "%.2f" %total + csv << row + + if criterias.length > level + 1 + report_criteria_to_csv(csv, criterias, periods, hours_for_value, level + 1) + end + end + end + + def to_utf8(s) + @ic ||= Iconv.new(l(:general_csv_encoding), 'UTF-8') + begin; @ic.iconv(s.to_s); rescue; s.to_s; end + end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 7bd137161..250ed8ce8 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -22,4 +22,16 @@ module UsersHelper [l(:status_registered), 2], [l(:status_locked), 3]], selected) end + + def change_status_link(user) + url = {:action => 'edit', :id => user, :page => params[:page], :status => params[:status]} + + if user.locked? + link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :post, :class => 'icon icon-unlock' + elsif user.registered? + link_to l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :post, :class => 'icon icon-unlock' + else + link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :post, :class => 'icon icon-lock' + end + end end diff --git a/app/models/attachment.rb b/app/models/attachment.rb index cdcb8d231..08f440816 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -35,48 +35,48 @@ class Attachment < ActiveRecord::Base errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes end - def file=(incomming_file) - unless incomming_file.nil? - @temp_file = incomming_file - if @temp_file.size > 0 - self.filename = sanitize_filename(@temp_file.original_filename) - self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename - self.content_type = @temp_file.content_type.to_s.chomp - self.filesize = @temp_file.size - end - end - end - - def file - nil - end - - # Copy temp file to its final location - def before_save - if @temp_file && (@temp_file.size > 0) - logger.debug("saving '#{self.diskfile}'") - File.open(diskfile, "wb") do |f| - f.write(@temp_file.read) - end - self.digest = Digest::MD5.hexdigest(File.read(diskfile)) - end - # Don't save the content type if it's longer than the authorized length - if self.content_type && self.content_type.length > 255 - self.content_type = nil - end - end - - # Deletes file on the disk - def after_destroy - if self.filename? - File.delete(diskfile) if File.exist?(diskfile) - end - end + def file=(incoming_file) + unless incoming_file.nil? + @temp_file = incoming_file + if @temp_file.size > 0 + self.filename = sanitize_filename(@temp_file.original_filename) + self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename + self.content_type = @temp_file.content_type.to_s.chomp + self.filesize = @temp_file.size + end + end + end - # Returns file's location on disk - def diskfile - "#{@@storage_path}/#{self.disk_filename}" - end + def file + nil + end + + # Copy temp file to its final location + def before_save + if @temp_file && (@temp_file.size > 0) + logger.debug("saving '#{self.diskfile}'") + File.open(diskfile, "wb") do |f| + f.write(@temp_file.read) + end + self.digest = Digest::MD5.hexdigest(File.read(diskfile)) + end + # Don't save the content type if it's longer than the authorized length + if self.content_type && self.content_type.length > 255 + self.content_type = nil + end + end + + # Deletes file on the disk + def after_destroy + if self.filename? + File.delete(diskfile) if File.exist?(diskfile) + end + end + + # Returns file's location on disk + def diskfile + "#{@@storage_path}/#{self.disk_filename}" + end def increment_download increment!(:downloads) @@ -87,18 +87,17 @@ class Attachment < ActiveRecord::Base end def image? - self.filename =~ /\.(jpeg|jpg|gif|png)$/i + self.filename =~ /\.(jpe?g|gif|png)$/i end private def sanitize_filename(value) - # get only the filename, not the whole path - just_filename = value.gsub(/^.*(\\|\/)/, '') - # NOTE: File.basename doesn't work right with Windows paths on Unix - # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/')) + # get only the filename, not the whole path + just_filename = value.gsub(/^.*(\\|\/)/, '') + # NOTE: File.basename doesn't work right with Windows paths on Unix + # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/')) - # Finally, replace all non alphanumeric, underscore or periods with underscore - @filename = just_filename.gsub(/[^\w\.\-]/,'_') + # Finally, replace all non alphanumeric, hyphens or periods with underscore + @filename = just_filename.gsub(/[^\w\.\-]/,'_') end - end diff --git a/app/models/changeset.rb b/app/models/changeset.rb index ce9ea28ca..3e95ce111 100644 --- a/app/models/changeset.rb +++ b/app/models/changeset.rb @@ -35,6 +35,10 @@ class Changeset < ActiveRecord::Base validates_uniqueness_of :revision, :scope => :repository_id validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true + def revision=(r) + write_attribute :revision, (r.nil? ? nil : r.to_s) + end + def comments=(comment) write_attribute(:comments, comment.strip) end diff --git a/app/models/issue.rb b/app/models/issue.rb index 1d25a4604..8082e43b7 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -93,7 +93,11 @@ class Issue < ActiveRecord::Base self.priority = nil write_attribute(:priority_id, pid) end - + + def estimated_hours=(h) + write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) + end + def validate if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty? errors.add :due_date, :activerecord_error_not_a_date @@ -153,6 +157,8 @@ class Issue < ActiveRecord::Base # Close duplicates if the issue was closed if @issue_before_change && !@issue_before_change.closed? && self.closed? duplicates.each do |duplicate| + # Reload is need in case the duplicate was updated by a previous duplicate + duplicate.reload # Don't re-close it if it's already closed next if duplicate.closed? # Same user and notes @@ -237,4 +243,8 @@ class Issue < ActiveRecord::Base yield end end + + def to_s + "#{tracker} ##{id}: #{subject}" + end end diff --git a/app/models/journal.rb b/app/models/journal.rb index 7c5e3d3bf..1376d349e 100644 --- a/app/models/journal.rb +++ b/app/models/journal.rb @@ -33,6 +33,7 @@ class Journal < ActiveRecord::Base acts_as_event :title => Proc.new {|o| "#{o.issue.tracker.name} ##{o.issue.id}: #{o.issue.subject}" + ((s = o.new_status) ? " (#{s})" : '') }, :description => :notes, :author => :user, + :type => Proc.new {|o| (s = o.new_status) && s.is_closed? ? 'issue-closed' : 'issue-edit' }, :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}} def save diff --git a/app/models/message.rb b/app/models/message.rb index 12b1cd990..a18d126c9 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -28,6 +28,7 @@ class Message < ActiveRecord::Base :date_column => 'created_on' acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, :description => :content, + :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'}, :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id, :id => o.id}} attr_protected :locked, :sticky diff --git a/app/models/message_observer.rb b/app/models/message_observer.rb index c26805c1b..043988172 100644 --- a/app/models/message_observer.rb +++ b/app/models/message_observer.rb @@ -21,6 +21,8 @@ class MessageObserver < ActiveRecord::Observer recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author && m.author.active?} # send notification to the board watchers recipients += message.board.watcher_recipients + # send notification to project members who want to be notified + recipients += message.board.project.recipients recipients = recipients.compact.uniq Mailer.deliver_message_posted(message, recipients) if !recipients.empty? && Setting.notified_events.include?('message_posted') end diff --git a/app/models/project.rb b/app/models/project.rb index a223b35f0..964469649 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -33,7 +33,7 @@ class Project < ActiveRecord::Base has_many :documents, :dependent => :destroy has_many :news, :dependent => :delete_all, :include => :author has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" - has_many :boards, :order => "position ASC" + has_many :boards, :dependent => :destroy, :order => "position ASC" has_one :repository, :dependent => :destroy has_many :changesets, :through => :repository has_one :wiki, :dependent => :destroy @@ -75,12 +75,14 @@ class Project < ActiveRecord::Base conditions = nil if include_subprojects && !active_children.empty? ids = [id] + active_children.collect {|c| c.id} - conditions = ["#{Issue.table_name}.project_id IN (#{ids.join(',')})"] + conditions = ["#{Project.table_name}.id IN (#{ids.join(',')})"] end - conditions ||= ["#{Issue.table_name}.project_id = ?", id] + conditions ||= ["#{Project.table_name}.id = ?", id] # Quick and dirty fix for Rails 2 compatibility Issue.send(:with_scope, :find => { :conditions => conditions }) do - yield + Version.send(:with_scope, :find => { :conditions => conditions }) do + yield + end end end diff --git a/app/models/query.rb b/app/models/query.rb index 99d13aa6a..641c0d17b 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -116,6 +116,11 @@ class Query < ActiveRecord::Base set_language_if_valid(User.current.language) end + def after_initialize + # Store the fact that project is nil (used in #editable_by?) + @is_for_all = project.nil? + end + def validate filters.each_key do |field| errors.add label_for(field), :activerecord_error_blank unless @@ -128,8 +133,10 @@ class Query < ActiveRecord::Base def editable_by?(user) return false unless user - return true if !is_public && self.user_id == user.id - is_public && user.allowed_to?(:manage_public_queries, project) + # Admin can edit them all and regular users can edit their private queries + return true if user.admin? || (!is_public && self.user_id == user.id) + # Members can not edit public queries that are for all project (only admin is allowed to) + is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project) end def available_filters @@ -139,7 +146,7 @@ class Query < ActiveRecord::Base @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } }, "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } }, - "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } }, + "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI'], :order => 'position').collect{|s| [s.name, s.id.to_s] } }, "subject" => { :type => :text, :order => 8 }, "created_on" => { :type => :date_past, :order => 9 }, "updated_on" => { :type => :date_past, :order => 10 }, @@ -294,7 +301,7 @@ class Query < ActiveRecord::Base # custom field db_table = CustomValue.table_name db_field = 'value' - sql << "#{Issue.table_name}.id IN (SELECT #{db_table}.customized_id FROM #{db_table} where #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} AND " + sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE " else # regular field db_table = Issue.table_name @@ -313,9 +320,9 @@ class Query < ActiveRecord::Base when "!" sql = sql + "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))" when "!*" - sql = sql + "#{db_table}.#{db_field} IS NULL" + sql = sql + "#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} = ''" when "*" - sql = sql + "#{db_table}.#{db_field} IS NOT NULL" + sql = sql + "#{db_table}.#{db_field} IS NOT NULL AND #{db_table}.#{db_field} <> ''" when ">=" sql = sql + "#{db_table}.#{db_field} >= #{v.first.to_i}" when "<=" diff --git a/app/models/repository/cvs.rb b/app/models/repository/cvs.rb index a78b60806..c2d8be977 100644 --- a/app/models/repository/cvs.rb +++ b/app/models/repository/cvs.rb @@ -35,7 +35,8 @@ class Repository::Cvs < Repository end def entries(path=nil, identifier=nil) - entries=scm.entries(path, identifier) + rev = identifier.nil? ? nil : changesets.find_by_revision(identifier) + entries = scm.entries(path, rev.nil? ? nil : rev.committed_on) if entries entries.each() do |entry| unless entry.lastrev.nil? || entry.lastrev.identifier @@ -137,12 +138,18 @@ class Repository::Cvs < Repository end # Renumber new changesets in chronological order - c = changesets.find(:first, :order => 'committed_on DESC, id DESC', :conditions => "revision NOT LIKE '_%'") - next_rev = c.nil? ? 1 : (c.revision.to_i + 1) changesets.find(:all, :order => 'committed_on ASC, id ASC', :conditions => "revision LIKE '_%'").each do |changeset| - changeset.update_attribute :revision, next_rev - next_rev += 1 + changeset.update_attribute :revision, next_revision_number end end # transaction end + + private + + # Returns the next revision number to assign to a CVS changeset + def next_revision_number + # Need to retrieve existing revision numbers to sort them as integers + @current_revision_number ||= (connection.select_values("SELECT revision FROM #{Changeset.table_name} WHERE repository_id = #{id} AND revision NOT LIKE '_%'").collect(&:to_i).max || 0) + @current_revision_number += 1 + end end diff --git a/app/models/repository/darcs.rb b/app/models/repository/darcs.rb index cc608d370..c7c14a397 100644 --- a/app/models/repository/darcs.rb +++ b/app/models/repository/darcs.rb @@ -29,7 +29,8 @@ class Repository::Darcs < Repository end def entries(path=nil, identifier=nil) - entries=scm.entries(path, identifier) + patch = identifier.nil? ? nil : changesets.find_by_revision(identifier) + entries = scm.entries(path, patch.nil? ? nil : patch.scmid) if entries entries.each do |entry| # Search the DB for the entry's last change diff --git a/app/models/repository/mercurial.rb b/app/models/repository/mercurial.rb index 27a8eaea9..18cbc9495 100644 --- a/app/models/repository/mercurial.rb +++ b/app/models/repository/mercurial.rb @@ -34,6 +34,11 @@ class Repository::Mercurial < Repository if entries entries.each do |entry| next unless entry.is_file? + # Set the filesize unless browsing a specific revision + if identifier.nil? + full_path = File.join(root_url, entry.path) + entry.size = File.stat(full_path).size if File.file?(full_path) + end # Search the DB for the entry's last change change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC") if change @@ -53,7 +58,9 @@ class Repository::Mercurial < Repository # latest revision found in database db_revision = latest_changeset ? latest_changeset.revision.to_i : -1 # latest revision in the repository - scm_revision = scm_info.lastrev.identifier.to_i + latest_revision = scm_info.lastrev + return if latest_revision.nil? + scm_revision = latest_revision.identifier.to_i if db_revision < scm_revision logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug? identifier_from = db_revision + 1 diff --git a/app/models/time_entry.rb b/app/models/time_entry.rb index bcf6d1223..ddaff2b60 100644 --- a/app/models/time_entry.rb +++ b/app/models/time_entry.rb @@ -1,5 +1,5 @@ # redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Copyright (C) 2006-2008 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -39,6 +39,10 @@ class TimeEntry < ActiveRecord::Base errors.add :issue_id, :activerecord_error_invalid if (issue_id && !issue) || (issue && project!=issue.project) end + def hours=(h) + write_attribute :hours, (h.is_a?(String) ? h.to_hours : h) + end + # tyear, tmonth, tweek assigned where setting spent_on attributes # these attributes make time aggregations easier def spent_on=(date) diff --git a/app/models/user.rb b/app/models/user.rb index ae81d46d2..a67a08567 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -18,6 +18,9 @@ require "digest/sha1" class User < ActiveRecord::Base + + class OnTheFlyCreationFailure < Exception; end + # Account statuses STATUS_ANONYMOUS = 0 STATUS_ACTIVE = 1 @@ -105,15 +108,17 @@ class User < ActiveRecord::Base onthefly.language = Setting.default_language if onthefly.save user = find(:first, :conditions => ["login=?", login]) - logger.info("User '#{user.login}' created on the fly.") if logger + logger.info("User '#{user.login}' created from the LDAP") if logger + else + logger.error("User '#{onthefly.login}' found in LDAP but could not be created (#{onthefly.errors.full_messages.join(', ')})") if logger + raise OnTheFlyCreationFailure.new end end end user.update_attribute(:last_login_on, Time.now) if user user - - rescue => text - raise text + rescue => text + raise text end # Return user's full name for display @@ -222,17 +227,26 @@ class User < ActiveRecord::Base # action can be: # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') # * a permission Symbol (eg. :edit_project) - def allowed_to?(action, project) - # No action allowed on archived projects - return false unless project.active? - # No action allowed on disabled modules - return false unless project.allows_to?(action) - # Admin users are authorized for anything else - return true if admin? - - role = role_for_project(project) - return false unless role - role.allowed_to?(action) && (project.is_public? || role.member?) + def allowed_to?(action, project, options={}) + if project + # No action allowed on archived projects + return false unless project.active? + # No action allowed on disabled modules + return false unless project.allows_to?(action) + # Admin users are authorized for anything else + return true if admin? + + role = role_for_project(project) + return false unless role + role.allowed_to?(action) && (project.is_public? || role.member?) + + elsif options[:global] + # authorize if user has at least one role that has this permission + roles = memberships.collect {|m| m.role}.uniq + roles.detect {|r| r.allowed_to?(action)} + else + false + end end def self.current=(user) diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 13915c274..724354ad6 100644 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -32,6 +32,7 @@ class WikiContent < ActiveRecord::Base acts_as_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"}, :description => :comments, :datetime => :updated_on, + :type => 'wiki-page', :url => Proc.new {|o| {:controller => 'wiki', :id => o.page.wiki.project_id, :page => o.page.title, :version => o.version}} def text=(plain) diff --git a/app/views/account/register.rhtml b/app/views/account/register.rhtml index c1425a380..7cf4b6da3 100644 --- a/app/views/account/register.rhtml +++ b/app/views/account/register.rhtml @@ -35,10 +35,3 @@ <%= submit_tag l(:button_submit) %> <% end %> - -<% content_for :header_tags do %> -<%= javascript_include_tag 'calendar/calendar' %> -<%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> -<%= javascript_include_tag 'calendar/calendar-setup' %> -<%= stylesheet_link_tag 'calendar' %> -<% end %> diff --git a/app/views/common/_calendar.rhtml b/app/views/common/_calendar.rhtml index 7534a1223..1095cd501 100644 --- a/app/views/common/_calendar.rhtml +++ b/app/views/common/_calendar.rhtml @@ -19,12 +19,15 @@ while day <= calendar.enddt %> elsif day == i.due_date image_tag('arrow_to.png') end %> - <%= h("#{i.project.name} -") unless @project && @project == i.project %> + <%= h("#{i.project} -") unless @project && @project == i.project %> <%= link_to_issue i %>: <%= h(truncate(i.subject, 30)) %> <span class="tip"><%= render_issue_tooltip i %></span> </div> <% else %> - <%= link_to_version i, :class => "icon icon-package" %> + <span class="icon icon-package"> + <%= h("#{i.project} -") unless @project && @project == i.project %> + <%= link_to_version i%> + </span> <% end %> <% end %> </td> diff --git a/app/views/issues/_form.rhtml b/app/views/issues/_form.rhtml index 5034e0a29..9bb74fd34 100644 --- a/app/views/issues/_form.rhtml +++ b/app/views/issues/_form.rhtml @@ -49,10 +49,3 @@ <% end %> <%= wikitoolbar_for 'issue_description' %> - -<% content_for :header_tags do %> - <%= javascript_include_tag 'calendar/calendar' %> - <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> - <%= javascript_include_tag 'calendar/calendar-setup' %> - <%= stylesheet_link_tag 'calendar' %> -<% end %> diff --git a/app/views/issues/_history.rhtml b/app/views/issues/_history.rhtml index 373758874..f29a44daf 100644 --- a/app/views/issues/_history.rhtml +++ b/app/views/issues/_history.rhtml @@ -1,5 +1,5 @@ <% for journal in journals %> - <div id="change-<%= journal.id %>"> + <div id="change-<%= journal.id %>" class="journal"> <h4><div style="float:right;"><%= link_to "##{journal.indice}", :anchor => "note-#{journal.indice}" %></div> <%= content_tag('a', '', :name => "note-#{journal.indice}")%> <%= format_time(journal.created_on) %> - <%= journal.user.name %></h4> diff --git a/app/views/issues/_sidebar.rhtml b/app/views/issues/_sidebar.rhtml index 4a1b7e9bc..e94d4180b 100644 --- a/app/views/issues/_sidebar.rhtml +++ b/app/views/issues/_sidebar.rhtml @@ -1,13 +1,14 @@ <h3><%= l(:label_issue_plural) %></h3> <%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %><br /> +<% if @project %> <%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %><br /> <%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %> +<% end %> +<% unless sidebar_queries.empty? -%> <h3><%= l(:label_query_plural) %></h3> -<% queries = @project.queries.find(:all, - :order => "name ASC", - :conditions => ["is_public = ? or user_id = ?", true, (User.current.logged? ? User.current.id : 0)]) - queries.each do |query| %> +<% sidebar_queries.each do |query| -%> <%= link_to query.name, :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %><br /> -<% end %> +<% end -%> +<% end -%> diff --git a/app/views/issues/bulk_edit.rhtml b/app/views/issues/bulk_edit.rhtml index 31ed7ee56..86bc76765 100644 --- a/app/views/issues/bulk_edit.rhtml +++ b/app/views/issues/bulk_edit.rhtml @@ -47,10 +47,3 @@ <p><%= submit_tag l(:button_submit) %> <% end %> - -<% content_for :header_tags do %> - <%= javascript_include_tag 'calendar/calendar' %> - <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> - <%= javascript_include_tag 'calendar/calendar-setup' %> - <%= stylesheet_link_tag 'calendar' %> -<% end %> diff --git a/app/views/issues/index.rhtml b/app/views/issues/index.rhtml index 094c49ff2..027f3f006 100644 --- a/app/views/issues/index.rhtml +++ b/app/views/issues/index.rhtml @@ -18,7 +18,7 @@ :update => "content", }, :class => 'icon icon-reload' %> - <% if current_role && current_role.allowed_to?(:save_queries) %> + <% if User.current.allowed_to?(:save_queries, @project, :global => true) %> <%= link_to l(:button_save), {}, :onclick => "$('query_form').submit(); return false;", :class => 'icon icon-save' %> <% end %> </p> @@ -54,7 +54,7 @@ <% content_for :sidebar do %> <%= render :partial => 'issues/sidebar' %> -<% end if @project%> +<% end %> <% content_for :header_tags do %> <%= auto_discovery_link_tag(:atom, {:query_id => @query, :format => 'atom', :page => nil, :key => User.current.rss_key}, :title => l(:label_issue_plural)) %> diff --git a/app/views/issues/new.rhtml b/app/views/issues/new.rhtml index 7b9dde899..280e2009b 100644 --- a/app/views/issues/new.rhtml +++ b/app/views/issues/new.rhtml @@ -8,7 +8,7 @@ </div> <%= submit_tag l(:button_create) %> <%= link_to_remote l(:label_preview), - { :url => { :controller => 'issues', :action => 'preview', :project_id => @project, :id => @issue }, + { :url => { :controller => 'issues', :action => 'preview', :project_id => @project }, :method => 'post', :update => 'preview', :with => "Form.serialize('issue-form')", diff --git a/app/views/issues/show.rhtml b/app/views/issues/show.rhtml index 77d9ce640..f788d0ec8 100644 --- a/app/views/issues/show.rhtml +++ b/app/views/issues/show.rhtml @@ -13,7 +13,7 @@ <h3><%=h @issue.subject %></h3> <p class="author"> <%= authoring @issue.created_on, @issue.author %>. - <%= l(:label_updated_time, distance_of_time_in_words(Time.now, @issue.updated_on)) if @issue.created_on != @issue.updated_on %>. + <%= l(:label_updated_time, distance_of_time_in_words(Time.now, @issue.updated_on)) + '.' if @issue.created_on != @issue.updated_on %> </p> <table width="100%"> diff --git a/app/views/messages/show.rhtml b/app/views/messages/show.rhtml index ef7db71ef..251b7c7a5 100644 --- a/app/views/messages/show.rhtml +++ b/app/views/messages/show.rhtml @@ -26,7 +26,7 @@ </div> <div class="message reply"> <h4><%=h message.subject %> - <%= authoring message.created_on, message.author %></h4> - <div class="wiki"><%= textilizable message.content %></div> + <div class="wiki"><%= textilizable message, :content, :attachments => message.attachments %></div> <%= link_to_attachments message.attachments, :no_author => true %> </div> <% end %> diff --git a/app/views/news/show.rhtml b/app/views/news/show.rhtml index 6de8aa86e..a55b56f0b 100644 --- a/app/views/news/show.rhtml +++ b/app/views/news/show.rhtml @@ -25,7 +25,7 @@ <div id="preview" class="wiki"></div> </div> -<p><em><% unless @news.summary.empty? %><%=h @news.summary %><br /><% end %> +<p><em><% unless @news.summary.blank? %><%=h @news.summary %><br /><% end %> <span class="author"><%= authoring @news.created_on, @news.author %></span></em></p> <div class="wiki"> <%= textilizable(@news.description) %> diff --git a/app/views/projects/_form.rhtml b/app/views/projects/_form.rhtml index a810369d4..32e4dcd44 100644 --- a/app/views/projects/_form.rhtml +++ b/app/views/projects/_form.rhtml @@ -46,11 +46,3 @@ </fieldset> <% end %> <!--[eoform:project]--> - - -<% content_for :header_tags do %> -<%= javascript_include_tag 'calendar/calendar' %> -<%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> -<%= javascript_include_tag 'calendar/calendar-setup' %> -<%= stylesheet_link_tag 'calendar' %> -<% end %> diff --git a/app/views/projects/activity.rhtml b/app/views/projects/activity.rhtml index 0cf7a5000..c2f2f9ebd 100644 --- a/app/views/projects/activity.rhtml +++ b/app/views/projects/activity.rhtml @@ -6,7 +6,7 @@ <h3><%= format_activity_day(day) %></h3> <dl> <% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%> - <dt class="<%= e.class.name.downcase %>"><span class="time"><%= format_time(e.event_datetime, false) %></span> + <dt class="<%= e.event_type %>"><span class="time"><%= format_time(e.event_datetime, false) %></span> <%= content_tag('span', h(e.project), :class => 'project') if @project.nil? || @project != e.project %> <%= link_to h(truncate(e.event_title, 100)), e.event_url %></dt> <dd><% unless e.event_description.blank? -%> <span class="description"><%= format_activity_description(e.event_description) %></span><br /> diff --git a/app/views/projects/destroy.rhtml b/app/views/projects/destroy.rhtml index 4531cb845..a1913c115 100644 --- a/app/views/projects/destroy.rhtml +++ b/app/views/projects/destroy.rhtml @@ -1,14 +1,16 @@ <h2><%=l(:label_confirmation)%></h2> -<div class="box"> -<center> -<p><strong><%=h @project_to_destroy.name %></strong><br /> -<%=l(:text_project_destroy_confirmation)%></p> +<div class="warning"> +<p><strong><%=h @project_to_destroy %></strong><br /> +<%=l(:text_project_destroy_confirmation)%> +<% if @project_to_destroy.children.any? %> +<br /><%= l(:text_subprojects_destroy_warning, content_tag('strong', h(@project_to_destroy.children.sort.collect{|p| p.to_s}.join(', ')))) %> +<% end %> +</p> <p> <% form_tag({:controller => 'projects', :action => 'destroy', :id => @project_to_destroy}) do %> - <%= hidden_field_tag "confirm", 1 %> + <label><%= check_box_tag 'confirm', 1 %> <%= l(:general_text_Yes) %></label> <%= submit_tag l(:button_delete) %> <% end %> </p> -</center> -</div>
\ No newline at end of file +</div> diff --git a/app/views/projects/gantt.rhtml b/app/views/projects/gantt.rhtml index 05bd4b9bc..d941d2777 100644 --- a/app/views/projects/gantt.rhtml +++ b/app/views/projects/gantt.rhtml @@ -70,10 +70,13 @@ top = headers_height + 8 @events.each do |i| %> <div style="position: absolute;line-height:1.2em;height:16px;top:<%= top %>px;left:4px;overflow:hidden;"><small> <% if i.is_a? Issue %> - <%= h("#{i.project.name} -") unless @project && @project == i.project %> + <%= h("#{i.project} -") unless @project && @project == i.project %> <%= link_to_issue i %>: <%=h i.subject %> <% else %> - <%= link_to_version i, :class => "icon icon-package" %> + <span class="icon icon-package"> + <%= h("#{i.project} -") unless @project && @project == i.project %> + <%= link_to_version i %> + </span> <% end %> </small></div> <% top = top + 20 @@ -197,7 +200,8 @@ top = headers_height + 10 %> <div style="top:<%= top %>px;left:<%= i_left %>px;width:15px;" class="task milestone"> </div> <div style="top:<%= top %>px;left:<%= i_left + 12 %>px;background:#fff;" class="task"> - <strong><%= i.name %></strong> + <%= h("#{i.project} -") unless @project && @project == i.project %> + <strong><%=h i %></strong> </div> <% end %> <% top = top + 20 diff --git a/app/views/projects/list_files.rhtml b/app/views/projects/list_files.rhtml index ec4a3619b..f385229ae 100644 --- a/app/views/projects/list_files.rhtml +++ b/app/views/projects/list_files.rhtml @@ -9,10 +9,10 @@ <table class="list"> <thead><tr> <th><%=l(:field_version)%></th> - <th><%=l(:field_filename)%></th> - <th><%=l(:label_date)%></th> - <th><%=l(:field_filesize)%></th> - <th><%=l(:label_downloads_abbr)%></th> + <%= sort_header_tag("#{Attachment.table_name}.filename", :caption => l(:field_filename)) %> + <%= sort_header_tag("#{Attachment.table_name}.created_on", :caption => l(:label_date), :default_order => 'desc') %> + <%= sort_header_tag("#{Attachment.table_name}.filesize", :caption => l(:field_filesize), :default_order => 'desc') %> + <%= sort_header_tag("#{Attachment.table_name}.downloads", :caption => l(:label_downloads_abbr), :default_order => 'desc') %> <th>MD5</th> <% if delete_allowed %><th></th><% end %> </tr></thead> diff --git a/app/views/queries/_filters.rhtml b/app/views/queries/_filters.rhtml index 458d7139e..ec9d4fef6 100644 --- a/app/views/queries/_filters.rhtml +++ b/app/views/queries/_filters.rhtml @@ -59,19 +59,19 @@ function toggle_multi_select(field) { <table width="100%"> <tr> <td> -<table style="padding:0;"> +<table> <% query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.each do |filter| %> <% field = filter[0] options = filter[1] %> - <tr <%= 'style="display:none;"' unless query.has_filter?(field) %> id="tr_<%= field %>"> - <td valign="top" style="width:200px;"> + <tr <%= 'style="display:none;"' unless query.has_filter?(field) %> id="tr_<%= field %>" class="filter"> + <td style="width:200px;"> <%= check_box_tag 'fields[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %> <label for="cb_<%= field %>"><%= filter[1][:name] || l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) %></label> </td> - <td valign="top" style="width:150px;"> + <td style="width:150px;"> <%= select_tag "operators[#{field}]", options_for_select(operators_for_select(options[:type]), query.operator_for(field)), :id => "operators_#{field}", :onchange => "toggle_operator('#{field}');", :class => "select-small", :style => "vertical-align: top;" %> </td> - <td valign="top"> + <td> <div id="div_values_<%= field %>" style="display:none;"> <% case options[:type] when :list, :list_optional, :list_status, :list_subprojects %> @@ -93,7 +93,7 @@ function toggle_multi_select(field) { <% end %> </table> </td> -<td align="right" valign="top"> +<td class="add-filter"> <%= l(:label_filter_add) %>: <%= select_tag 'add_filter_select', options_for_select([["",""]] + query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.collect{|field| [ field[1][:name] || l(("field_"+field[0].to_s.gsub(/\_id$/, "")).to_sym), field[0]] unless query.has_filter?(field[0])}.compact), :onchange => "add_filter();", :class => "select-small" %> </td> diff --git a/app/views/queries/_form.rhtml b/app/views/queries/_form.rhtml index 2d4b96fd1..8da264032 100644 --- a/app/views/queries/_form.rhtml +++ b/app/views/queries/_form.rhtml @@ -6,11 +6,16 @@ <p><label for="query_name"><%=l(:field_name)%></label> <%= text_field 'query', 'name', :size => 80 %></p> -<% if current_role.allowed_to?(:manage_public_queries) %> - <p><label for="query_is_public"><%=l(:field_is_public)%></label> - <%= check_box 'query', 'is_public' %></p> +<% if User.current.admin? || (@project && current_role.allowed_to?(:manage_public_queries)) %> +<p><label for="query_is_public"><%=l(:field_is_public)%></label> +<%= check_box 'query', 'is_public', + :onchange => (User.current.admin? ? nil : 'if (this.checked) {$("query_is_for_all").checked = false; $("query_is_for_all").disabled = true;} else {$("query_is_for_all").disabled = false;}') %></p> <% end %> +<p><label for="query_is_for_all"><%=l(:field_is_for_all)%></label> +<%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, + :disabled => (!@query.new_record? && (@query.project.nil? || (@query.is_public? && !User.current.admin?))) %></p> + <p><label for="query_default_columns"><%=l(:label_default_columns)%></label> <%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns', :onclick => 'if (this.checked) {Element.hide("columns")} else {Element.show("columns")}' %></p> diff --git a/app/views/repositories/revision.rhtml b/app/views/repositories/revision.rhtml index 5a7ef1fd5..f1e176669 100644 --- a/app/views/repositories/revision.rhtml +++ b/app/views/repositories/revision.rhtml @@ -49,7 +49,7 @@ <td><div class="square action_<%= change.action %>"></div> <%= change.path %> <%= "(#{change.revision})" unless change.revision.blank? %></td> <td align="right"> <% if change.action == "M" %> -<%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => change.path, :rev => @changeset.revision %> +<%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => without_leading_slash(change.path), :rev => @changeset.revision %> <% end %> </td> </tr> diff --git a/app/views/timelog/_date_range.rhtml b/app/views/timelog/_date_range.rhtml new file mode 100644 index 000000000..ed84b16cf --- /dev/null +++ b/app/views/timelog/_date_range.rhtml @@ -0,0 +1,28 @@ +<fieldset id="filters"><legend><%= l(:label_date_range) %></legend> +<p> +<%= radio_button_tag 'period_type', '1', !@free_period %> +<%= select_tag 'period', options_for_period_select(params[:period]), + :onchange => 'this.form.onsubmit();', + :onfocus => '$("period_type_1").checked = true;' %> +</p> +<p> +<%= radio_button_tag 'period_type', '2', @free_period %> +<span onclick="$('period_type_2').checked = true;"> +<%= l(:label_date_from) %> +<%= text_field_tag 'from', @from, :size => 10 %> <%= calendar_for('from') %> +<%= l(:label_date_to) %> +<%= text_field_tag 'to', @to, :size => 10 %> <%= calendar_for('to') %> +</span> +<%= submit_tag l(:button_apply), :name => nil %> +</p> +</fieldset> + +<div class="tabs"> +<% url_params = @free_period ? { :from => @from, :to => @to } : { :period => params[:period] } %> +<ul> + <li><%= link_to(l(:label_details), url_params.merge({:controller => 'timelog', :action => 'details', :project_id => @project }), + :class => (@controller.action_name == 'details' ? 'selected' : nil)) %></li> + <li><%= link_to(l(:label_report), url_params.merge({:controller => 'timelog', :action => 'report', :project_id => @project}), + :class => (@controller.action_name == 'report' ? 'selected' : nil)) %></li> +</ul> +</div> diff --git a/app/views/timelog/_list.rhtml b/app/views/timelog/_list.rhtml index 67e3c67d5..189f4f5e8 100644 --- a/app/views/timelog/_list.rhtml +++ b/app/views/timelog/_list.rhtml @@ -1,5 +1,6 @@ <table class="list time-entries"> <thead> +<tr> <%= sort_header_tag('spent_on', :caption => l(:label_date), :default_order => 'desc') %> <%= sort_header_tag('user_id', :caption => l(:label_member)) %> <%= sort_header_tag('activity_id', :caption => l(:label_activity)) %> @@ -8,6 +9,7 @@ <th><%= l(:field_comments) %></th> <%= sort_header_tag('hours', :caption => l(:field_hours)) %> <th></th> +</tr> </thead> <tbody> <% entries.each do |entry| -%> @@ -35,5 +37,5 @@ </td> </tr> <% end -%> -</tbdoy> +</tbody> </table> diff --git a/app/views/timelog/_report_criteria.rhtml b/app/views/timelog/_report_criteria.rhtml index b048c874a..94f3d20f9 100644 --- a/app/views/timelog/_report_criteria.rhtml +++ b/app/views/timelog/_report_criteria.rhtml @@ -1,14 +1,16 @@ -<% @hours.collect {|h| h[criterias[level]]}.uniq.each do |value| %> +<% @hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value| %> <% hours_for_value = select_hours(hours, criterias[level], value) -%> <% next if hours_for_value.empty? -%> <tr class="<%= cycle('odd', 'even') %> <%= 'last-level' unless criterias.length > level+1 %>"> <%= '<td></td>' * level %> -<td><%= value.nil? ? l(:label_none) : @available_criterias[criterias[level]][:klass].find_by_id(value) %></td> +<td><%= format_criteria_value(criterias[level], value) %></td> <%= '<td></td>' * (criterias.length - level - 1) -%> + <% total = 0 -%> <% @periods.each do |period| -%> - <% sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s)) %> + <% sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s)); total += sum -%> <td class="hours"><%= html_hours("%.2f" % sum) if sum > 0 %></td> <% end -%> + <td class="hours"><%= html_hours("%.2f" % total) if total > 0 %></td> </tr> <% if criterias.length > level+1 -%> <%= render(:partial => 'report_criteria', :locals => {:criterias => criterias, :hours => hours_for_value, :level => (level + 1)}) %> diff --git a/app/views/timelog/details.rhtml b/app/views/timelog/details.rhtml index cba1597d1..f02da9959 100644 --- a/app/views/timelog/details.rhtml +++ b/app/views/timelog/details.rhtml @@ -1,5 +1,4 @@ <div class="contextual">
-<%= link_to(l(:label_report), {:controller => 'timelog', :action => 'report', :project_id => @project}, :class => 'icon icon-report') %>
<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %>
</div>
@@ -12,23 +11,7 @@ <% form_remote_tag( :url => {}, :method => :get, :update => 'content' ) do %>
<%= hidden_field_tag 'project_id', params[:project_id] %>
<%= hidden_field_tag 'issue_id', params[:issue_id] if @issue %>
-
-<fieldset><legend><%= l(:label_date_range) %></legend>
-<p>
-<%= radio_button_tag 'period_type', '1', !@free_period %>
-<%= select_tag 'period', options_for_period_select(params[:period]),
- :onchange => 'this.form.onsubmit();',
- :onfocus => '$("period_type_1").checked = true;' %>
-</p>
-<p>
-<%= radio_button_tag 'period_type', '2', @free_period %>
-<%= l(:label_date_from) %>
-<%= text_field_tag 'from', @from, :size => 10, :onfocus => '$("period_type_2").checked = true;' %> <%= calendar_for('from') %>
-<%= l(:label_date_to) %>
-<%= text_field_tag 'to', @to, :size => 10, :onfocus => '$("period_type_2").checked = true;' %> <%= calendar_for('to') %>
-<%= submit_tag l(:button_apply), :name => nil, :onclick => '$("period_type_2").checked = true;' %>
-</p>
-</fieldset>
+<%= render :partial => 'date_range' %>
<% end %>
<div class="total-hours">
@@ -45,9 +28,4 @@ </p>
<% end %>
-<% content_for :header_tags do %>
- <%= javascript_include_tag 'calendar/calendar' %>
- <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %>
- <%= javascript_include_tag 'calendar/calendar-setup' %>
- <%= stylesheet_link_tag 'calendar' %>
-<% end %>
+<% html_title l(:label_spent_time), l(:label_details) %>
diff --git a/app/views/timelog/edit.rhtml b/app/views/timelog/edit.rhtml index 13d76f1ef..f9dae8a99 100644 --- a/app/views/timelog/edit.rhtml +++ b/app/views/timelog/edit.rhtml @@ -2,6 +2,7 @@ <% labelled_tabular_form_for :time_entry, @time_entry, :url => {:action => 'edit', :project_id => @time_entry.project} do |f| %>
<%= error_messages_for 'time_entry' %>
+<%= back_url_hidden_field_tag %>
<div class="box">
<p><%= f.text_field :issue_id, :size => 6 %> <em><%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %></em></p>
@@ -14,10 +15,3 @@ <%= submit_tag l(:button_save) %>
<% end %>
-
-<% content_for :header_tags do %>
-<%= javascript_include_tag 'calendar/calendar' %>
-<%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %>
-<%= javascript_include_tag 'calendar/calendar-setup' %>
-<%= stylesheet_link_tag 'calendar' %>
-<% end %>
\ No newline at end of file diff --git a/app/views/timelog/report.rhtml b/app/views/timelog/report.rhtml index 8fc15a3b4..97251bc11 100644 --- a/app/views/timelog/report.rhtml +++ b/app/views/timelog/report.rhtml @@ -1,36 +1,32 @@ <div class="contextual"> -<%= link_to(l(:label_details), {:controller => 'timelog', :action => 'details', :project_id => @project}, :class => 'icon icon-details') %> <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %> </div> <h2><%= l(:label_spent_time) %></h2> -<% form_remote_tag(:url => {:project_id => @project}, :update => 'content') do %> +<% form_remote_tag(:url => {}, :update => 'content') do %> <% @criterias.each do |criteria| %> - <%= hidden_field_tag 'criterias[]', criteria %> + <%= hidden_field_tag 'criterias[]', criteria, :id => nil %> <% end %> - <fieldset><legend><%= l(:label_date_range) %></legend> - <p> - <%= l(:label_date_from) %> - <%= text_field_tag 'date_from', @date_from, :size => 10 %><%= calendar_for('date_from') %> - <%= l(:label_date_to) %> - <%= text_field_tag 'date_to', @date_to, :size => 10 %><%= calendar_for('date_to') %> - <%= l(:label_details) %> - <%= select_tag 'period', options_for_select([[l(:label_year), 'year'], - [l(:label_month), 'month'], - [l(:label_week), 'week']], @columns) %> - - <%= submit_tag l(:button_apply) %> - </p> - </fieldset> + <%= hidden_field_tag 'project_id', params[:project_id] %> + <%= render :partial => 'date_range' %> - <p><%= l(:button_add) %>: <%= select_tag('criterias[]', options_for_select([[]] + (@available_criterias.keys - @criterias).collect{|k| [l(@available_criterias[k][:label]), k]}), + <p><%= l(:label_details) %>: <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'], + [l(:label_month), 'month'], + [l(:label_week), 'week'], + [l(:label_day_plural).titleize, 'day']], @columns), + :onchange => "this.form.onsubmit();" %> + + <%= l(:button_add) %>: <%= select_tag('criterias[]', options_for_select([[]] + (@available_criterias.keys - @criterias).collect{|k| [l(@available_criterias[k][:label]), k]}), :onchange => "this.form.onsubmit();", :style => 'width: 200px', + :id => nil, :disabled => (@criterias.length >= 3)) %> - <%= link_to_remote l(:button_clear), {:url => {:project_id => @project, :date_from => @date_from, :date_to => @date_to, :period => @columns}, :update => 'content'}, - :class => 'icon icon-reload' %></p> - + <%= link_to_remote l(:button_clear), {:url => {:project_id => @project, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @columns}, + :update => 'content' + }, :class => 'icon icon-reload' %></p> +<% end %> + <% unless @criterias.empty? %> <div class="total-hours"> <p><%= l(:label_total) %>: <%= html_hours(lwr(:label_f_hour, @total_hours)) %></p> @@ -41,11 +37,13 @@ <thead> <tr> <% @criterias.each do |criteria| %> - <th width="15%"><%= l(@available_criterias[criteria][:label]) %></th> + <th><%= l(@available_criterias[criteria][:label]) %></th> <% end %> +<% columns_width = (40 / (@periods.length+1)).to_i %> <% @periods.each do |period| %> - <th width="<%= ((100 - @criterias.length * 15 - 15 ) / @periods.length).to_i %>%"><%= period %></th> + <th class="period" width="<%= columns_width %>%"><%= period %></th> <% end %> + <th class="total" width="<%= columns_width %>%"><%= l(:label_total) %></th> </tr> </thead> <tbody> @@ -53,20 +51,22 @@ <tr class="total"> <td><%= l(:label_total) %></td> <%= '<td></td>' * (@criterias.size - 1) %> + <% total = 0 -%> <% @periods.each do |period| -%> - <% sum = sum_hours(select_hours(@hours, @columns, period.to_s)) %> + <% sum = sum_hours(select_hours(@hours, @columns, period.to_s)); total += sum -%> <td class="hours"><%= html_hours("%.2f" % sum) if sum > 0 %></td> <% end -%> + <td class="hours"><%= html_hours("%.2f" % total) if total > 0 %></td> </tr> </tbody> </table> -<% end %> + +<p class="other-formats"> +<%= l(:label_export_to) %> +<span><%= link_to 'CSV', params.merge({:format => 'csv'}), :class => 'csv' %></span> +</p> <% end %> <% end %> -<% content_for :header_tags do %> -<%= javascript_include_tag 'calendar/calendar' %> -<%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> -<%= javascript_include_tag 'calendar/calendar-setup' %> -<%= stylesheet_link_tag 'calendar' %> -<% end %> +<% html_title l(:label_spent_time), l(:label_report) %> + diff --git a/app/views/users/_form.rhtml b/app/views/users/_form.rhtml index d32399c60..ff4278c1f 100644 --- a/app/views/users/_form.rhtml +++ b/app/views/users/_form.rhtml @@ -30,10 +30,3 @@ </div> </div> <!--[eoform:user]--> - -<% content_for :header_tags do %> -<%= javascript_include_tag 'calendar/calendar' %> -<%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> -<%= javascript_include_tag 'calendar/calendar-setup' %> -<%= stylesheet_link_tag 'calendar' %> -<% end %>
\ No newline at end of file diff --git a/app/views/users/list.rhtml b/app/views/users/list.rhtml index e12aa3425..d89672d19 100644 --- a/app/views/users/list.rhtml +++ b/app/views/users/list.rhtml @@ -33,17 +33,7 @@ <td align="center"><%= image_tag('true.png') if user.admin? %></td> <td class="created_on" align="center"><%= format_time(user.created_on) %></td> <td class="last_login_on" align="center"><%= format_time(user.last_login_on) unless user.last_login_on.nil? %></td> - <td> - <small> - <% if user.locked? -%> - <%= link_to l(:button_unlock), {:action => 'edit', :id => user, :user => {:status => User::STATUS_ACTIVE}}, :method => :post, :class => 'icon icon-unlock' %> - <% elsif user.registered? -%> - <%= link_to l(:button_activate), {:action => 'edit', :id => user, :user => {:status => User::STATUS_ACTIVE}}, :method => :post, :class => 'icon icon-unlock' %> - <% else -%> - <%= link_to l(:button_lock), {:action => 'edit', :id => user, :user => {:status => User::STATUS_LOCKED}}, :method => :post, :class => 'icon icon-lock' %> - <% end -%> - </small> - </td> + <td><small><%= change_status_link(user) %></small></td> </tr> <% end -%> </tbody> diff --git a/app/views/versions/_form.rhtml b/app/views/versions/_form.rhtml index e18f912bf..adc83b573 100644 --- a/app/views/versions/_form.rhtml +++ b/app/views/versions/_form.rhtml @@ -6,10 +6,3 @@ <p><%= f.text_field :wiki_page_title, :label => :label_wiki_page, :size => 60, :disabled => @project.wiki.nil? %></p> <p><%= f.text_field :effective_date, :size => 10 %><%= calendar_for('version_effective_date') %></p> </div> - -<% content_for :header_tags do %> -<%= javascript_include_tag 'calendar/calendar' %> -<%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> -<%= javascript_include_tag 'calendar/calendar-setup' %> -<%= stylesheet_link_tag 'calendar' %> -<% end %> diff --git a/config/environment.rb b/config/environment.rb index 2d581168b..7878eca47 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -4,6 +4,9 @@ # you don't control web/app server and can't set it the proper way # ENV['RAILS_ENV'] ||= 'production' +# Specifies gem version of Rails to use when vendor/rails is not present +RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION + # Bootstrap the Rails environment, frameworks, and default configuration require File.join(File.dirname(__FILE__), 'boot') diff --git a/doc/CHANGELOG b/doc/CHANGELOG index 9ced8b4c0..b39185151 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -5,6 +5,38 @@ Copyright (C) 2006-2008 Jean-Philippe Lang http://www.redmine.org/ +== 2008-04-28 v0.7.0 + +* Forces Redmine to use rails 2.0.2 gem when vendor/rails is not present +* Queries can be marked as 'For all projects'. Such queries will be available on all projects and on the global issue list. +* Add predefined date ranges to the time report +* Time report can be done at issue level +* Various timelog report enhancements +* Accept the following formats for "hours" field: 1h, 1 h, 1 hour, 2 hours, 30m, 30min, 1h30, 1h30m, 1:30 +* Display the context menu above and/or to the left of the click if needed +* Make the admin project files list sortable +* Mercurial: display working directory files sizes unless browsing a specific revision +* Preserve status filter and page number when using lock/unlock/activate links on the users list +* Redmine.pm support for LDAP authentication +* Better error message and AR errors in log for failed LDAP on-the-fly user creation +* Redirected user to where he is coming from after logging hours +* Warn user that subprojects are also deleted when deleting a project +* Include subprojects versions on calendar and gantt +* Notify project members when a message is posted if they want to receive notifications +* Fixed: Feed content limit setting has no effect +* Fixed: Priorities not ordered when displayed as a filter in issue list +* Fixed: can not display attached images inline in message replies +* Fixed: Boards are not deleted when project is deleted +* Fixed: trying to preview a new issue raises an exception with postgresql +* Fixed: single file 'View difference' links do not work because of duplicate slashes in url +* Fixed: inline image not displayed when including a wiki page +* Fixed: CVS duplicate key violation +* Fixed: ActiveRecord::StaleObjectError exception on closing a set of circular duplicate issues +* Fixed: custom field filters behaviour +* Fixed: Postgresql 8.3 compatibility +* Fixed: Links to repository directories don't work + + == 2008-03-29 v0.7.0-rc1 * Overall activity view and feed added, link is available on the project list diff --git a/extra/svn/Redmine.pm b/extra/svn/Redmine.pm index b76622e3d..6f3ba4385 100644 --- a/extra/svn/Redmine.pm +++ b/extra/svn/Redmine.pm @@ -8,8 +8,8 @@ against redmine database =head1 SYNOPSIS This module allow anonymous users to browse public project and -registred users to browse and commit their project. authentication is -done on the redmine database. +registred users to browse and commit their project. Authentication is +done against the redmine database or the LDAP configured in redmine. This method is far simpler than the one with pam_* and works with all database without an hassle but you need to have apache/mod_perl on the @@ -29,6 +29,11 @@ On debian/ubuntu you must do : aptitude install libapache-dbi-perl libapache2-mod-perl2 libdbd-mysql-perl +If your Redmine users use LDAP authentication, you will also need +Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used): + + aptitude install libauthen-simple-ldap-perl libio-socket-ssl-perl + =head1 CONFIGURATION ## if the module isn't in your perl path @@ -90,6 +95,8 @@ use strict; use DBI; use Digest::SHA1; +# optional module for LDAP authentication +my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1"); use Apache2::Module; use Apache2::Access; @@ -140,7 +147,7 @@ sub is_public_project { my $dbh = connect_database($r); my $sth = $dbh->prepare( - "SELECT * FROM projects WHERE projects.identifier=? and projects.is_public=true;" + "SELECT * FROM projects WHERE projects.identifier=? and projects.is_public=true;" ); $sth->execute($project_id); @@ -176,17 +183,37 @@ sub is_member { my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass); my $sth = $dbh->prepare( - "SELECT hashed_password FROM members, projects, users WHERE projects.id=members.project_id AND users.id=members.user_id AND users.status=1 AND login=? AND identifier=?;" + "SELECT hashed_password, auth_source_id FROM members, projects, users WHERE projects.id=members.project_id AND users.id=members.user_id AND users.status=1 AND login=? AND identifier=?;" ); $sth->execute($redmine_user, $project_id); my $ret; while (my @row = $sth->fetchrow_array) { - if ($row[0] eq $pass_digest) { - $ret = 1; - last; + unless ($row[1]) { + if ($row[0] eq $pass_digest) { + $ret = 1; + last; + } + } elsif ($CanUseLDAPAuth) { + my $sthldap = $dbh->prepare( + "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;" + ); + $sthldap->execute($row[1]); + while (my @rowldap = $sthldap->fetchrow_array) { + my $ldap = Authen::Simple::LDAP->new( + host => ($rowldap[2] == 1 || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0], + port => $rowldap[1], + basedn => $rowldap[5], + binddn => $rowldap[3] ? $rowldap[3] : "", + bindpw => $rowldap[4] ? $rowldap[4] : "", + filter => "(".$rowldap[6]."=%s)" + ); + $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass)); + } + $sthldap->finish(); } } + $sth->finish(); $dbh->disconnect(); $ret; diff --git a/lang/bg.yml b/lang/bg.yml index a49365d20..b341d989f 100644 --- a/lang/bg.yml +++ b/lang/bg.yml @@ -48,8 +48,8 @@ general_text_no: 'не' general_text_yes: 'да' general_lang_name: 'Bulgarian' general_csv_separator: ',' -general_csv_encoding: cp1251 -general_pdf_encoding: cp1251 +general_csv_encoding: UTF-8 +general_pdf_encoding: UTF-8 general_day_names: Понеделник,Вторник,СрÑда,Четвъртък,Петък,Събота,ÐÐµÐ´ÐµÐ»Ñ general_first_day_of_week: '1' @@ -57,11 +57,11 @@ notice_account_updated: Профилът е обновен уÑпешно. notice_account_invalid_creditentials: Ðевалиден потребител или парола. notice_account_password_updated: Паролата е уÑпешно променена. notice_account_wrong_password: Грешна парола -notice_account_register_done: Ðкаунтът е Ñъздаден уÑпешно. -notice_account_unknown_email: Ðепознат потребител. -notice_can_t_change_password: Този акаунт е Ñ Ð²ÑŠÐ½ÑˆÐµÐ½ метод за оторизациÑ. Ðевъзможна ÑмÑна на паролата. +notice_account_register_done: Профилът е Ñъздаден уÑпешно. +notice_account_unknown_email: Ðепознат e-mail. +notice_can_t_change_password: Този профил е Ñ Ð²ÑŠÐ½ÑˆÐµÐ½ метод за оторизациÑ. Ðевъзможна ÑмÑна на паролата. notice_account_lost_email_sent: Изпратен ви е e-mail Ñ Ð¸Ð½Ñтрукции за избор на нова парола. -notice_account_activated: Ðкаунтът ви е активиран. Вече може да влезете. +notice_account_activated: Профилът ви е активиран. Вече може да влезете в ÑиÑтемата. notice_successful_create: УÑпешно Ñъздаване. notice_successful_update: УÑпешно обновÑване. notice_successful_delete: УÑпешно изтриване. @@ -78,8 +78,8 @@ error_scm_command_failed: "Грешка при опит за комуникацРmail_subject_lost_password: Вашата парола (%s) mail_body_lost_password: 'За да Ñмените паролата Ñи, използвайте ÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð»Ð¸Ð½Ðº:' -mail_subject_register: ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð½Ð° акаунт (%s) -mail_body_register: 'За да активирате акаунта Ñи използвайте ÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð»Ð¸Ð½Ðº:' +mail_subject_register: ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð½Ð° профил (%s) +mail_body_register: 'За да активирате профила Ñи използвайте ÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð»Ð¸Ð½Ðº:' gui_validation_error: 1 грешка gui_validation_error_plural: %d грешки @@ -113,11 +113,11 @@ field_notes: Бележка field_is_closed: Затворена задача field_is_default: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð¾ подразбиране field_tracker: Тракер -field_subject: Тема +field_subject: ОтноÑно field_due_date: Крайна дата field_assigned_to: Възложена на field_priority: Приоритет -field_fixed_version: Target version +field_fixed_version: Планувана верÑÐ¸Ñ field_user: Потребител field_role: Ð Ð¾Ð»Ñ field_homepage: Ðачална Ñтраница @@ -138,7 +138,7 @@ field_version: ВерÑÐ¸Ñ field_type: Тип field_host: ХоÑÑ‚ field_port: Порт -field_account: Ðкаунт +field_account: Профил field_base_dn: Base DN field_attr_login: Login attribute field_attr_firstname: Firstname attribute @@ -163,7 +163,7 @@ field_delay: ОтмеÑтване field_assignable: Възможно е възлагане на задачи за тази Ñ€Ð¾Ð»Ñ field_redirect_existing_links: ПренаÑочване на ÑъщеÑтвуващи линкове field_estimated_hours: ИзчиÑлено време -field_default_value: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð¾ подразбиране +field_default_value: СтойноÑÑ‚ по подразбиране setting_app_title: Заглавие setting_app_subtitle: ОпиÑание @@ -171,15 +171,15 @@ setting_welcome_text: Допълнителен текÑÑ‚ setting_default_language: Език по подразбиране setting_login_required: ИзиÑкване за вход в ÑиÑтемата setting_self_registration: РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¾Ñ‚ потребители -setting_attachment_max_size: МакÑимално голÑм приложен файл +setting_attachment_max_size: МакÑимална големина на прикачен файл setting_issues_export_limit: Лимит за екÑпорт на задачи setting_mail_from: E-mail Ð°Ð´Ñ€ÐµÑ Ð·Ð° емиÑии setting_host_name: ХоÑÑ‚ setting_text_formatting: Форматиране на текÑта setting_wiki_compression: Wiki компреÑиране на иÑториÑта setting_feeds_limit: Лимит на Feeds -setting_autofetch_changesets: Ðвтоматично обработване на commits в хранилището -setting_sys_api_enabled: Разрешаване на WS за управление на хранилището +setting_autofetch_changesets: Ðвтоматично обработване на ревизиите +setting_sys_api_enabled: Разрешаване на WS за управление setting_commit_ref_keywords: ОтбелÑзващи ключови думи setting_commit_fix_keywords: Приключващи ключови думи setting_autologin: Ðвтоматичен вход @@ -231,7 +231,7 @@ label_password_lost: Забравена парола label_home: Ðачало label_my_page: Лична Ñтраница label_my_account: Профил -label_my_projects: Моите проекти +label_my_projects: Проекти, в които учаÑтвам label_administration: ÐдминиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ label_login: Вход label_logout: Изход @@ -375,8 +375,8 @@ label_f_hour_plural: %.2f чаÑа label_time_tracking: ОтделÑне на време label_change_plural: Промени label_statistics: СтатиÑтики -label_commits_per_month: Commits за меÑец -label_commits_per_author: Commits за автор +label_commits_per_month: Ревизии по меÑеци +label_commits_per_author: Ревизии по автор label_view_diff: Виж разликите label_diff_inline: хоризонтално label_diff_side_by_side: вертикално @@ -389,7 +389,7 @@ label_applied_status: Промени ÑтатуÑа на label_loading: Зареждане... label_relation_new: Ðова Ñ€ÐµÐ»Ð°Ñ†Ð¸Ñ label_relation_delete: Изтриване на Ñ€ÐµÐ»Ð°Ñ†Ð¸Ñ -label_relates_to: Свързана ÑÑŠÑ +label_relates_to: Ñвързана ÑÑŠÑ label_duplicates: дублира label_blocks: блокира label_blocked_by: блокирана от @@ -427,10 +427,10 @@ label_updated_time: Обновена преди %s label_jump_to_a_project: Проект... button_login: Вход -button_submit: Приложи +button_submit: Прикачване button_save: Ð—Ð°Ð¿Ð¸Ñ -button_check_all: Маркирай вÑички -button_uncheck_all: ИзчиÑти вÑички +button_check_all: Избор на вÑички +button_uncheck_all: ИзчиÑтване на вÑички button_delete: Изтриване button_create: Създаване button_test: ТеÑÑ‚ @@ -481,9 +481,9 @@ text_length_between: От %d до %d Ñимвола. text_tracker_no_workflow: ÐÑма дефиниран работен Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð·Ð° този тракер text_unallowed_characters: Ðепозволени Ñимволи text_comma_separated: Позволено е изброÑване (Ñ Ñ€Ð°Ð·Ð´ÐµÐ»Ð¸Ñ‚ÐµÐ» запетаÑ). -text_issues_ref_in_commit_messages: ОтбелÑзване и приключване на задачи от commit ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ -text_issue_added: Публикувана е нова задача Ñ Ð½Ð¾Ð¼ÐµÑ€ %s (by %s). -text_issue_updated: Задача %s е обновена (by %s). +text_issues_ref_in_commit_messages: ОтбелÑзване и приключване на задачи от ревизии +text_issue_added: Публикувана е нова задача Ñ Ð½Ð¾Ð¼ÐµÑ€ %s (от %s). +text_issue_updated: Задача %s е обновена (от %s). text_wiki_destroy_confirmation: Сигурни ли Ñте, че иÑкате да изтриете това Wiki и цÑлото му Ñъдържание? text_issue_category_destroy_question: Има задачи (%d) обвързани Ñ Ñ‚Ð°Ð·Ð¸ категориÑ. Какво ще изберете? text_issue_category_destroy_assignments: Премахване на връзките Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñта @@ -515,11 +515,11 @@ enumeration_issue_priorities: Приоритети на задачи enumeration_doc_categories: Категории документи enumeration_activities: ДейноÑти (time tracking) label_file_plural: Файлове -label_changeset_plural: Changesets +label_changeset_plural: Ревизии field_column_names: Колони label_default_columns: По подразбиране setting_issue_list_default_columns: Показвани колони по подразбиране -setting_repositories_encodings: Кодови таблици на хранилищата +setting_repositories_encodings: Кодови таблици notice_no_issue_selected: "ÐÑма избрани задачи." label_bulk_edit_selected_issues: Редактиране на задачи label_no_change_option: (Без промÑна) @@ -536,20 +536,20 @@ label_user_mail_option_none: "Само за наблюдавани или в кРsetting_emails_footer: ПодтекÑÑ‚ за e-mail label_float: Дробно button_copy: Копиране -mail_body_account_information_external: Можете да използвате Ð²Ð°ÑˆÐ¸Ñ "%s" акаунт за вход. -mail_body_account_information: ИнформациÑта за акаунта +mail_body_account_information_external: Можете да използвате Ð²Ð°ÑˆÐ¸Ñ "%s" профил за вход. +mail_body_account_information: ИнформациÑта за профила ви setting_protocol: Протокол label_user_mail_no_self_notified: "Ðе иÑкам извеÑÑ‚Ð¸Ñ Ð·Ð° извършени от мен промени" setting_time_format: Формат на чаÑа -label_registration_activation_by_email: активиране на акаунта по email -mail_subject_account_activation_request: ЗаÑвка за активиране на акаунт в %s +label_registration_activation_by_email: активиране на профила по email +mail_subject_account_activation_request: ЗаÑвка за активиране на профил в %s mail_body_account_activation_request: 'Има новорегиÑтриран потребител (%s), очакващ вашето одобрение:' label_registration_automatic_activation: автоматично активиране label_registration_manual_activation: ръчно активиране -notice_account_pending: "Ðкаунтът Ви е Ñъздаден и очаква одобрение от админиÑтратор." +notice_account_pending: "Профилът Ви е Ñъздаден и очаква одобрение от админиÑтратор." field_time_zone: ЧаÑова зона text_caracters_minimum: Минимум %d Ñимвола. -setting_bcc_recipients: Blind carbon copy (bcc) получатели +setting_bcc_recipients: Получатели на Ñкрито копие (bcc) button_annotate: ÐÐ½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ label_issues_by: Задачи по %s field_searchable: С възможноÑÑ‚ за Ñ‚ÑŠÑ€Ñене @@ -566,54 +566,55 @@ label_general: ОÑновни label_repository_plural: Хранилища label_associated_revisions: ÐÑоциирани ревизии setting_user_format: ПотребителÑки формат -text_status_changed_by_changeset: Applied in changeset %s. -label_more: More -text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?' -label_scm: SCM -text_select_project_modules: 'Select modules to enable for this project:' -label_issue_added: Issue added -label_issue_updated: Issue updated -label_document_added: Document added -label_message_posted: Message added -label_file_added: File added -label_news_added: News added -project_module_boards: Boards -project_module_issue_tracking: Issue tracking +text_status_changed_by_changeset: Приложено Ñ Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ %s. +label_more: Още +text_issues_destroy_confirmation: 'Сигурни ли Ñте, че иÑкате да изтриете избраните задачи?' +label_scm: SCM (СиÑтема за контрол на кода) +text_select_project_modules: 'Изберете активните модули за този проект:' +label_issue_added: Добавена задача +label_issue_updated: Обновена задача +label_document_added: Добавен документ +label_message_posted: Добавено Ñъобщение +label_file_added: Добавен файл +label_news_added: Добавена новина +project_module_boards: Форуми +project_module_issue_tracking: Тракинг project_module_wiki: Wiki -project_module_files: Files -project_module_documents: Documents -project_module_repository: Repository -project_module_news: News -project_module_time_tracking: Time tracking -text_file_repository_writable: File repository writable -text_default_administrator_account_changed: Default administrator account changed -text_rmagick_available: RMagick available (optional) -button_configure: Configure -label_plugins: Plugins -label_ldap_authentication: LDAP authentication +project_module_files: Файлове +project_module_documents: Документи +project_module_repository: Хранилище +project_module_news: Ðовини +project_module_time_tracking: ОтделÑне на време +text_file_repository_writable: ВъзможноÑÑ‚ за пиÑане в хранилището Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ðµ +text_default_administrator_account_changed: Сменен Ñ„Ð°Ð±Ñ€Ð¸Ñ‡Ð½Ð¸Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸ÑтраторÑки профил +text_rmagick_available: Ðаличен RMagick (по избор) +button_configure: Конфигуриране +label_plugins: Плъгини +label_ldap_authentication: LDAP Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ label_downloads_abbr: D/L -label_this_month: this month -label_last_n_days: last %d days -label_all_time: all time -label_this_year: this year -label_date_range: Date range -label_last_week: last week -label_yesterday: yesterday -label_last_month: last month -label_add_another_file: Add another file -label_optional_description: Optional description -text_destroy_time_entries_question: %.02f hours were reported on the issues you are about to delete. What do you want to do ? -error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' -text_assign_time_entries_to_project: Assign reported hours to the project -text_destroy_time_entries: Delete reported hours -text_reassign_time_entries: 'Reassign reported hours to this issue:' -setting_activity_days_default: Days displayed on project activity -label_chronological_order: In chronological order -field_comments_sorting: Display comments -label_reverse_chronological_order: In reverse chronological order -label_preferences: Preferences -setting_display_subprojects_issues: Display subprojects issues on main projects by default -label_overall_activity: Overall activity -setting_default_projects_public: New projects are public by default -error_scm_annotate: "The entry does not exist or can not be annotated." -label_planning: Planning +label_this_month: Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ Ð¼ÐµÑец +label_last_n_days: поÑледните %d дни +label_all_time: вÑички +label_this_year: текущата година +label_date_range: Период +label_last_week: поÑледната Ñедмица +label_yesterday: вчера +label_last_month: поÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð¼ÐµÑец +label_add_another_file: ДобавÑне на друг файл +label_optional_description: Ðезадължително опиÑание +text_destroy_time_entries_question: %.02f чаÑа Ñа отделени на задачите, които иÑкате да изтриете. Какво избирате? +error_issue_not_found_in_project: 'Задачата не е намерена или не принадлежи на този проект' +text_assign_time_entries_to_project: ПрехвърлÑне на отделеното време към проект +text_destroy_time_entries: Изтриване на отделеното време +text_reassign_time_entries: 'ПрехвърлÑне на отделеното време към задача:' +setting_activity_days_default: Брой дни показвани на таб ДейноÑÑ‚ +label_chronological_order: Хронологичен ред +field_comments_sorting: Сортиране на коментарите +label_reverse_chronological_order: Обратен хронологичен ред +label_preferences: ÐŸÑ€ÐµÐ´Ð¿Ð¾Ñ‡Ð¸Ñ‚Ð°Ð½Ð¸Ñ +setting_display_subprojects_issues: Показване на подпроектите в проектите по подразбиране +label_overall_activity: ЦÑлоÑтна дейноÑÑ‚ +setting_default_projects_public: Ðовите проекти Ñа публични по подразбиране +error_scm_annotate: "Обектът не ÑъщеÑтвува или не може да бъде анотиран." +label_planning: Планиране +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/cs.yml b/lang/cs.yml index 03702eaa2..250c602c2 100644 --- a/lang/cs.yml +++ b/lang/cs.yml @@ -10,16 +10,16 @@ actionview_datehelper_select_month_prefix: actionview_datehelper_select_year_prefix: actionview_datehelper_time_in_words_day: 1 den actionview_datehelper_time_in_words_day_plural: %d dny -actionview_datehelper_time_in_words_hour_about: asi hodinu -actionview_datehelper_time_in_words_hour_about_plural: asi %d hodin -actionview_datehelper_time_in_words_hour_about_single: asi hodinu -actionview_datehelper_time_in_words_minute: 1 minuta -actionview_datehelper_time_in_words_minute_half: půl minuty -actionview_datehelper_time_in_words_minute_less_than: ménÄ› než minutu -actionview_datehelper_time_in_words_minute_plural: %d minut -actionview_datehelper_time_in_words_minute_single: 1 minuta -actionview_datehelper_time_in_words_second_less_than: ménÄ› než sekunda -actionview_datehelper_time_in_words_second_less_than_plural: ménÄ› než %d sekund +actionview_datehelper_time_in_words_hour_about: asi hodinou +actionview_datehelper_time_in_words_hour_about_plural: asi %d hodinami +actionview_datehelper_time_in_words_hour_about_single: asi hodinou +actionview_datehelper_time_in_words_minute: 1 minutou +actionview_datehelper_time_in_words_minute_half: půl minutou +actionview_datehelper_time_in_words_minute_less_than: ménÄ› než minutou +actionview_datehelper_time_in_words_minute_plural: %d minutami +actionview_datehelper_time_in_words_minute_single: 1 minutou +actionview_datehelper_time_in_words_second_less_than: ménÄ› než sekundou +actionview_datehelper_time_in_words_second_less_than_plural: ménÄ› než %d sekundami actionview_instancetag_blank_option: ProsÃm vyberte activerecord_error_inclusion: nenà zahrnuto v seznamu @@ -98,7 +98,7 @@ mail_body_account_activation_request: Byl zaregistrován nový uživatel "%s". A gui_validation_error: 1 chyba gui_validation_error_plural: %d chyb(y) -field_name: Jméno +field_name: Název field_description: Popis field_summary: PÅ™ehled field_is_required: Povinné pole @@ -306,7 +306,7 @@ label_attribute: Atribut label_attribute_plural: Atributy label_download: %d Download label_download_plural: %d Downloads -label_no_data: Žádná data k zobrazenà +label_no_data: Žádné položky label_change_status: ZmÄ›nit stav label_history: Historie label_attachment: Soubor @@ -480,8 +480,8 @@ label_sort_by: SeÅ™adit podle %s label_send_test_email: Poslat testovacà email label_feeds_access_key_created_on: PÅ™Ãstupový klÃÄ pro RSS byl vytvoÅ™en pÅ™ed %s label_module_plural: Moduly -label_added_time_by: 'PÅ™idáno pÅ™ed: %s %s' -label_updated_time: 'Aktualizováno pÅ™ed: %s' +label_added_time_by: 'PÅ™idáno uživatelem %s pÅ™ed %s' +label_updated_time: 'Aktualizováno pÅ™ed %s' label_jump_to_a_project: Zvolit projekt... label_file_plural: Soubory label_changeset_plural: Changesety @@ -586,7 +586,7 @@ text_no_configuration_data: "Role, fronty, stavy úkolů ani workflow nebyly zat text_load_default_configuration: Nahrát výchozà konfiguraci text_status_changed_by_changeset: Použito v changesetu %s. text_issues_destroy_confirmation: 'Opravdu si pÅ™ejete odstranit vÅ¡echny zvolené úkoly?' -text_select_project_modules: 'Zvolte moduly aktivnà v tomto projektu:' +text_select_project_modules: 'Aktivnà moduly v tomto projektu:' text_default_administrator_account_changed: Výchozà nastavenà administrátorského úÄtu zmÄ›nÄ›no text_file_repository_writable: Povolen zápis do repository text_rmagick_available: RMagick k dispozici (volitelné) @@ -620,5 +620,6 @@ default_activity_development: Vývoj enumeration_issue_priorities: Priority úkolů enumeration_doc_categories: Kategorie dokumentů enumeration_activities: Aktivity (sledovánà Äasu) -error_scm_annotate: "The entry does not exist or can not be annotated." -label_planning: Planning +error_scm_annotate: "Položka neexistuje nebo nemůže být komentována." +label_planning: Plánovánà +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/da.yml b/lang/da.yml index b0a6764bb..ff2ed982d 100644 --- a/lang/da.yml +++ b/lang/da.yml @@ -619,3 +619,4 @@ label_overall_activity: Overordnet aktivitet setting_default_projects_public: Nye projekter er offentlige som default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planlægning +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/de.yml b/lang/de.yml index 17c6ac617..77184cf88 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -71,7 +71,7 @@ notice_locking_conflict: Datum wurde von einem anderen Benutzer geändert. notice_not_authorized: Sie sind nicht berechtigt, auf diese Seite zuzugreifen. notice_email_sent: Eine E-Mail wurde an %s gesendet. notice_email_error: Beim Senden einer E-Mail ist ein Fehler aufgetreten (%s). -notice_feeds_access_key_reseted: Ihr RSS-Zugriffsschlüssel wurde zurückgesetzt. +notice_feeds_access_key_reseted: Ihr Atom-Zugriffsschlüssel wurde zurückgesetzt. notice_failed_to_save_issues: "%d von %d ausgewählten Tickets konnte(n) nicht gespeichert werden: %s." notice_no_issue_selected: "Kein Ticket ausgewählt! Bitte wählen Sie die Tickets, die Sie bearbeiten möchten." notice_account_pending: "Ihr Konto wurde erstellt und wartet jetzt auf die Genehmigung des Administrators." @@ -80,6 +80,7 @@ notice_default_data_loaded: Die Standard-Konfiguration wurde erfolgreich geladen error_can_t_load_default_data: "Die Standard-Konfiguration konnte nicht geladen werden: %s" error_scm_not_found: Eintrag und/oder Revision besteht nicht im Projektarchiv. error_scm_command_failed: "Beim Zugriff auf das Projektarchiv ist ein Fehler aufgetreten: %s" +error_scm_annotate: "Der Eintrag existiert nicht oder kann nicht annotiert werden." error_issue_not_found_in_project: 'Das Ticket wurde nicht gefunden oder gehört nicht zu diesem Projekt.' mail_subject_lost_password: Ihr %s Kennwort @@ -120,8 +121,8 @@ field_project: Projekt field_issue: Ticket field_status: Status field_notes: Kommentare -field_is_closed: Problem erledigt -field_is_default: Default +field_is_closed: Ticket geschlossen +field_is_default: Standardeinstellung field_tracker: Tracker field_subject: Thema field_due_date: Abgabedatum @@ -133,8 +134,8 @@ field_role: Rolle field_homepage: Projekt-Homepage field_is_public: Öffentlich field_parent: Unterprojekt von -field_is_in_chlog: Ansicht im Change-Log -field_is_in_roadmap: Ansicht in der Roadmap +field_is_in_chlog: Im Change-Log anzeigen +field_is_in_roadmap: In der Roadmap anzeigen field_login: Mitgliedsname field_mail_notification: Mailbenachrichtigung field_admin: Administrator @@ -177,6 +178,7 @@ field_column_names: Spalten field_time_zone: Zeitzone field_searchable: Durchsuchbar field_default_value: Standardwert +field_comments_sorting: Kommentare anzeigen setting_app_title: Applikations-Titel setting_app_subtitle: Applikations-Untertitel @@ -191,7 +193,8 @@ setting_bcc_recipients: E-Mails als Blindkopie (BCC) senden setting_host_name: Hostname setting_text_formatting: Textformatierung setting_wiki_compression: Wiki-Historie komprimieren -setting_feeds_limit: Feed-Inhalt begrenzen +setting_feeds_limit: Max. Anzahl Einträge pro Atom-Feed +setting_default_projects_public: Neue Projekte sind standardmäßig öffentlich setting_autofetch_changesets: Changesets automatisch abrufen setting_sys_api_enabled: Webservice zur Verwaltung der Projektarchive benutzen setting_commit_ref_keywords: Schlüsselwörter (Beziehungen) @@ -206,6 +209,8 @@ setting_emails_footer: E-Mail-Fußzeile setting_protocol: Protokoll setting_per_page_options: Objekte pro Seite setting_user_format: Benutzer-Anzeigeformat +setting_activity_days_default: Anzahl Tage pro Seite der Projekt-Aktivität +setting_display_subprojects_issues: Tickets von Unterprojekten im Hauptprojekt anzeigen project_module_issue_tracking: Ticket-Verfolgung project_module_time_tracking: Zeiterfassung @@ -227,7 +232,7 @@ label_project_latest: Neueste Projekte label_issue: Ticket label_issue_new: Neues Ticket label_issue_plural: Tickets -label_issue_view_all: Alle Tickets ansehen +label_issue_view_all: Alle Tickets anzeigen label_issues_by: Tickets von %s label_issue_added: Ticket hinzugefügt label_issue_updated: Ticket aktualisiert @@ -277,6 +282,7 @@ label_last_updates: zuletzt aktualisiert label_last_updates_plural: %d zuletzt aktualisierten label_registered_on: Angemeldet am label_activity: Aktivität +label_overall_activity: Aktivität aller Projekte anzeigen label_new: Neu label_logged_as: Angemeldet als label_environment: Environment @@ -320,7 +326,7 @@ label_version: Version label_version_new: Neue Version label_version_plural: Versionen label_confirmation: Bestätigung -label_export_to: Export zu +label_export_to: "Auch abrufbar als:" label_read: Lesen... label_public_projects: Öffentliche Projekte label_open_issues: offen @@ -345,7 +351,7 @@ label_months_from: Monate ab label_gantt: Gantt label_internal: Intern label_last_changes: %d letzte Änderungen -label_change_view_all: Alle Änderungen ansehen +label_change_view_all: Alle Änderungen anzeigen label_personalize_page: Diese Seite anpassen label_comment: Kommentar label_comment_plural: Kommentare @@ -469,7 +475,7 @@ label_date_to: Bis label_language_based: Sprachabhängig label_sort_by: Sortiert nach %s label_send_test_email: Test-E-Mail senden -label_feeds_access_key_created_on: RSS-Zugriffsschlüssel vor %s erstellt +label_feeds_access_key_created_on: Atom-Zugriffsschlüssel vor %s erstellt label_module_plural: Module label_added_time_by: Von %s vor %s hinzugefügt label_updated_time: Vor %s aktualisiert @@ -500,6 +506,10 @@ label_ldap_authentication: LDAP-Authentifizierung label_downloads_abbr: D/L label_optional_description: Beschreibung (optional) label_add_another_file: Eine weitere Datei hinzufügen +label_preferences: Präferenzen +label_chronological_order: in zeitlicher Reihenfolge +label_reverse_chronological_order: in umgekehrter zeitlicher Reihenfolge +label_planning: Terminplanung button_login: Anmelden button_submit: OK @@ -518,7 +528,7 @@ button_lock: Sperren button_unlock: Entsperren button_download: Download button_list: Liste -button_view: Ansehen +button_view: Anzeigen button_move: Verschieben button_back: Zurück button_cancel: Abbrechen @@ -535,7 +545,7 @@ button_reset: Zurücksetzen button_rename: Umbenennen button_change_password: Kennwort ändern button_copy: Kopieren -button_annotate: Mit Anmerkungen versehen +button_annotate: Annotieren button_update: Aktualisieren button_configure: Konfigurieren @@ -608,13 +618,4 @@ default_activity_development: Entwicklung enumeration_issue_priorities: Ticket-Prioritäten enumeration_doc_categories: Dokumentenkategorien enumeration_activities: Aktivitäten (Zeiterfassung) -setting_activity_days_default: Days displayed on project activity -label_chronological_order: In chronological order -field_comments_sorting: Display comments -label_reverse_chronological_order: In reverse chronological order -label_preferences: Preferences -setting_display_subprojects_issues: Display subprojects issues on main projects by default -label_overall_activity: Overall activity -setting_default_projects_public: New projects are public by default -error_scm_annotate: "The entry does not exist or can not be annotated." -label_planning: Planning +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/en.yml b/lang/en.yml index 8264ba908..e39aec301 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -557,6 +557,7 @@ text_select_mail_notifications: Select actions for which email notifications sho text_regexp_info: eg. ^[A-Z0-9]+$ text_min_max_length_info: 0 means no restriction text_project_destroy_confirmation: Are you sure you want to delete this project and related data ? +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' text_workflow_edit: Select a role and a tracker to edit the workflow text_are_you_sure: Are you sure ? text_journal_changed: changed from %s to %s diff --git a/lang/es.yml b/lang/es.yml index 5d5492833..c6eef021a 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -333,8 +333,8 @@ label_revision_plural: Revisiones label_added: añadido label_modified: modificado label_deleted: suprimido -label_latest_revision: La revisión más actual -label_latest_revision_plural: Las revisiones más actuales +label_latest_revision: Última revisión +label_latest_revision_plural: Últimas revisiones label_view_revisions: Ver las revisiones label_max_size: Tamaño máximo label_on: de @@ -372,7 +372,7 @@ label_view_diff: Ver diferencias label_diff_inline: en lÃnea label_diff_side_by_side: cara a cara label_options: Opciones -label_copy_workflow_from: Copiar workflow desde +label_copy_workflow_from: Copiar flujo de trabajo desde label_permissions_report: Informe de permisos label_watched_issues: Peticiones monitorizadas label_related_issues: Peticiones relacionadas @@ -432,7 +432,7 @@ button_move: Mover button_back: Atrás button_cancel: Cancelar button_activate: Activar -button_sort: Clasificar +button_sort: Ordenar button_log_time: Tiempo dedicado button_rollback: Volver a esta versión button_watch: Monitorizar @@ -448,7 +448,7 @@ status_locked: bloqueado text_select_mail_notifications: Seleccionar los eventos a notificar text_regexp_info: eg. ^[A-Z0-9]+$ text_min_max_length_info: 0 para ninguna restricción -text_project_destroy_confirmation: ¿ Estás seguro de querer eliminar el proyecto ? +text_project_destroy_confirmation: ¿Estás seguro de querer eliminar el proyecto? text_workflow_edit: Seleccionar un flujo de trabajo para actualizar text_are_you_sure: ¿ Estás seguro ? text_journal_changed: cambiado de %s a %s @@ -460,7 +460,7 @@ text_tip_task_begin_end_day: tarea que comienza y termina este dÃa text_project_identifier_info: 'Letras minúsculas (a-z), números y signos de puntuación permitidos.<br />Una vez guardado, el identificador no puede modificarse.' text_caracters_maximum: %d carácteres como máximo. text_length_between: Longitud entre %d y %d carácteres. -text_tracker_no_workflow: No hay ningún workflow definido para este tracker +text_tracker_no_workflow: No hay ningún flujo de trabajo definido para este tracker text_unallowed_characters: Carácteres no permitidos text_comma_separated: Múltiples valores permitidos (separados por coma). text_issues_ref_in_commit_messages: Referencia y petición de corrección en los mensajes @@ -559,64 +559,65 @@ field_searchable: Incluir en las búsquedas label_display_per_page: 'Por página: %s' setting_per_page_options: Objetos por página label_age: Edad -notice_default_data_loaded: Default configuration successfully loaded. -text_load_default_configuration: Load the default configuration -text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." -error_can_t_load_default_data: "Default configuration could not be loaded: %s" -button_update: Update -label_change_properties: Change properties +notice_default_data_loaded: Configuración por defecto cargada correctamente. +text_load_default_configuration: Cargar la configuración por defecto +text_no_configuration_data: "TodavÃa no se han configurado roles, ni trackers, ni estados y flujo de trabajo asociado a peticiones. Se recomiendo encarecidamente cargar la configuración por defecto. Una vez cargada, podrá modificarla." +error_can_t_load_default_data: "No se ha podido cargar la configuración por defecto: %s" +button_update: Actualizar +label_change_properties: Cambiar propiedades label_general: General -label_repository_plural: Repositories -label_associated_revisions: Associated revisions -setting_user_format: Users display format -text_status_changed_by_changeset: Applied in changeset %s. -label_more: More -text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?' +label_repository_plural: Repositorios +label_associated_revisions: Revisiones asociadas +setting_user_format: Formato de nombre de usuario +text_status_changed_by_changeset: Aplicado en los cambios %s +label_more: Más +text_issues_destroy_confirmation: '¿Seguro que quiere borrar las peticiones seleccionadas?' label_scm: SCM -text_select_project_modules: 'Select modules to enable for this project:' -label_issue_added: Issue added -label_issue_updated: Issue updated -label_document_added: Document added -label_message_posted: Message added -label_file_added: File added -label_news_added: News added -project_module_boards: Boards -project_module_issue_tracking: Issue tracking +text_select_project_modules: 'Seleccione los módulos a activar para este proyecto:' +label_issue_added: Petición añadida +label_issue_updated: Petición actualizada +label_document_added: Documento añadido +label_message_posted: Mensaje añadido +label_file_added: Fichero añadido +label_news_added: Noticia añadida +project_module_boards: Foros +project_module_issue_tracking: Peticiones project_module_wiki: Wiki -project_module_files: Files -project_module_documents: Documents -project_module_repository: Repository -project_module_news: News -project_module_time_tracking: Time tracking -text_file_repository_writable: File repository writable -text_default_administrator_account_changed: Default administrator account changed -text_rmagick_available: RMagick available (optional) -button_configure: Configure +project_module_files: Ficheros +project_module_documents: Documentos +project_module_repository: Repositorio +project_module_news: Noticias +project_module_time_tracking: Control de tiempo +text_file_repository_writable: Se puede escribir en el repositorio +text_default_administrator_account_changed: Cuenta de administrador por defecto modificada +text_rmagick_available: RMagick disponible (opcional) +button_configure: Configurar label_plugins: Plugins -label_ldap_authentication: LDAP authentication +label_ldap_authentication: Autenticación LDAP label_downloads_abbr: D/L -label_this_month: this month -label_last_n_days: last %d days -label_all_time: all time -label_this_year: this year -label_date_range: Date range -label_last_week: last week -label_yesterday: yesterday -label_last_month: last month -label_add_another_file: Add another file -label_optional_description: Optional description -text_destroy_time_entries_question: %.02f hours were reported on the issues you are about to delete. What do you want to do ? -error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' -text_assign_time_entries_to_project: Assign reported hours to the project -text_destroy_time_entries: Delete reported hours -text_reassign_time_entries: 'Reassign reported hours to this issue:' -setting_activity_days_default: Days displayed on project activity -label_chronological_order: In chronological order -field_comments_sorting: Display comments -label_reverse_chronological_order: In reverse chronological order -label_preferences: Preferences -setting_display_subprojects_issues: Display subprojects issues on main projects by default -label_overall_activity: Overall activity -setting_default_projects_public: New projects are public by default -error_scm_annotate: "The entry does not exist or can not be annotated." -label_planning: Planning +label_this_month: este mes +label_last_n_days: últimos %d dÃas +label_all_time: todo el tiempo +label_this_year: este año +label_date_range: Rango de fechas +label_last_week: última semana +label_yesterday: ayer +label_last_month: último mes +label_add_another_file: Añadir otro fichero +label_optional_description: Descripción opcional +text_destroy_time_entries_question: Existen %.02f horas asignadas a la petición que quiere borrar. ¿Qué quiere hacer ? +error_issue_not_found_in_project: 'La petición no se encuentra o no está asociada a este proyecto' +text_assign_time_entries_to_project: Asignar las horas al proyecto +text_destroy_time_entries: Borrar las horas +text_reassign_time_entries: 'Reasignar las horas a esta petición:' +setting_activity_days_default: DÃas a mostrar en la actividad de proyecto +label_chronological_order: En orden cronológico +field_comments_sorting: Mostrar comentarios +label_reverse_chronological_order: En orden cronológico inverso +label_preferences: Preferencias +setting_display_subprojects_issues: Mostrar peticiones de un subproyecto en el proyecto padre por defecto +label_overall_activity: Actividad global +setting_default_projects_public: Los proyectos nuevos son públicos por defecto +error_scm_annotate: "No existe la entrada o no ha podido ser anotada" +label_planning: Planificación +text_subprojects_destroy_warning: 'Sus subprojectos: %s también se eliminarán' diff --git a/lang/fi.yml b/lang/fi.yml index f345be139..68b6c20d7 100644 --- a/lang/fi.yml +++ b/lang/fi.yml @@ -614,6 +614,7 @@ field_comments_sorting: Näytä kommentit label_reverse_chronological_order: Käänteisessä aikajärjestyksessä label_preferences: Asetukset setting_default_projects_public: Uudet projektit ovat oletuksena julkisia -label_overall_activity: Kokonaisaktiviteetti -error_scm_annotate: "The entry does not exist or can not be annotated." -label_planning: Planning +label_overall_activity: Kokonaishistoria +error_scm_annotate: "Merkintää ei ole tai siihen ei voi lisätä selityksiä." +label_planning: Suunnittelu +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/fr.yml b/lang/fr.yml index 602b3c8e1..cbdda4f3d 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -556,7 +556,8 @@ status_locked: vérouillé text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyée text_regexp_info: ex. ^[A-Z0-9]+$ text_min_max_length_info: 0 pour aucune restriction -text_project_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce projet et tout ce qui lui est rattaché ? +text_project_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce projet et toutes ses données ? +text_subprojects_destroy_warning: 'Ses sous-projets: %s seront également supprimés.' text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow text_are_you_sure: Etes-vous sûr ? text_journal_changed: changé de %s à %s diff --git a/lang/he.yml b/lang/he.yml index 018281581..a611c8c39 100644 --- a/lang/he.yml +++ b/lang/he.yml @@ -76,7 +76,7 @@ notice_failed_to_save_issues: "× ×›×©×¨×ª בשמירת %d × ×•×©×\×™× ×‘ %d × notice_no_issue_selected: "×œ× × ×‘×—×¨ ××£ × ×•×©×! בחר בבקשה ×ת ×”× ×•×©××™× ×©×‘×¨×¦×•× ×š לערוך." error_scm_not_found: ×›× ×™×¡×” ו\×ו ×’×™×¨×¡× ××™× × ×§×™×™×ž×™× ×‘×ž×גר. -error_scm_command_failed: "An error occurred when trying to access the repository: %s" +error_scm_command_failed: "×רעה שגי××” בעת × ×™×¡×•×Ÿ גישה למ×גר: %s" mail_subject_lost_password: סיסמת ×”-%s שלך mail_body_lost_password: '×œ×©×™× ×• סיסמת ×”-Redmine שלך,לחץ על הקישור הב×:' @@ -98,7 +98,7 @@ field_filesize: גודל field_downloads: הורדות field_author: כותב field_created_on: × ×•×¦×¨ -field_updated_on: עודגן +field_updated_on: עודכן field_field_format: פורמט field_is_for_all: לכל ×”×¤×¨×•×™×§×˜×™× field_possible_values: ×¢×¨×›×™× ××¤×©×¨×™×™× @@ -119,7 +119,7 @@ field_subject: ×©× × ×•×©× field_due_date: ת×ריך ×¡×™×•× field_assigned_to: מוצב ל field_priority: עדיפות -field_fixed_version: Target version +field_fixed_version: גירס×ת יעד field_user: מתשמש field_role: תפקיד field_homepage: דף הבית @@ -140,7 +140,7 @@ field_version: ×’×™×¨×¡× field_type: סוג field_host: שרת field_port: פורט -field_account: ×—×©×‘×•× +field_account: חשבון field_base_dn: בסיס DN field_attr_login: ×ª×›×•× ×ª התחברות field_attr_firstname: ×ª×›×•× ×ª ×©× ×¤×¨×˜×™× @@ -182,7 +182,7 @@ setting_text_formatting: עיצוב טקסט setting_wiki_compression: כיווץ היסטורית WIKI setting_feeds_limit: גבול תוכן ×”×–× ×•×ª setting_autofetch_changesets: משיכה ×וטומתי של ×¢×™×“×›×•× ×™× -setting_sys_api_enabled: Enable WS for repository management +setting_sys_api_enabled: ×פשר WS ×œ× ×™×”×•×œ המ×גר setting_commit_ref_keywords: מילות מפתח מקשרות setting_commit_fix_keywords: מילות מפתח ×ž×ª×§× ×•×ª setting_autologin: חיבור ×וטומטי @@ -233,7 +233,7 @@ label_information_plural: מידע label_please_login: התחבר בבקשה label_register: הרשמה label_password_lost: ×בדה הסיסמה? -label_home: דך הבית +label_home: דף הבית label_my_page: הדף שלי label_my_account: השבון שלי label_my_projects: ×”×¤×¨×•×™×§×˜×™× ×©×œ×™ @@ -259,7 +259,7 @@ label_subproject_plural: תת-×¤×¨×•×™×§×˜×™× label_min_max_length: ×ורך ×ž×™× ×™×ž×לי - מקסימ×לי label_list: רשימה label_date: ת×ריך -label_integer: מספר ×©×œ×™× +label_integer: מספר ×©×œ× label_boolean: ערך בולי×× ×™ label_string: טקסט label_text: טקסט ×רוך @@ -269,7 +269,7 @@ label_download: הורדה %d label_download_plural: %d הורדות label_no_data: ×ין מידע להציג label_change_status: ×©× ×” מצב -label_history: הידטוריה +label_history: היסטוריה label_attachment: קובץ label_attachment_new: קובץ חדש label_attachment_delete: מחק קובץ @@ -279,7 +279,7 @@ label_report_plural: דו"חות label_news: חדשות label_news_new: הוסף חדשות label_news_plural: חדשות -label_news_latest: חדשות חדשות +label_news_latest: חדשות ××—×¨×•× ×•×ª label_news_view_all: צפה בכל החדשות label_change_log: דו"×— ×©×™× ×•×™×™× label_settings: הגדרות @@ -419,7 +419,7 @@ label_reply_plural: השבות label_send_information: שלח מידע על חשבון למשתמש label_year: ×©× ×” label_month: חודש -label_week: שבו +label_week: שבוע label_date_from: מ×ת label_date_to: ×ל label_language_based: מבוסס שפה @@ -444,7 +444,7 @@ button_save: שמור button_check_all: בחר הכל button_uncheck_all: בחר ×›×œ×•× button_delete: מחק -button_create: צוק +button_create: צור button_test: בדוק button_edit: ערוך button_add: הוסף @@ -454,13 +454,13 @@ button_clear: × ×§×” button_lock: × ×¢×œ button_unlock: בטל × ×¢×™×œ×” button_download: הורד -button_list: קשימה +button_list: רשימה button_view: צפה button_move: ×”×–×– button_back: ×”×§×•×“× button_cancel: בטח button_activate: הפעל -button_sort: מין +button_sort: מיין button_log_time: זמן לוג button_rollback: חזור ×œ×’×™×¨×¡× ×–×• button_watch: צפה @@ -526,94 +526,95 @@ default_activity_development: פיתוח enumeration_issue_priorities: עדיפות × ×•×©××™× enumeration_doc_categories: קטגוריות ×ž×¡×ž×›×™× enumeration_activities: פעילויות (מעקב ×חר ×–×ž× ×™×) -label_search_titles_only: Search titles only -label_nobody: nobody -button_change_password: Change password -text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." -label_user_mail_option_selected: "For any event on the selected projects only..." -label_user_mail_option_all: "For any event on all my projects" -label_user_mail_option_none: "Only for things I watch or I'm involved in" -setting_emails_footer: Emails footer -label_float: Float -button_copy: Copy -mail_body_account_information_external: You can use your "%s" account to log in. -mail_body_account_information: Your account information -setting_protocol: Protocol -label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" -setting_time_format: Time format -label_registration_activation_by_email: account activation by email -mail_subject_account_activation_request: %s account activation request -mail_body_account_activation_request: 'A new user (%s) has registered. His account his pending your approval:' -label_registration_automatic_activation: automatic account activation -label_registration_manual_activation: manual account activation -notice_account_pending: "Your account was created and is now pending administrator approval." -field_time_zone: Time zone -text_caracters_minimum: Must be at least %d characters long. -setting_bcc_recipients: Blind carbon copy recipients (bcc) -button_annotate: Annotate -label_issues_by: Issues by %s -field_searchable: Searchable -label_display_per_page: 'Per page: %s' -setting_per_page_options: Objects per page options -label_age: Age -notice_default_data_loaded: Default configuration successfully loaded. -text_load_default_configuration: Load the default configuration -text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." -error_can_t_load_default_data: "Default configuration could not be loaded: %s" -button_update: Update -label_change_properties: Change properties -label_general: General -label_repository_plural: Repositories -label_associated_revisions: Associated revisions -setting_user_format: Users display format -text_status_changed_by_changeset: Applied in changeset %s. -label_more: More -text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?' +label_search_titles_only: חפש בכותרות בלבד +label_nobody: ××£ ×חד +button_change_password: ×©× ×” ×¡×™×¡×ž× +text_user_mail_option: "×‘×¤×¨×•×™×§×˜×™× ×©×œ× ×‘×—×¨×ª, ×תה רק תקבל התרעות על ש×תה צופה ×ו קשור ××œ×™×”× (לדוגמ×:× ×•×©××™× ×©×תה היוצר ×©×œ×”× ×ו ×ž×•×¦×‘×™× ×ליך)." +label_user_mail_option_selected: "לכל ×ירוע ×‘×¤×¨×•×™×§×˜×™× ×©×‘×—×¨×ª×™ בלבד..." +label_user_mail_option_all: "לכל ×ירוע בכל ×”×¤×¨×•×™×§×˜×™× ×©×œ×™" +label_user_mail_option_none: "רק ×œ× ×•×©××™× ×©×× ×™ צופה ×ו קשור ×ליה×" +setting_emails_footer: תחתית דו×"ל +label_float: צף +button_copy: העתק +mail_body_account_information_external: ×תה יכול להשתמש בחשבון "%s" כדי להתחבר +mail_body_account_information: פרטי החשבון שלך +setting_protocol: פרוטוקול +label_user_mail_no_self_notified: "×× ×™ ×œ× ×¨×•×¦×” שיודיעו לי על ×©×™× ×•×™×™× ×©×× ×™ מבצע" +setting_time_format: פורמט זמן +label_registration_activation_by_email: הפעל חשבון ב×מצעות דו×"ל +mail_subject_account_activation_request: בקשת הפעלה לחשבון %s +mail_body_account_activation_request: 'משתמש חדש (%s) × ×¨×©×. החשבון שלו מחכה ל×ישור שלך:' +label_registration_automatic_activation: הפעלת חשבון ×וטומטית +label_registration_manual_activation: הפעלת חשבון ×™×“× ×™×ª +notice_account_pending: "החשבון שלך × ×•×¦×¨ ועתה מחכה ל×ישור ×ž× ×”×œ המערכת." +field_time_zone: ×יזור זמן +text_caracters_minimum: חייב להיות לפחות ב×ורך של %d תווי×. +setting_bcc_recipients: מוסתר (bcc) +button_annotate: הוסף תי×ור מסגרת +label_issues_by: × ×•×©××™× ×©×œ %s +field_searchable: × ×™×ª×Ÿ לחיפוש +label_display_per_page: 'לכל דף: %s' +setting_per_page_options: ×פשרויות ××•×‘×™×§×˜×™× ×œ×¤×™ דף +label_age: גיל +notice_default_data_loaded: ×פשרויות ברירת מחדל מופעלות. +text_load_default_configuration: טען ×ת ×פשרויות ברירת המחדל +text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. ×™×”×™×” ב×פשרותך ×œ×©× ×•×ª×• ל×חר שיטען." +error_can_t_load_default_data: "×פשרויות ברירת המחדל ×œ× ×”×¦×œ×™×—×• להיטען: %s" +button_update: עדכן +label_change_properties: ×©× ×” מ××¤×™×™× ×™× +label_general: כללי +label_repository_plural: מ××’×¨×™× +label_associated_revisions: ×©×™× ×•×™×™× ×§×©×•×¨×™× +setting_user_format: פורמט הצגת ×ž×©×ª×ž×©×™× +text_status_changed_by_changeset: הוחל בסדרת ×”×©×™× ×•×™×™× %s. +label_more: עוד +text_issues_destroy_confirmation: '×”×× ×ת\×” בטוח ×©×‘×¨×¦×•× ×š למחוק ×ת ×”× ×•×©×\×™× ?' label_scm: SCM -text_select_project_modules: 'Select modules to enable for this project:' -label_issue_added: Issue added -label_issue_updated: Issue updated -label_document_added: Document added -label_message_posted: Message added -label_file_added: File added -label_news_added: News added -project_module_boards: Boards -project_module_issue_tracking: Issue tracking +text_select_project_modules: 'בחר ×ž×•×“×•×œ×™× ×œ×”×—×™×œ על פקרויקט ×–×”:' +label_issue_added: × ×•×©× ×”×•×¡×£ +label_issue_updated: × ×•×©× ×¢×•×“×›×Ÿ +label_document_added: מוסמך הוסף +label_message_posted: הודעה הוספה +label_file_added: קובץ הוסף +label_news_added: חדשות הוספו +project_module_boards: לוחות +project_module_issue_tracking: מעקב × ×•×©××™× project_module_wiki: Wiki -project_module_files: Files -project_module_documents: Documents -project_module_repository: Repository -project_module_news: News -project_module_time_tracking: Time tracking -text_file_repository_writable: File repository writable -text_default_administrator_account_changed: Default administrator account changed +project_module_files: ×§×‘×¦×™× +project_module_documents: ×ž×¡×ž×›×™× +project_module_repository: מ×גר +project_module_news: חדשות +project_module_time_tracking: מעקב ×חר ×–×ž× ×™× +text_file_repository_writable: מ×גר ×”×§×‘×¦×™× × ×™×ª×Ÿ לכתיבה +text_default_administrator_account_changed: ×ž× ×”×œ המערכת ברירת המחדל ×©×•× ×” text_rmagick_available: RMagick available (optional) -button_configure: Configure -label_plugins: Plugins -label_ldap_authentication: LDAP authentication +button_configure: ×פשרויות +label_plugins: פל××’×™× ×™× +label_ldap_authentication: ×ימות LDAP label_downloads_abbr: D/L -label_this_month: this month -label_last_n_days: last %d days -label_all_time: all time -label_this_year: this year -label_date_range: Date range -label_last_week: last week -label_yesterday: yesterday -label_last_month: last month -label_add_another_file: Add another file -label_optional_description: Optional description -text_destroy_time_entries_question: %.02f hours were reported on the issues you are about to delete. What do you want to do ? -error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' -text_assign_time_entries_to_project: Assign reported hours to the project -text_destroy_time_entries: Delete reported hours -text_reassign_time_entries: 'Reassign reported hours to this issue:' -setting_activity_days_default: Days displayed on project activity -label_chronological_order: In chronological order -field_comments_sorting: Display comments -label_reverse_chronological_order: In reverse chronological order -label_preferences: Preferences -setting_display_subprojects_issues: Display subprojects issues on main projects by default -label_overall_activity: Overall activity -setting_default_projects_public: New projects are public by default -error_scm_annotate: "The entry does not exist or can not be annotated." -label_planning: Planning +label_this_month: החודש +label_last_n_days: ב-%d ×™×ž×™× ××—×¨×•× ×™× +label_all_time: תמיד +label_this_year: ×”×©× ×” +label_date_range: טווח ת××¨×™×›×™× +label_last_week: שבוע שעבר +label_yesterday: ×תמול +label_last_month: חודש שעבר +label_add_another_file: הוסף עוד קובץ +label_optional_description: תי×ור רשות +text_destroy_time_entries_question: %.02f שעות דווחו על ×”× ×•×©×™× ×©×ת\×” עומד\ת למחוק. מה ×‘×¨×¦×•× ×š לעשות ? +error_issue_not_found_in_project: '×”× ×•×©××™× ×œ× × ×ž×¦×ו ×ו ××™× × ×©×™×›×™× ×œ×¤×¨×•×™×§×˜' +text_assign_time_entries_to_project: הצב שעות שדווחו לפרויקט ×”×–×” +text_destroy_time_entries: מחק שעות שדווחו +text_reassign_time_entries: 'הצב מחדש שעות שדווחו לפרויקט ×”×–×”:' +setting_activity_days_default: ×™×ž×™× ×”×ž×•×¦×’×™× ×¢×œ פעילות הפרויקט +label_chronological_order: בסדר ×›×¨×•× ×•×œ×•×’×™ +field_comments_sorting: הצג הערות +label_reverse_chronological_order: בסדר ×›×¨×•× ×•×œ×•×’×™ הפוך +label_preferences: העדפות +setting_display_subprojects_issues: הצג × ×•×©××™× ×©×œ תת ×¤×¨×•×™×§×˜×™× ×›×‘×¨×™×¨×ª מחדל +label_overall_activity: פעילות כוללת +setting_default_projects_public: ×¤×¨×•×™×§×˜×™× ×—×“×©×™× ×”×™× × ×¤×•×ž×‘×™×™× ×›×‘×¨×™×¨×ª מחדל +error_scm_annotate: "×”×›× ×™×¡×” ×œ× ×§×™×™×ž×ª ×ו ×©×œ× × ×™×ª×Ÿ לת×ר ×ותה." +label_planning: ×ª×›× ×•×Ÿ +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/it.yml b/lang/it.yml index 7e9345b30..3d1dea09e 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -617,3 +617,4 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/ja.yml b/lang/ja.yml index 44d0c3ebd..680d29836 100644 --- a/lang/ja.yml +++ b/lang/ja.yml @@ -618,3 +618,4 @@ label_overall_activity: å…¨ã¦ã®æ´»å‹• setting_default_projects_public: デフォルトã§æ–°ã—ã„プãƒã‚¸ã‚§ã‚¯ãƒˆã¯å…¬é–‹ã«ã™ã‚‹ error_scm_annotate: "エントリãŒå˜åœ¨ã—ãªã„ã€ã‚‚ã—ãã¯ã‚¢ãƒŽãƒ†ãƒ¼ãƒˆã§ãã¾ã›ã‚“。" label_planning: 計画 +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/ko.yml b/lang/ko.yml index a8b105855..4281f3881 100644 --- a/lang/ko.yml +++ b/lang/ko.yml @@ -617,3 +617,4 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/lt.yml b/lang/lt.yml index 5db73d300..df7cd960b 100644 --- a/lang/lt.yml +++ b/lang/lt.yml @@ -618,3 +618,4 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/nl.yml b/lang/nl.yml index 6494eae07..e487a7a6d 100644 --- a/lang/nl.yml +++ b/lang/nl.yml @@ -618,3 +618,4 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/no.yml b/lang/no.yml index f2d7ac144..22b8c10af 100644 --- a/lang/no.yml +++ b/lang/no.yml @@ -80,7 +80,7 @@ notice_default_data_loaded: Standardkonfigurasjonen lastet inn. error_can_t_load_default_data: "Standardkonfigurasjonen kunne ikke lastes inn: %s" error_scm_not_found: "Elementet og/eller revisjonen eksisterer ikke i depoet." error_scm_command_failed: "En feil oppstod under tilkobling til depoet: %s" -error_scm_annotate: "Elementet eksisterer ikke, eller kan ikke annoteres." +error_scm_annotate: "Elementet eksisterer ikke, eller kan ikke noteres." error_issue_not_found_in_project: 'Saken eksisterer ikke, eller hører ikke til dette prosjektet' mail_subject_lost_password: Ditt %s passord @@ -137,7 +137,7 @@ field_parent: Underprosjekt til field_is_in_chlog: Vises i endringslogg field_is_in_roadmap: Vises i veikart field_login: Brukernavn -field_mail_notification: E-post varsling +field_mail_notification: E-post-varsling field_admin: Administrator field_last_login_on: Sist innlogget field_language: SprÃ¥k @@ -165,7 +165,7 @@ field_url: URL field_start_page: Startside field_subproject: Underprosjekt field_hours: Timer -field_activity: Activitet +field_activity: Aktivitet field_spent_on: Dato field_identifier: Identifikasjon field_is_filter: Brukes som filter @@ -282,7 +282,7 @@ label_last_updates: Sist oppdatert label_last_updates_plural: %d siste oppdaterte label_registered_on: Registrert label_activity: Aktivitet -label_overall_activity: Total aktivitet +label_overall_activity: All aktivitet label_new: Ny label_logged_as: Innlogget som label_environment: Miljø @@ -352,7 +352,7 @@ label_gantt: Gantt label_internal: Intern label_last_changes: siste %d endringer label_change_view_all: Vis alle endringer -label_personalize_page: Tilrettelegg denne siden +label_personalize_page: Tilpass denne siden label_comment: Kommentar label_comment_plural: Kommentarer label_comment_add: Legg til kommentar @@ -545,7 +545,7 @@ button_reset: Nullstill button_rename: Endre navn button_change_password: Endre passord button_copy: Kopier -button_annotate: Annotér +button_annotate: Notér button_update: Oppdater button_configure: Konfigurer @@ -557,6 +557,7 @@ text_select_mail_notifications: Velg hendelser som skal varsles med e-post. text_regexp_info: eg. ^[A-Z0-9]+$ text_min_max_length_info: 0 betyr ingen begrensning text_project_destroy_confirmation: Er du sikker pÃ¥ at du vil slette dette prosjekter og alle relatert data ? +text_subprojects_destroy_warning: 'Underprojekt(ene): %s vil ogsÃ¥ bli slettet.' text_workflow_edit: Velg en rolle og en sakstype for Ã¥ endre arbeidsflyten text_are_you_sure: Er du sikker ? text_journal_changed: endret fra %s til %s @@ -565,7 +566,7 @@ text_journal_deleted: slettet text_tip_task_begin_day: oppgaven starter denne dagen text_tip_task_end_day: oppgaven avsluttes denne dagen text_tip_task_begin_end_day: oppgaven starter og avsluttes denne dagen -text_project_identifier_info: 'SmÃ¥ bokstaver (a-z), nummer og binde-/understrek tillat.<br />Identifikatoren kan ikke endres etter den er lagret.' +text_project_identifier_info: 'SmÃ¥ bokstaver (a-z), nummer og bindestrek tillatt.<br />Identifikatoren kan ikke endres etter den er lagret.' text_caracters_maximum: %d tegn maksimum. text_caracters_minimum: MÃ¥ være minst %d tegn langt. text_length_between: Lengde mellom %d og %d tegn. @@ -586,8 +587,8 @@ text_status_changed_by_changeset: Brukt i endringssett %s. text_issues_destroy_confirmation: 'Er du sikker pÃ¥ at du vil slette valgte sak(er) ?' text_select_project_modules: 'Velg moduler du vil aktivere for dette prosjektet:' text_default_administrator_account_changed: Standard administrator-konto er endret -text_file_repository_writable: Fil-depotet er skrivbart -text_rmagick_available: RMagick tilgjengelig (valgfritt) +text_file_repository_writable: Fil-arkivet er skrivbart +text_rmagick_available: RMagick er tilgjengelig (valgfritt) text_destroy_time_entries_question: %.02f timer er ført pÃ¥ sakene du er i ferd med Ã¥ slette. Hva vil du gjøre ? text_destroy_time_entries: Slett førte timer text_assign_time_entries_to_project: Overfør førte timer til prosjektet diff --git a/lang/pl.yml b/lang/pl.yml index fdb8afaa2..81f03a62f 100644 --- a/lang/pl.yml +++ b/lang/pl.yml @@ -617,3 +617,4 @@ label_overall_activity: Ogólna aktywność setting_default_projects_public: Nowe projekty sÄ… domyÅ›lnie publiczne error_scm_annotate: "Wpis nie istnieje lub nie można do niego dodawać adnotacji." label_planning: Planning +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/pt-br.yml b/lang/pt-br.yml index 218cbab51..9facd8d19 100644 --- a/lang/pt-br.yml +++ b/lang/pt-br.yml @@ -617,3 +617,4 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default
error_scm_annotate: "The entry does not exist or can not be annotated."
label_planning: Planning
+text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.'
diff --git a/lang/pt.yml b/lang/pt.yml index e82176c56..6f51c8ed2 100644 --- a/lang/pt.yml +++ b/lang/pt.yml @@ -617,3 +617,4 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/ro.yml b/lang/ro.yml index 4d26ab103..59edfeb70 100644 --- a/lang/ro.yml +++ b/lang/ro.yml @@ -617,3 +617,4 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/ru.yml b/lang/ru.yml index e488c35e1..f69009847 100644 --- a/lang/ru.yml +++ b/lang/ru.yml @@ -619,5 +619,6 @@ label_preferences: ÐŸÑ€ÐµÐ´Ð¿Ð¾Ñ‡Ñ‚ÐµÐ½Ð¸Ñ setting_display_subprojects_issues: Отображение подпроектов по умолчанию label_overall_activity: Ð¡Ð²Ð¾Ð´Ð½Ð°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ setting_default_projects_public: Ðовые проекты ÑвлÑÑŽÑ‚ÑÑ Ð¿ÑƒÐ±Ð»Ð¸Ñ‡Ð½Ñ‹Ð¼Ð¸ -error_scm_annotate: "The entry does not exist or can not be annotated." -label_planning: Planning +error_scm_annotate: "Данные отÑутÑтвуют или не могут быть подпиÑаны." +label_planning: Планирование +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/sr.yml b/lang/sr.yml index fa4ecd8de..d9869c362 100644 --- a/lang/sr.yml +++ b/lang/sr.yml @@ -618,3 +618,4 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/sv.yml b/lang/sv.yml index e1ac6b4bc..c0f691230 100644 --- a/lang/sv.yml +++ b/lang/sv.yml @@ -618,3 +618,4 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/uk.yml b/lang/uk.yml index 8fc418e67..a52a05603 100644 --- a/lang/uk.yml +++ b/lang/uk.yml @@ -619,3 +619,4 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/zh-tw.yml b/lang/zh-tw.yml index e16fbe591..a0c7fafb3 100644 --- a/lang/zh-tw.yml +++ b/lang/zh-tw.yml @@ -618,3 +618,4 @@ default_activity_development: 開發 enumeration_issue_priorities: é …ç›®å„ªå…ˆæ¬Š enumeration_doc_categories: 文件分類 enumeration_activities: 活動 (時間追蹤) +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lang/zh.yml b/lang/zh.yml index bff45a2ec..12fb8cb3e 100644 --- a/lang/zh.yml +++ b/lang/zh.yml @@ -618,3 +618,4 @@ default_activity_development: å¼€å‘ enumeration_issue_priorities: 问题优先级 enumeration_doc_categories: 文档类别 enumeration_activities: 活动(时间跟踪) +text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' diff --git a/lib/redcloth.rb b/lib/redcloth.rb index 5ed23b8f7..7e0c71839 100644 --- a/lib/redcloth.rb +++ b/lib/redcloth.rb @@ -1134,7 +1134,7 @@ class RedCloth < String ALLOWED_TAGS = %w(redpre pre code) def escape_html_tags(text) - text.gsub!(%r{<((\/?)(\w+))}) {|m| ALLOWED_TAGS.include?($3) ? "<#{$1}" : "<#{$1}" } + text.gsub!(%r{<(\/?(\w+)[^>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "<#{$1}#{'>' if $3}" } end end diff --git a/lib/redmine.rb b/lib/redmine.rb index 5443eef4a..2697e8f5f 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -1,6 +1,7 @@ require 'redmine/access_control' require 'redmine/menu_manager' require 'redmine/mime_type' +require 'redmine/core_ext' require 'redmine/themes' require 'redmine/plugin' diff --git a/lib/redmine/core_ext.rb b/lib/redmine/core_ext.rb new file mode 100644 index 000000000..573313e74 --- /dev/null +++ b/lib/redmine/core_ext.rb @@ -0,0 +1 @@ +Dir[File.dirname(__FILE__) + "/core_ext/*.rb"].each { |file| require(file) } diff --git a/lib/redmine/core_ext/string.rb b/lib/redmine/core_ext/string.rb new file mode 100644 index 000000000..ce2646fb9 --- /dev/null +++ b/lib/redmine/core_ext/string.rb @@ -0,0 +1,5 @@ +require File.dirname(__FILE__) + '/string/conversions' + +class String #:nodoc: + include Redmine::CoreExtensions::String::Conversions +end diff --git a/lib/redmine/core_ext/string/conversions.rb b/lib/redmine/core_ext/string/conversions.rb new file mode 100644 index 000000000..7444445b0 --- /dev/null +++ b/lib/redmine/core_ext/string/conversions.rb @@ -0,0 +1,40 @@ +# redMine - project management software +# Copyright (C) 2008 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module Redmine #:nodoc: + module CoreExtensions #:nodoc: + module String #:nodoc: + # Custom string conversions + module Conversions + # Parses hours format and returns a float + def to_hours + s = self.dup + s.strip! + unless s =~ %r{^[\d\.,]+$} + # 2:30 => 2.5 + s.gsub!(%r{^(\d+):(\d+)$}) { $1.to_i + $2.to_i / 60.0 } + # 2h30, 2h, 30m => 2.5, 2, 0.5 + s.gsub!(%r{^((\d+)\s*(h|hours?))?\s*((\d+)\s*(m|min)?)?$}) { |m| ($1 || $4) ? ($2.to_i + $5.to_i / 60.0) : m[0] } + end + # 2,5 => 2.5 + s.gsub!(',', '.') + s.to_f + end + end + end + end +end diff --git a/lib/redmine/scm/adapters/abstract_adapter.rb b/lib/redmine/scm/adapters/abstract_adapter.rb index 41edf00ad..2c254d48d 100644 --- a/lib/redmine/scm/adapters/abstract_adapter.rb +++ b/lib/redmine/scm/adapters/abstract_adapter.rb @@ -59,8 +59,17 @@ module Redmine # Returns the entry identified by path and revision identifier # or nil if entry doesn't exist in the repository def entry(path=nil, identifier=nil) - e = entries(path, identifier) - e ? e.first : nil + parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?} + search_path = parts[0..-2].join('/') + search_name = parts[-1] + if search_path.blank? && search_name.blank? + # Root entry + Entry.new(:path => '', :kind => 'dir') + else + # Search for the entry in the parent directory + es = entries(search_path, identifier) + es ? es.detect {|e| e.name == search_name} : nil + end end # Returns an Entries collection diff --git a/lib/redmine/scm/adapters/bazaar_adapter.rb b/lib/redmine/scm/adapters/bazaar_adapter.rb index 11a44b7cf..2225a627c 100644 --- a/lib/redmine/scm/adapters/bazaar_adapter.rb +++ b/lib/redmine/scm/adapters/bazaar_adapter.rb @@ -44,18 +44,6 @@ module Redmine return nil end - # Returns the entry identified by path and revision identifier - # or nil if entry doesn't exist in the repository - def entry(path=nil, identifier=nil) - path ||= '' - parts = path.split(%r{[\/\\]}).select {|p| !p.blank?} - if parts.size > 0 - parent = parts[0..-2].join('/') - entries = entries(parent, identifier) - entries ? entries.detect {|e| e.name == parts.last} : nil - end - end - # Returns an Entries collection # or nil if the given path doesn't exist in the repository def entries(path=nil, identifier=nil) diff --git a/lib/redmine/scm/adapters/cvs_adapter.rb b/lib/redmine/scm/adapters/cvs_adapter.rb index c0f60c02a..37920b599 100644 --- a/lib/redmine/scm/adapters/cvs_adapter.rb +++ b/lib/redmine/scm/adapters/cvs_adapter.rb @@ -55,15 +55,6 @@ module Redmine def get_previous_revision(revision) CvsRevisionHelper.new(revision).prevRev end - - # Returns the entry identified by path and revision identifier - # or nil if entry doesn't exist in the repository - # this method returns all revisions from one single SCM-Entry - def entry(path=nil, identifier="HEAD") - e = entries(path, identifier) - logger.debug("<cvs-result> #{e.first.inspect}") if e - e ? e.first : nil - end # Returns an Entries collection # or nil if the given path doesn't exist in the repository @@ -72,7 +63,9 @@ module Redmine logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'" path_with_project="#{url}#{with_leading_slash(path)}" entries = Entries.new - cmd = "#{CVS_BIN} -d #{root_url} rls -ed #{path_with_project}" + cmd = "#{CVS_BIN} -d #{root_url} rls -ed" + cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier + cmd << " #{path_with_project}" shellout(cmd) do |io| io.each_line(){|line| fields=line.chop.split('/',-1) diff --git a/lib/redmine/scm/adapters/darcs_adapter.rb b/lib/redmine/scm/adapters/darcs_adapter.rb index cd8610121..a1d1867b1 100644 --- a/lib/redmine/scm/adapters/darcs_adapter.rb +++ b/lib/redmine/scm/adapters/darcs_adapter.rb @@ -40,20 +40,15 @@ module Redmine rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil end - # Returns the entry identified by path and revision identifier - # or nil if entry doesn't exist in the repository - def entry(path=nil, identifier=nil) - e = entries(path, identifier) - e ? e.first : nil - end - # Returns an Entries collection # or nil if the given path doesn't exist in the repository def entries(path=nil, identifier=nil) path_prefix = (path.blank? ? '' : "#{path}/") path = '.' if path.blank? entries = Entries.new - cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output #{path}" + cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output" + cmd << " --match \"hash #{identifier}\"" if identifier + cmd << " #{path}" shellout(cmd) do |io| begin doc = REXML::Document.new(io) diff --git a/lib/redmine/scm/adapters/git_adapter.rb b/lib/redmine/scm/adapters/git_adapter.rb index f1d076360..77604f283 100644 --- a/lib/redmine/scm/adapters/git_adapter.rb +++ b/lib/redmine/scm/adapters/git_adapter.rb @@ -79,7 +79,7 @@ module Redmine rev = Revision.new({:identifier => changeset[:commit], :scmid => changeset[:commit], :author => changeset[:author], - :time => Time.parse(changeset[:date]), + :time => (changeset[:date] ? Time.parse(changeset[:date]) : nil), :message => changeset[:description], :paths => files }) @@ -132,14 +132,6 @@ module Redmine entries.sort_by_name end - def entry(path=nil, identifier=nil) - path ||= '' - search_path = path.split('/')[0..-2].join('/') - entry_name = path.split('/').last - e = entries(search_path, identifier) - e ? e.detect{|entry| entry.name == entry_name} : nil - end - def revisions(path, identifier_from, identifier_to, options={}) revisions = Revisions.new cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw " diff --git a/lib/redmine/scm/adapters/mercurial_adapter.rb b/lib/redmine/scm/adapters/mercurial_adapter.rb index ff52ab893..6f42dda06 100644 --- a/lib/redmine/scm/adapters/mercurial_adapter.rb +++ b/lib/redmine/scm/adapters/mercurial_adapter.rb @@ -59,14 +59,6 @@ module Redmine return nil if $? && $?.exitstatus != 0 entries.sort_by_name end - - def entry(path=nil, identifier=nil) - path ||= '' - search_path = path.split('/')[0..-2].join('/') - entry_name = path.split('/').last - e = entries(search_path, identifier) - e ? e.detect{|entry| entry.name == entry_name} : nil - end def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) revisions = Revisions.new @@ -88,13 +80,7 @@ module Redmine value = $2 if parsing_descr && line_feeds > 1 parsing_descr = false - revisions << Revision.new({:identifier => changeset[:changeset].split(':').first.to_i, - :scmid => changeset[:changeset].split(':').last, - :author => changeset[:user], - :time => Time.parse(changeset[:date]), - :message => changeset[:description], - :paths => changeset[:files].to_s.split.collect{|path| {:action => 'X', :path => "/#{path}"}} - }) + revisions << build_revision_from_changeset(changeset) changeset = {} end if !parsing_descr @@ -111,13 +97,8 @@ module Redmine line_feeds += 1 if line.chomp.empty? end end - revisions << Revision.new({:identifier => changeset[:changeset].split(':').first.to_i, - :scmid => changeset[:changeset].split(':').last, - :author => changeset[:user], - :time => Time.parse(changeset[:date]), - :message => changeset[:description], - :paths => changeset[:files].to_s.split.collect{|path| {:action => 'X', :path => "/#{path}"}} - }) + # Add the last changeset if there is one left + revisions << build_revision_from_changeset(changeset) if changeset[:date] end return nil if $? && $?.exitstatus != 0 revisions @@ -171,6 +152,47 @@ module Redmine return nil if $? && $?.exitstatus != 0 blame end + + private + + # Builds a revision objet from the changeset returned by hg command + def build_revision_from_changeset(changeset) + rev_id = changeset[:changeset].to_s.split(':').first.to_i + + # Changes + paths = (rev_id == 0) ? + # Can't get changes for revision 0 with hg status + changeset[:files].to_s.split.collect{|path| {:action => 'A', :path => "/#{path}"}} : + status(rev_id) + + Revision.new({:identifier => rev_id, + :scmid => changeset[:changeset].to_s.split(':').last, + :author => changeset[:user], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => paths + }) + end + + # Returns the file changes for a given revision + def status(rev_id) + cmd = "#{HG_BIN} -R #{target('')} status --rev #{rev_id.to_i - 1}:#{rev_id.to_i}" + result = [] + shellout(cmd) do |io| + io.each_line do |line| + action, file = line.chomp.split + next unless action && file + file.gsub!("\\", "/") + case action + when 'R' + result << { :action => 'D' , :path => "/#{file}" } + else + result << { :action => action, :path => "/#{file}" } + end + end + end + result + end end end end diff --git a/lib/redmine/scm/adapters/subversion_adapter.rb b/lib/redmine/scm/adapters/subversion_adapter.rb index 1e0320e2c..40c7eb3f1 100644 --- a/lib/redmine/scm/adapters/subversion_adapter.rb +++ b/lib/redmine/scm/adapters/subversion_adapter.rb @@ -51,18 +51,11 @@ module Redmine return nil
end
- # Returns the entry identified by path and revision identifier
- # or nil if entry doesn't exist in the repository
- def entry(path=nil, identifier=nil)
- e = entries(path, identifier)
- e ? e.first : nil
- end
-
# Returns an Entries collection
# or nil if the given path doesn't exist in the repository
def entries(path=nil, identifier=nil)
path ||= ''
- identifier = 'HEAD' unless identifier and identifier > 0
+ identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
entries = Entries.new
cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
cmd << credentials_string
@@ -94,8 +87,8 @@ module Redmine def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
path ||= ''
- identifier_from = 'HEAD' unless identifier_from and identifier_from.to_i > 0
- identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
+ identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
+ identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
revisions = Revisions.new
cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
cmd << credentials_string
@@ -131,11 +124,9 @@ module Redmine def diff(path, identifier_from, identifier_to=nil, type="inline")
path ||= ''
- if identifier_to and identifier_to.to_i > 0
- identifier_to = identifier_to.to_i
- else
- identifier_to = identifier_from.to_i - 1
- end
+ identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
+ identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
+
cmd = "#{SVN_BIN} diff -r "
cmd << "#{identifier_to}:"
cmd << "#{identifier_from}"
diff --git a/lib/redmine/wiki_formatting/macros.rb b/lib/redmine/wiki_formatting/macros.rb index c0f2b222a..0848aee4e 100644 --- a/lib/redmine/wiki_formatting/macros.rb +++ b/lib/redmine/wiki_formatting/macros.rb @@ -77,21 +77,24 @@ module Redmine content_tag('dl', out) end - desc "Include a wiki page. Example:\n\n !{{include(Foo)}}" + desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}" macro :include do |obj, args| - if @project && !@project.wiki.nil? - page = @project.wiki.find_page(args.first) - if page && page.content - @included_wiki_pages ||= [] - raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title) - @included_wiki_pages << page.title - out = textilizable(page.content, :text) - @included_wiki_pages.pop - out - else - raise "Page #{args.first} doesn't exist" - end + project = @project + title = args.first.to_s + if title =~ %r{^([^\:]+)\:(.*)$} + project_identifier, title = $1, $2 + project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier) end + raise 'Unknow project' unless project && User.current.allowed_to?(:view_wiki_pages, project) + raise 'No wiki for this project' unless !project.wiki.nil? + page = project.wiki.find_page(title) + raise "Page #{args.first} doesn't exist" unless page && page.content + @included_wiki_pages ||= [] + raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title) + @included_wiki_pages << page.title + out = textilizable(page.content, :text, :attachments => page.attachments) + @included_wiki_pages.pop + out end end end diff --git a/public/images/attachment.png b/public/images/attachment.png Binary files differindex eea26921b..b7ce3c445 100644 --- a/public/images/attachment.png +++ b/public/images/attachment.png diff --git a/public/images/changeset.png b/public/images/changeset.png Binary files differnew file mode 100644 index 000000000..67de2c6cc --- /dev/null +++ b/public/images/changeset.png diff --git a/public/images/comments.png b/public/images/comments.png Binary files differnew file mode 100644 index 000000000..39433cf78 --- /dev/null +++ b/public/images/comments.png diff --git a/public/images/document.png b/public/images/document.png Binary files differnew file mode 100644 index 000000000..d00b9b2f4 --- /dev/null +++ b/public/images/document.png diff --git a/public/images/message.png b/public/images/message.png Binary files differnew file mode 100644 index 000000000..252ea14d5 --- /dev/null +++ b/public/images/message.png diff --git a/public/images/news.png b/public/images/news.png Binary files differnew file mode 100644 index 000000000..6a2ecce1b --- /dev/null +++ b/public/images/news.png diff --git a/public/images/ticket.png b/public/images/ticket.png Binary files differnew file mode 100644 index 000000000..244e6ca04 --- /dev/null +++ b/public/images/ticket.png diff --git a/public/images/ticket_checked.png b/public/images/ticket_checked.png Binary files differnew file mode 100644 index 000000000..4b1dfbc3e --- /dev/null +++ b/public/images/ticket_checked.png diff --git a/public/images/ticket_edit.png b/public/images/ticket_edit.png Binary files differnew file mode 100644 index 000000000..291bfc764 --- /dev/null +++ b/public/images/ticket_edit.png diff --git a/public/images/wiki_edit.png b/public/images/wiki_edit.png Binary files differnew file mode 100644 index 000000000..bdc333a65 --- /dev/null +++ b/public/images/wiki_edit.png diff --git a/public/javascripts/context_menu.js b/public/javascripts/context_menu.js index e3f128d89..3e2d571fa 100644 --- a/public/javascripts/context_menu.js +++ b/public/javascripts/context_menu.js @@ -93,14 +93,55 @@ ContextMenu.prototype = { }, showMenu: function(e) { - $('context-menu').style['left'] = (Event.pointerX(e) + 'px'); - $('context-menu').style['top'] = (Event.pointerY(e) + 'px'); - Element.update('context-menu', ''); - new Ajax.Updater({success:'context-menu'}, this.url, + var mouse_x = Event.pointerX(e); + var mouse_y = Event.pointerY(e); + var render_x = mouse_x; + var render_y = mouse_y; + var dims; + var menu_width; + var menu_height; + var window_width; + var window_height; + var max_width; + var max_height; + + $('context-menu').style['left'] = (render_x + 'px'); + $('context-menu').style['top'] = (render_y + 'px'); + Element.update('context-menu', ''); + + new Ajax.Updater({success:'context-menu'}, this.url, {asynchronous:true, evalScripts:true, parameters:Form.serialize(Event.findElement(e, 'form')), onComplete:function(request){ + dims = $('context-menu').getDimensions(); + menu_width = dims.width; + menu_height = dims.height; + max_width = mouse_x + 2*menu_width; + max_height = mouse_y + menu_height; + + var ws = window_size(); + window_width = ws.width; + window_height = ws.height; + + /* display the menu above and/or to the left of the click if needed */ + if (max_width > window_width) { + render_x -= menu_width; + $('context-menu').addClassName('reverse-x'); + } else { + $('context-menu').removeClassName('reverse-x'); + } + if (max_height > window_height) { + render_y -= menu_height; + $('context-menu').addClassName('reverse-y'); + } else { + $('context-menu').removeClassName('reverse-y'); + } + if (render_x <= 0) render_x = 1; + if (render_y <= 0) render_y = 1; + $('context-menu').style['left'] = (render_x + 'px'); + $('context-menu').style['top'] = (render_y + 'px'); + Effect.Appear('context-menu', {duration: 0.20}); if (window.parseStylesheets) { window.parseStylesheets(); } // IE }}) @@ -159,3 +200,19 @@ function toggleIssuesSelection(el) { } } } + +function window_size() { + var w; + var h; + if (window.innerWidth) { + w = window.innerWidth; + h = window.innerHeight; + } else if (document.documentElement) { + w = document.documentElement.clientWidth; + h = document.documentElement.clientHeight; + } else { + w = document.body.clientWidth; + h = document.body.clientHeight; + } + return {width: w, height: h}; +} diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index a1981c64a..26f66f0b8 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -57,10 +57,7 @@ h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; bord #content { width: 80%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;} * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;} -html>body #content { -height: auto; -min-height: 600px; -} +html>body #content { height: auto; min-height: 600px; overflow: auto; } #main.nosidebar #sidebar{ display: none; } #main.nosidebar #content{ width: auto; border-right: 0; } @@ -162,7 +159,13 @@ div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;} p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; } -fieldset#filters .buttons { text-align: right; font-size: 0.9em; margin: 0 4px 0px 0; } +fieldset#filters { padding: 0.7em; } +fieldset#filters p { margin: 1.2em 0 0.8em 2px; } +fieldset#filters .buttons { font-size: 0.9em; } +fieldset#filters table { border-collapse: collapse; } +fieldset#filters table td { padding: 0; vertical-align: middle; } +fieldset#filters tr.filter { height: 2em; } +fieldset#filters td.add-filter { text-align: right; vertical-align: top; } div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;} div#issue-changesets .changeset { padding: 4px;} @@ -170,11 +173,21 @@ div#issue-changesets .changeset { border-bottom: 1px solid #ddd; } div#issue-changesets p { margin-top: 0; margin-bottom: 1em;} div#activity dl { margin-left: 2em; } -div#activity dd { margin-bottom: 1em; } -div#activity dt { margin-bottom: 1px; } +div#activity dd { margin-bottom: 1em; padding-left: 18px; } +div#activity dt { margin-bottom: 1px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; } div#activity dt .time { color: #777; font-size: 80%; } div#activity dd .description { font-style: italic; } div#activity span.project:after { content: " -"; } +div#activity dt.issue { background-image: url(../images/ticket.png); } +div#activity dt.issue-edit { background-image: url(../images/ticket_edit.png); } +div#activity dt.issue-closed { background-image: url(../images/ticket_checked.png); } +div#activity dt.changeset { background-image: url(../images/changeset.png); } +div#activity dt.news { background-image: url(../images/news.png); } +div#activity dt.message { background-image: url(../images/message.png); } +div#activity dt.reply { background-image: url(../images/comments.png); } +div#activity dt.wiki-page { background-image: url(../images/wiki_edit.png); } +div#activity dt.attachment { background-image: url(../images/attachment.png); } +div#activity dt.document { background-image: url(../images/document.png); } div#roadmap fieldset.related-issues { margin-bottom: 1em; } div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; } @@ -186,7 +199,7 @@ div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom div#version-summary fieldset { margin-bottom: 1em; } div#version-summary .total-hours { text-align: right; } -table#time-report td.hours { text-align: right; padding-right: 0.5em; } +table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; } table#time-report tbody tr { font-style: italic; color: #777; } table#time-report tbody tr.last-level { font-style: normal; color: #555; } table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; } @@ -195,7 +208,7 @@ table#time-report .hours-dec { font-size: 0.9em; } .total-hours { font-size: 110%; font-weight: bold; } .total-hours span.hours-int { font-size: 120%; } -.autoscroll {overflow-x: auto; padding:1px; width:100%; margin-bottom: 1.2em;} +.autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;} #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; } .pagination {font-size: 90%} @@ -246,7 +259,7 @@ p.other-formats { text-align: right; font-size:0.9em; color: #666; } a.feed { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; } /***** Flash & error messages ****/ -#errorExplanation, div.flash, .nodata { +#errorExplanation, div.flash, .nodata, .warning { padding: 4px 4px 4px 30px; margin-bottom: 12px; font-size: 1.1em; @@ -269,7 +282,7 @@ div.flash.notice { color: #005f00; } -.nodata { +.nodata, .warning { text-align: center; background-color: #FFEBC1; border-color: #FDBF3B; @@ -575,7 +588,7 @@ vertical-align: middle; /***** Media print specific styles *****/ @media print { - #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; } + #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; } #main { background: #fff; } #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; } } diff --git a/public/stylesheets/context_menu.css b/public/stylesheets/context_menu.css index f68b33fe1..e5a83be0d 100644 --- a/public/stylesheets/context_menu.css +++ b/public/stylesheets/context_menu.css @@ -22,13 +22,13 @@ padding:1px; z-index:9; } -#context-menu li.folder ul { - position:absolute; - left:168px; /* IE6 */ - top:-2px; -} +#context-menu li.folder ul { position:absolute; left:168px; /* IE6 */ top:-2px; } #context-menu li.folder>ul { left:148px; } +#context-menu.reverse-y li.folder>ul { top:auto; bottom:0; } +#context-menu.reverse-x li.folder ul { left:auto; right:168px; /* IE6 */ } +#context-menu.reverse-x li.folder>ul { right:148px; } + #context-menu a { border:1px solid white; text-decoration:none; diff --git a/test/fixtures/attachments.yml b/test/fixtures/attachments.yml index 764948755..162d44720 100644 --- a/test/fixtures/attachments.yml +++ b/test/fixtures/attachments.yml @@ -23,4 +23,17 @@ attachments_002: filesize: 28
filename: document.txt
author_id: 2
+attachments_003:
+ created_on: 2006-07-19 21:07:27 +02:00
+ downloads: 0
+ content_type: image/gif
+ disk_filename: 060719210727_logo.gif
+ container_id: 4
+ digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
+ id: 3
+ container_type: WikiPage
+ filesize: 280
+ filename: logo.gif
+ description: This is a logo
+ author_id: 2
\ No newline at end of file diff --git a/test/fixtures/custom_fields.yml b/test/fixtures/custom_fields.yml index e58d8e3dc..6be840fcc 100644 --- a/test/fixtures/custom_fields.yml +++ b/test/fixtures/custom_fields.yml @@ -3,7 +3,7 @@ custom_fields_001: name: Database
min_length: 0
regexp: ""
- is_for_all: false
+ is_for_all: true
type: IssueCustomField
max_length: 0
possible_values: MySQL|PostgreSQL|Oracle
diff --git a/test/fixtures/queries.yml b/test/fixtures/queries.yml index a4c045b15..f12022729 100644 --- a/test/fixtures/queries.yml +++ b/test/fixtures/queries.yml @@ -1,7 +1,9 @@ ---
queries_001:
- name: Multiple custom fields query
+ id: 1
project_id: 1
+ is_public: true
+ name: Multiple custom fields query
filters: |
---
cf_1:
@@ -17,6 +19,51 @@ queries_001: - "125"
:operator: "="
- id: 1
- is_public: true
user_id: 1
+ column_names:
+queries_002:
+ id: 2
+ project_id: 1
+ is_public: false
+ name: Private query for cookbook
+ filters: |
+ ---
+ tracker_id:
+ :values:
+ - "3"
+ :operator: "="
+ status_id:
+ :values:
+ - "1"
+ :operator: o
+
+ user_id: 3
+ column_names:
+queries_003:
+ id: 3
+ project_id:
+ is_public: false
+ name: Private query for all projects
+ filters: |
+ ---
+ tracker_id:
+ :values:
+ - "3"
+ :operator: "="
+
+ user_id: 3
+ column_names:
+queries_004:
+ id: 4
+ project_id:
+ is_public: true
+ name: Public query for all projects
+ filters: |
+ ---
+ tracker_id:
+ :values:
+ - "3"
+ :operator: "="
+
+ user_id: 2
+ column_names:
diff --git a/test/fixtures/roles.yml b/test/fixtures/roles.yml index 0aabbb619..1ede6fca9 100644 --- a/test/fixtures/roles.yml +++ b/test/fixtures/roles.yml @@ -57,7 +57,6 @@ roles_002: - :add_issue_notes
- :move_issues
- :delete_issues
- - :manage_public_queries
- :save_queries
- :view_gantt
- :view_calendar
@@ -94,7 +93,6 @@ roles_003: - :manage_issue_relations
- :add_issue_notes
- :move_issues
- - :manage_public_queries
- :save_queries
- :view_gantt
- :view_calendar
diff --git a/test/fixtures/time_entries.yml b/test/fixtures/time_entries.yml index 151077d2b..4a8a4a2a4 100644 --- a/test/fixtures/time_entries.yml +++ b/test/fixtures/time_entries.yml @@ -15,7 +15,7 @@ time_entries_001: tyear: 2007
time_entries_002:
created_on: 2007-03-23 14:11:04 +01:00
- tweek: 12
+ tweek: 11
tmonth: 3
project_id: 1
comments: ""
@@ -36,7 +36,7 @@ time_entries_003: updated_on: 2007-04-21 12:20:48 +02:00
activity_id: 9
spent_on: 2007-04-21
- issue_id: 2
+ issue_id: 3
id: 3
hours: 1.0
user_id: 1
diff --git a/test/fixtures/wiki_contents.yml b/test/fixtures/wiki_contents.yml index 6937dbd14..5d6d3f1de 100644 --- a/test/fixtures/wiki_contents.yml +++ b/test/fixtures/wiki_contents.yml @@ -15,6 +15,8 @@ wiki_contents_002: h1. Another page
This is a link to a ticket: #2
+ And this is an included page:
+ {{include(Page with an inline image)}}
updated_on: 2007-03-08 00:18:07 +01:00
page_id: 2
id: 2
@@ -32,3 +34,17 @@ wiki_contents_003: version: 1
author_id: 1
comments:
+wiki_contents_004:
+ text: |-
+ h1. Page with an inline image
+
+ This is an inline image:
+
+ !logo.gif!
+ updated_on: 2007-03-08 00:18:07 +01:00
+ page_id: 4
+ id: 4
+ version: 1
+ author_id: 1
+ comments:
+
\ No newline at end of file diff --git a/test/fixtures/wiki_pages.yml b/test/fixtures/wiki_pages.yml index ee260291d..f89832e44 100644 --- a/test/fixtures/wiki_pages.yml +++ b/test/fixtures/wiki_pages.yml @@ -14,4 +14,9 @@ wiki_pages_003: title: Start_page
id: 3
wiki_id: 2
+wiki_pages_004:
+ created_on: 2007-03-08 00:18:07 +01:00
+ title: Page_with_an_inline_image
+ id: 4
+ wiki_id: 1
\ No newline at end of file diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 7d49f088d..042a8f3f2 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -181,6 +181,16 @@ class IssuesControllerTest < Test::Unit::TestCase assert_equal 'Value for field 2', v.value end + def test_post_new_without_custom_fields_param + @request.session[:user_id] = 2 + post :new, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5} + assert_redirected_to 'issues/show' + end + def test_copy_issue @request.session[:user_id] = 2 get :new, :project_id => 1, :copy_from => 1 @@ -440,10 +450,11 @@ class IssuesControllerTest < Test::Unit::TestCase end def test_destroy_issue_with_no_time_entries + assert_nil TimeEntry.find_by_issue_id(2) @request.session[:user_id] = 2 - post :destroy, :id => 3 + post :destroy, :id => 2 assert_redirected_to 'projects/ecookbook/issues' - assert_nil Issue.find_by_id(3) + assert_nil Issue.find_by_id(2) end def test_destroy_issues_with_time_entries diff --git a/test/functional/messages_controller_test.rb b/test/functional/messages_controller_test.rb index dcfe0caa7..1fe8d086a 100644 --- a/test/functional/messages_controller_test.rb +++ b/test/functional/messages_controller_test.rb @@ -54,6 +54,9 @@ class MessagesControllerTest < Test::Unit::TestCase def test_post_new @request.session[:user_id] = 2 + ActionMailer::Base.deliveries.clear + Setting.notified_events << 'message_posted' + post :new, :board_id => 1, :message => { :subject => 'Test created message', :content => 'Message body'} @@ -63,6 +66,15 @@ class MessagesControllerTest < Test::Unit::TestCase assert_equal 'Message body', message.content assert_equal 2, message.author_id assert_equal 1, message.board_id + + mail = ActionMailer::Base.deliveries.last + assert_kind_of TMail::Mail, mail + assert_equal "[#{message.board.project.name} - #{message.board.name}] Test created message", mail.subject + assert mail.body.include?('Message body') + # author + assert mail.bcc.include?('jsmith@somenet.foo') + # project member + assert mail.bcc.include?('dlopper@somenet.foo') end def test_get_edit diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 75b4673a1..eb5795152 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -144,7 +144,7 @@ class ProjectsControllerTest < Test::Unit::TestCase :content => /#{2.days.ago.to_date.day}/, :sibling => { :tag => "dl", :child => { :tag => "dt", - :attributes => { :class => 'journal' }, + :attributes => { :class => 'issue-edit' }, :child => { :tag => "a", :content => /(#{IssueStatus.find(2).name})/, } diff --git a/test/functional/queries_controller_test.rb b/test/functional/queries_controller_test.rb new file mode 100644 index 000000000..de08b4245 --- /dev/null +++ b/test/functional/queries_controller_test.rb @@ -0,0 +1,211 @@ +# redMine - project management software +# Copyright (C) 2006-2008 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.dirname(__FILE__) + '/../test_helper' +require 'queries_controller' + +# Re-raise errors caught by the controller. +class QueriesController; def rescue_action(e) raise e end; end + +class QueriesControllerTest < Test::Unit::TestCase + fixtures :projects, :users, :members, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :queries + + def setup + @controller = QueriesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + end + + def test_get_new_project_query + @request.session[:user_id] = 2 + get :new, :project_id => 1 + assert_response :success + assert_template 'new' + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query[is_public]', + :checked => nil } + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query_is_for_all', + :checked => nil, + :disabled => nil } + end + + def test_get_new_global_query + @request.session[:user_id] = 2 + get :new + assert_response :success + assert_template 'new' + assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query[is_public]' } + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query_is_for_all', + :checked => 'checked', + :disabled => nil } + end + + def test_new_project_public_query + @request.session[:user_id] = 2 + post :new, + :project_id => 'ecookbook', + :confirm => '1', + :default_columns => '1', + :fields => ["status_id", "assigned_to_id"], + :operators => {"assigned_to_id" => "=", "status_id" => "o"}, + :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, + :query => {"name" => "test_new_project_public_query", "is_public" => "1"} + + q = Query.find_by_name('test_new_project_public_query') + assert_redirected_to :controller => 'issues', :action => 'index', :query_id => q + assert q.is_public? + assert q.has_default_columns? + assert q.valid? + end + + def test_new_project_private_query + @request.session[:user_id] = 3 + post :new, + :project_id => 'ecookbook', + :confirm => '1', + :default_columns => '1', + :fields => ["status_id", "assigned_to_id"], + :operators => {"assigned_to_id" => "=", "status_id" => "o"}, + :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, + :query => {"name" => "test_new_project_private_query", "is_public" => "1"} + + q = Query.find_by_name('test_new_project_private_query') + assert_redirected_to :controller => 'issues', :action => 'index', :query_id => q + assert !q.is_public? + assert q.has_default_columns? + assert q.valid? + end + + def test_new_global_private_query_with_custom_columns + @request.session[:user_id] = 3 + post :new, + :confirm => '1', + :fields => ["status_id", "assigned_to_id"], + :operators => {"assigned_to_id" => "=", "status_id" => "o"}, + :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, + :query => {"name" => "test_new_global_private_query", "is_public" => "1", "column_names" => ["", "tracker", "subject", "priority", "category"]} + + q = Query.find_by_name('test_new_global_private_query') + assert_redirected_to :controller => 'issues', :action => 'index', :query_id => q + assert !q.is_public? + assert !q.has_default_columns? + assert_equal [:tracker, :subject, :priority, :category], q.columns.collect {|c| c.name} + assert q.valid? + end + + def test_get_edit_global_public_query + @request.session[:user_id] = 1 + get :edit, :id => 4 + assert_response :success + assert_template 'edit' + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query[is_public]', + :checked => 'checked' } + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query_is_for_all', + :checked => 'checked', + :disabled => 'disabled' } + end + + def test_edit_global_public_query + @request.session[:user_id] = 1 + post :edit, + :id => 4, + :confirm => '1', + :default_columns => '1', + :fields => ["status_id", "assigned_to_id"], + :operators => {"assigned_to_id" => "=", "status_id" => "o"}, + :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, + :query => {"name" => "test_edit_global_public_query", "is_public" => "1"} + + assert_redirected_to :controller => 'issues', :action => 'index', :query_id => 4 + q = Query.find_by_name('test_edit_global_public_query') + assert q.is_public? + assert q.has_default_columns? + assert q.valid? + end + + def test_get_edit_global_private_query + @request.session[:user_id] = 3 + get :edit, :id => 3 + assert_response :success + assert_template 'edit' + assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query[is_public]' } + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query_is_for_all', + :checked => 'checked', + :disabled => 'disabled' } + end + + def test_edit_global_private_query + @request.session[:user_id] = 3 + post :edit, + :id => 3, + :confirm => '1', + :default_columns => '1', + :fields => ["status_id", "assigned_to_id"], + :operators => {"assigned_to_id" => "=", "status_id" => "o"}, + :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, + :query => {"name" => "test_edit_global_private_query", "is_public" => "1"} + + assert_redirected_to :controller => 'issues', :action => 'index', :query_id => 3 + q = Query.find_by_name('test_edit_global_private_query') + assert !q.is_public? + assert q.has_default_columns? + assert q.valid? + end + + def test_get_edit_project_private_query + @request.session[:user_id] = 3 + get :edit, :id => 2 + assert_response :success + assert_template 'edit' + assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query[is_public]' } + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query_is_for_all', + :checked => nil, + :disabled => nil } + end + + def test_get_edit_project_public_query + @request.session[:user_id] = 2 + get :edit, :id => 1 + assert_response :success + assert_template 'edit' + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query[is_public]', + :checked => 'checked' + } + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query_is_for_all', + :checked => nil, + :disabled => 'disabled' } + end + + def test_destroy + @request.session[:user_id] = 2 + post :destroy, :id => 1 + assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :set_filter => 1, :query_id => nil + assert_nil Query.find_by_id(1) + end +end diff --git a/test/functional/repositories_bazaar_controller_test.rb b/test/functional/repositories_bazaar_controller_test.rb new file mode 100644 index 000000000..acb6c1d21 --- /dev/null +++ b/test/functional/repositories_bazaar_controller_test.rb @@ -0,0 +1,137 @@ +# redMine - project management software +# Copyright (C) 2006-2008 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.dirname(__FILE__) + '/../test_helper' +require 'repositories_controller' + +# Re-raise errors caught by the controller. +class RepositoriesController; def rescue_action(e) raise e end; end + +class RepositoriesBazaarControllerTest < Test::Unit::TestCase + fixtures :projects, :users, :roles, :members, :repositories, :enabled_modules + + # No '..' in the repository path + REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/bazaar_repository' + + def setup + @controller = RepositoriesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + Repository::Bazaar.create(:project => Project.find(3), :url => REPOSITORY_PATH) + end + + if File.directory?(REPOSITORY_PATH) + def test_show + get :show, :id => 3 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_not_nil assigns(:changesets) + end + + def test_browse_root + get :browse, :id => 3 + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entries) + assert_equal 2, assigns(:entries).size + assert assigns(:entries).detect {|e| e.name == 'directory' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'doc-mkdir.txt' && e.kind == 'file'} + end + + def test_browse_directory + get :browse, :id => 3, :path => ['directory'] + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entries) + assert_equal ['doc-ls.txt', 'document.txt', 'edit.png'], assigns(:entries).collect(&:name) + entry = assigns(:entries).detect {|e| e.name == 'edit.png'} + assert_not_nil entry + assert_equal 'file', entry.kind + assert_equal 'directory/edit.png', entry.path + end + + def test_browse_at_given_revision + get :browse, :id => 3, :path => [], :rev => 3 + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entries) + assert_equal ['directory', 'doc-deleted.txt', 'doc-ls.txt', 'doc-mkdir.txt'], assigns(:entries).collect(&:name) + end + + def test_changes + get :changes, :id => 3, :path => ['doc-mkdir.txt'] + assert_response :success + assert_template 'changes' + assert_tag :tag => 'h2', :content => 'doc-mkdir.txt' + end + + def test_entry_show + get :entry, :id => 3, :path => ['directory', 'doc-ls.txt'] + assert_response :success + assert_template 'entry' + # Line 19 + assert_tag :tag => 'th', + :content => /29/, + :attributes => { :class => /line-num/ }, + :sibling => { :tag => 'td', :content => /Show help message/ } + end + + def test_entry_download + get :entry, :id => 3, :path => ['directory', 'doc-ls.txt'], :format => 'raw' + assert_response :success + # File content + assert @response.body.include?('Show help message') + end + + def test_directory_entry + get :entry, :id => 3, :path => ['directory'] + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entry) + assert_equal 'directory', assigns(:entry).name + end + + def test_diff + # Full diff of changeset 3 + get :diff, :id => 3, :rev => 3 + assert_response :success + assert_template 'diff' + # Line 22 removed + assert_tag :tag => 'th', + :content => /2/, + :sibling => { :tag => 'td', + :attributes => { :class => /diff_in/ }, + :content => /Main purpose/ } + end + + def test_annotate + get :annotate, :id => 3, :path => ['doc-mkdir.txt'] + assert_response :success + assert_template 'annotate' + # Line 2, revision 3 + assert_tag :tag => 'th', :content => /2/, + :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /3/ } }, + :sibling => { :tag => 'td', :content => /jsmith/ }, + :sibling => { :tag => 'td', :content => /Main purpose/ } + end + else + puts "Bazaar test repository NOT FOUND. Skipping functional tests !!!" + def test_fake; assert true end + end +end diff --git a/test/functional/repositories_cvs_controller_test.rb b/test/functional/repositories_cvs_controller_test.rb index 1e101f59a..e12bb53ac 100644 --- a/test/functional/repositories_cvs_controller_test.rb +++ b/test/functional/repositories_cvs_controller_test.rb @@ -65,13 +65,24 @@ class RepositoriesCvsControllerTest < Test::Unit::TestCase end def test_browse_directory - get :browse, :id => 1, :path => ['sources'] + get :browse, :id => 1, :path => ['images'] assert_response :success assert_template 'browse' assert_not_nil assigns(:entries) - entry = assigns(:entries).detect {|e| e.name == 'watchers_controller.rb'} + assert_equal ['add.png', 'delete.png', 'edit.png'], assigns(:entries).collect(&:name) + entry = assigns(:entries).detect {|e| e.name == 'edit.png'} + assert_not_nil entry assert_equal 'file', entry.kind - assert_equal 'sources/watchers_controller.rb', entry.path + assert_equal 'images/edit.png', entry.path + end + + def test_browse_at_given_revision + Project.find(1).repository.fetch_changesets + get :browse, :id => 1, :path => ['images'], :rev => 1 + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entries) + assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name) end def test_entry @@ -90,6 +101,14 @@ class RepositoriesCvsControllerTest < Test::Unit::TestCase get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb'], :format => 'raw' assert_response :success end + + def test_directory_entry + get :entry, :id => 1, :path => ['sources'] + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entry) + assert_equal 'sources', assigns(:entry).name + end def test_diff Project.find(1).repository.fetch_changesets diff --git a/test/functional/repositories_darcs_controller_test.rb b/test/functional/repositories_darcs_controller_test.rb index fc77b8747..43c715924 100644 --- a/test/functional/repositories_darcs_controller_test.rb +++ b/test/functional/repositories_darcs_controller_test.rb @@ -60,13 +60,22 @@ class RepositoriesDarcsControllerTest < Test::Unit::TestCase assert_response :success assert_template 'browse' assert_not_nil assigns(:entries) - assert_equal 2, assigns(:entries).size + assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name) entry = assigns(:entries).detect {|e| e.name == 'edit.png'} assert_not_nil entry assert_equal 'file', entry.kind assert_equal 'images/edit.png', entry.path end + def test_browse_at_given_revision + Project.find(3).repository.fetch_changesets + get :browse, :id => 3, :path => ['images'], :rev => 1 + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entries) + assert_equal ['delete.png'], assigns(:entries).collect(&:name) + end + def test_changes get :changes, :id => 3, :path => ['images', 'edit.png'] assert_response :success diff --git a/test/functional/repositories_git_controller_test.rb b/test/functional/repositories_git_controller_test.rb index f8b3cb2bb..339e22897 100644 --- a/test/functional/repositories_git_controller_test.rb +++ b/test/functional/repositories_git_controller_test.rb @@ -1,5 +1,5 @@ # redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Copyright (C) 2006-2008 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -61,13 +61,21 @@ class RepositoriesGitControllerTest < Test::Unit::TestCase assert_response :success assert_template 'browse' assert_not_nil assigns(:entries) - assert_equal 2, assigns(:entries).size + assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name) entry = assigns(:entries).detect {|e| e.name == 'edit.png'} assert_not_nil entry assert_equal 'file', entry.kind assert_equal 'images/edit.png', entry.path end + def test_browse_at_given_revision + get :browse, :id => 3, :path => ['images'], :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518' + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entries) + assert_equal ['delete.png'], assigns(:entries).collect(&:name) + end + def test_changes get :changes, :id => 3, :path => ['images', 'edit.png'] assert_response :success @@ -93,6 +101,14 @@ class RepositoriesGitControllerTest < Test::Unit::TestCase assert @response.body.include?('WITHOUT ANY WARRANTY') end + def test_directory_entry + get :entry, :id => 3, :path => ['sources'] + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entry) + assert_equal 'sources', assigns(:entry).name + end + def test_diff # Full diff of changeset 2f9c0091 get :diff, :id => 3, :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' diff --git a/test/functional/repositories_mercurial_controller_test.rb b/test/functional/repositories_mercurial_controller_test.rb index 736e38c83..cb870aa32 100644 --- a/test/functional/repositories_mercurial_controller_test.rb +++ b/test/functional/repositories_mercurial_controller_test.rb @@ -1,5 +1,5 @@ # redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Copyright (C) 2006-2008 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -60,13 +60,21 @@ class RepositoriesMercurialControllerTest < Test::Unit::TestCase assert_response :success assert_template 'browse' assert_not_nil assigns(:entries) - assert_equal 2, assigns(:entries).size + assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name) entry = assigns(:entries).detect {|e| e.name == 'edit.png'} assert_not_nil entry assert_equal 'file', entry.kind assert_equal 'images/edit.png', entry.path end + def test_browse_at_given_revision + get :browse, :id => 3, :path => ['images'], :rev => 0 + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entries) + assert_equal ['delete.png'], assigns(:entries).collect(&:name) + end + def test_changes get :changes, :id => 3, :path => ['images', 'edit.png'] assert_response :success @@ -91,7 +99,15 @@ class RepositoriesMercurialControllerTest < Test::Unit::TestCase # File content assert @response.body.include?('WITHOUT ANY WARRANTY') end - + + def test_directory_entry + get :entry, :id => 3, :path => ['sources'] + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entry) + assert_equal 'sources', assigns(:entry).name + end + def test_diff # Full diff of changeset 4 get :diff, :id => 3, :rev => 4 diff --git a/test/functional/repositories_subversion_controller_test.rb b/test/functional/repositories_subversion_controller_test.rb index 9b21a13e8..dd56947fc 100644 --- a/test/functional/repositories_subversion_controller_test.rb +++ b/test/functional/repositories_subversion_controller_test.rb @@ -1,5 +1,5 @@ # redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Copyright (C) 2006-2008 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -58,11 +58,20 @@ class RepositoriesSubversionControllerTest < Test::Unit::TestCase assert_response :success assert_template 'browse' assert_not_nil assigns(:entries) + assert_equal ['folder', '.project', 'helloworld.c', 'textfile.txt'], assigns(:entries).collect(&:name) entry = assigns(:entries).detect {|e| e.name == 'helloworld.c'} assert_equal 'file', entry.kind assert_equal 'subversion_test/helloworld.c', entry.path end - + + def test_browse_at_given_revision + get :browse, :id => 1, :path => ['subversion_test'], :rev => 4 + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entries) + assert_equal ['folder', '.project', 'helloworld.c', 'helloworld.rb', 'textfile.txt'], assigns(:entries).collect(&:name) + end + def test_entry get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c'] assert_response :success @@ -80,6 +89,14 @@ class RepositoriesSubversionControllerTest < Test::Unit::TestCase assert_response :success end + def test_directory_entry + get :entry, :id => 1, :path => ['subversion_test', 'folder'] + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entry) + assert_equal 'folder', assigns(:entry).name + end + def test_diff get :diff, :id => 1, :rev => 3 assert_response :success diff --git a/test/functional/timelog_controller_test.rb b/test/functional/timelog_controller_test.rb index fa4432295..e80a67728 100644 --- a/test/functional/timelog_controller_test.rb +++ b/test/functional/timelog_controller_test.rb @@ -22,7 +22,7 @@ require 'timelog_controller' class TimelogController; def rescue_action(e) raise e end; end class TimelogControllerTest < Test::Unit::TestCase - fixtures :projects, :roles, :members, :issues, :time_entries, :users, :trackers, :enumerations, :issue_statuses + fixtures :projects, :enabled_modules, :roles, :members, :issues, :time_entries, :users, :trackers, :enumerations, :issue_statuses, :custom_fields, :custom_values def setup @controller = TimelogController.new @@ -78,30 +78,75 @@ class TimelogControllerTest < Test::Unit::TestCase assert_response :success assert_template 'report' end + + def test_report_all_time + get :report, :project_id => 1, :criterias => ['project', 'issue'] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:total_hours) + assert_equal "162.90", "%.2f" % assigns(:total_hours) + end + + def test_report_all_time_by_day + get :report, :project_id => 1, :criterias => ['project', 'issue'], :columns => 'day' + assert_response :success + assert_template 'report' + assert_not_nil assigns(:total_hours) + assert_equal "162.90", "%.2f" % assigns(:total_hours) + assert_tag :tag => 'th', :content => '2007-03-12' + end def test_report_one_criteria - get :report, :project_id => 1, :period => 'week', :date_from => "2007-04-01", :date_to => "2007-04-30", :criterias => ['project'] + get :report, :project_id => 1, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criterias => ['project'] assert_response :success assert_template 'report' assert_not_nil assigns(:total_hours) assert_equal "8.65", "%.2f" % assigns(:total_hours) - end + end def test_report_two_criterias - get :report, :project_id => 1, :period => 'month', :date_from => "2007-01-01", :date_to => "2007-12-31", :criterias => ["member", "activity"] + get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criterias => ["member", "activity"] assert_response :success assert_template 'report' assert_not_nil assigns(:total_hours) assert_equal "162.90", "%.2f" % assigns(:total_hours) end + def test_report_custom_field_criteria + get :report, :project_id => 1, :criterias => ['project', 'cf_1'] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:total_hours) + assert_not_nil assigns(:criterias) + assert_equal 2, assigns(:criterias).size + assert_equal "162.90", "%.2f" % assigns(:total_hours) + # Custom field column + assert_tag :tag => 'th', :content => 'Database' + # Custom field row + assert_tag :tag => 'td', :content => 'MySQL', + :sibling => { :tag => 'td', :attributes => { :class => 'hours' }, + :child => { :tag => 'span', :attributes => { :class => 'hours hours-int' }, + :content => '1' }} + end + def test_report_one_criteria_no_result - get :report, :project_id => 1, :period => 'week', :date_from => "1998-04-01", :date_to => "1998-04-30", :criterias => ['project'] + get :report, :project_id => 1, :columns => 'week', :from => "1998-04-01", :to => "1998-04-30", :criterias => ['project'] assert_response :success assert_template 'report' assert_not_nil assigns(:total_hours) assert_equal "0.00", "%.2f" % assigns(:total_hours) - end + end + + def test_report_csv_export + get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", :criterias => ["project", "member", "activity"], :format => "csv" + assert_response :success + assert_equal 'text/csv', @response.content_type + lines = @response.body.chomp.split("\n") + # Headers + assert_equal 'Project,Member,Activity,2007-1,2007-2,2007-3,2007-4,2007-5,2007-6,Total', lines.first + # Total row + assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last + end def test_details_at_project_level get :details, :project_id => 1 @@ -114,8 +159,8 @@ class TimelogControllerTest < Test::Unit::TestCase assert_not_nil assigns(:total_hours) assert_equal "162.90", "%.2f" % assigns(:total_hours) # display all time by default - assert_nil assigns(:from) - assert_nil assigns(:to) + assert_equal '2007-03-11'.to_date, assigns(:from) + assert_equal '2007-04-22'.to_date, assigns(:to) end def test_details_at_project_level_with_date_range @@ -149,8 +194,8 @@ class TimelogControllerTest < Test::Unit::TestCase assert_not_nil assigns(:total_hours) assert_equal 154.25, assigns(:total_hours) # display all time by default - assert_nil assigns(:from) - assert_nil assigns(:to) + assert_equal '2007-03-11'.to_date, assigns(:from) + assert_equal '2007-04-22'.to_date, assigns(:to) end def test_details_csv_export @@ -158,6 +203,6 @@ class TimelogControllerTest < Test::Unit::TestCase assert_response :success assert_equal 'text/csv', @response.content_type assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment\n") - assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,2,Feature request,Add ingredients categories,1.0,\"\"\n") + assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\"\n") end end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 0418a02b6..bf31e6614 100644 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -22,7 +22,7 @@ require 'wiki_controller' class WikiController; def rescue_action(e) raise e end; end class WikiControllerTest < Test::Unit::TestCase - fixtures :projects, :users, :roles, :members, :enabled_modules, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions + fixtures :projects, :users, :roles, :members, :enabled_modules, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, :attachments def setup @controller = WikiController.new @@ -43,6 +43,10 @@ class WikiControllerTest < Test::Unit::TestCase assert_response :success assert_template 'show' assert_tag :tag => 'h1', :content => /Another page/ + # Included page with an inline image + assert_tag :tag => 'p', :content => /This is an inline image/ + assert_tag :tag => 'img', :attributes => { :src => '/attachments/download/3', + :alt => 'This is a logo' } end def test_show_unexistent_page_without_edit_right @@ -147,7 +151,7 @@ class WikiControllerTest < Test::Unit::TestCase assert_template 'special_page_index' pages = assigns(:pages) assert_not_nil pages - assert_equal 2, pages.size + assert_equal Project.find(1).wiki.pages.size, pages.size assert_tag :tag => 'a', :attributes => { :href => '/wiki/ecookbook/CookBook_documentation' }, :content => /CookBook documentation/ end diff --git a/test/unit/changeset_test.rb b/test/unit/changeset_test.rb index 2442a8b8c..bbfe6952d 100644 --- a/test/unit/changeset_test.rb +++ b/test/unit/changeset_test.rb @@ -41,22 +41,22 @@ class ChangesetTest < Test::Unit::TestCase end def test_previous - changeset = Changeset.find_by_revision(3) - assert_equal Changeset.find_by_revision(2), changeset.previous + changeset = Changeset.find_by_revision('3') + assert_equal Changeset.find_by_revision('2'), changeset.previous end def test_previous_nil - changeset = Changeset.find_by_revision(1) + changeset = Changeset.find_by_revision('1') assert_nil changeset.previous end def test_next - changeset = Changeset.find_by_revision(2) - assert_equal Changeset.find_by_revision(3), changeset.next + changeset = Changeset.find_by_revision('2') + assert_equal Changeset.find_by_revision('3'), changeset.next end def test_next_nil - changeset = Changeset.find_by_revision(4) + changeset = Changeset.find_by_revision('4') assert_nil changeset.next end end diff --git a/test/unit/helpers/application_helper_test.rb b/test/unit/helpers/application_helper_test.rb index 66499c003..fa2109131 100644 --- a/test/unit/helpers/application_helper_test.rb +++ b/test/unit/helpers/application_helper_test.rb @@ -20,7 +20,7 @@ require File.dirname(__FILE__) + '/../../test_helper' class ApplicationHelperTest < HelperTestCase include ApplicationHelper include ActionView::Helpers::TextHelper - fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents + fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents, :roles, :enabled_modules def setup super @@ -134,8 +134,9 @@ class ApplicationHelperTest < HelperTestCase def test_html_tags to_test = { - "<div>content</div>" => "<p><div>content</div></p>", - "<script>some script;</script>" => "<p><script>some script;</script></p>", + "<div>content</div>" => "<p><div>content</div></p>", + "<div class=\"bold\">content</div>" => "<p><div class=\"bold\">content</div></p>", + "<script>some script;</script>" => "<p><script>some script;</script></p>", # do not escape pre/code tags "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>", "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>", @@ -167,6 +168,24 @@ class ApplicationHelperTest < HelperTestCase assert_equal '<p>{{hello_world}}</p>', textilizable(text) end + def test_macro_include + @project = Project.find(1) + # include a page of the current project wiki + text = "{{include(Another page)}}" + assert textilizable(text).match(/This is a link to a ticket/) + + @project = nil + # include a page of a specific project wiki + text = "{{include(ecookbook:Another page)}}" + assert textilizable(text).match(/This is a link to a ticket/) + + text = "{{include(ecookbook:)}}" + assert textilizable(text).match(/CookBook documentation/) + + text = "{{include(unknowidentifier:somepage)}}" + assert textilizable(text).match(/Unknow project/) + end + def test_date_format_default today = Date.today Setting.date_format = '' diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index 7712b764e..36ba1fb45 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -20,6 +20,13 @@ require File.dirname(__FILE__) + '/../test_helper' class IssueTest < Test::Unit::TestCase fixtures :projects, :users, :members, :trackers, :projects_trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :time_entries + def test_create + issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30') + assert issue.save + issue.reload + assert_equal 1.5, issue.estimated_hours + end + def test_category_based_assignment issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1) assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to @@ -48,6 +55,8 @@ class IssueTest < Test::Unit::TestCase IssueRelation.create(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES) # And 3 is a dupe of 2 IssueRelation.create(:issue_from => issue2, :issue_to => issue3, :relation_type => IssueRelation::TYPE_DUPLICATES) + # And 3 is a dupe of 1 (circular duplicates) + IssueRelation.create(:issue_from => issue1, :issue_to => issue3, :relation_type => IssueRelation::TYPE_DUPLICATES) assert issue1.reload.duplicates.include?(issue2) diff --git a/test/unit/project_test.rb b/test/unit/project_test.rb index f7da6ecb5..9af68c231 100644 --- a/test/unit/project_test.rb +++ b/test/unit/project_test.rb @@ -18,7 +18,7 @@ require File.dirname(__FILE__) + '/../test_helper'
class ProjectTest < Test::Unit::TestCase
- fixtures :projects, :issues, :issue_statuses, :journals, :journal_details, :users, :members, :roles, :projects_trackers, :trackers
+ fixtures :projects, :issues, :issue_statuses, :journals, :journal_details, :users, :members, :roles, :projects_trackers, :trackers, :boards
def setup
@ecookbook = Project.find(1)
@@ -84,12 +84,15 @@ class ProjectTest < Test::Unit::TestCase assert_equal 2, @ecookbook.members.size
# and 1 is locked
assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
+ # some boards
+ assert @ecookbook.boards.any?
@ecookbook.destroy
# make sure that the project non longer exists
assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
- # make sure all members have been removed
- assert_equal 0, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
+ # make sure related data was removed
+ assert Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
+ assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
end
def test_subproject_ok
diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb index c00f47e5d..d291018fb 100644 --- a/test/unit/query_test.rb +++ b/test/unit/query_test.rb @@ -1,5 +1,5 @@ # redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Copyright (C) 2006-2008 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,12 +18,12 @@ require File.dirname(__FILE__) + '/../test_helper' class QueryTest < Test::Unit::TestCase - fixtures :projects, :users, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :queries + fixtures :projects, :users, :members, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :queries def test_query_with_multiple_custom_fields query = Query.find(1) assert query.valid? - assert query.statement.include?("custom_values.value IN ('MySQL')") + assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')") issues = Issue.find :all,:include => [ :assigned_to, :status, :tracker, :project, :priority ], :conditions => query.statement assert_equal 1, issues.length assert_equal Issue.find(3), issues.first @@ -41,4 +41,34 @@ class QueryTest < Test::Unit::TestCase c = q.columns.first assert q.has_column?(c) end + + def test_editable_by + admin = User.find(1) + manager = User.find(2) + developer = User.find(3) + + # Public query on project 1 + q = Query.find(1) + assert q.editable_by?(admin) + assert q.editable_by?(manager) + assert !q.editable_by?(developer) + + # Private query on project 1 + q = Query.find(2) + assert q.editable_by?(admin) + assert !q.editable_by?(manager) + assert q.editable_by?(developer) + + # Private query for all projects + q = Query.find(3) + assert q.editable_by?(admin) + assert !q.editable_by?(manager) + assert q.editable_by?(developer) + + # Public query for all projects + q = Query.find(4) + assert q.editable_by?(admin) + assert !q.editable_by?(manager) + assert !q.editable_by?(developer) + end end diff --git a/test/unit/repository_bazaar_test.rb b/test/unit/repository_bazaar_test.rb index 15fcc8672..b7a3cf98e 100644 --- a/test/unit/repository_bazaar_test.rb +++ b/test/unit/repository_bazaar_test.rb @@ -36,13 +36,13 @@ class RepositoryBazaarTest < Test::Unit::TestCase assert_equal 4, @repository.changesets.count assert_equal 9, @repository.changes.count - assert_equal 'Initial import', @repository.changesets.find_by_revision(1).comments + assert_equal 'Initial import', @repository.changesets.find_by_revision('1').comments end def test_fetch_changesets_incremental @repository.fetch_changesets # Remove changesets with revision > 5 - @repository.changesets.find(:all, :conditions => 'revision > 2').each(&:destroy) + @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 2} @repository.reload assert_equal 2, @repository.changesets.count diff --git a/test/unit/repository_darcs_test.rb b/test/unit/repository_darcs_test.rb index 1228976f1..1c8c1b8dd 100644 --- a/test/unit/repository_darcs_test.rb +++ b/test/unit/repository_darcs_test.rb @@ -35,13 +35,13 @@ class RepositoryDarcsTest < Test::Unit::TestCase assert_equal 6, @repository.changesets.count assert_equal 13, @repository.changes.count - assert_equal "Initial commit.", @repository.changesets.find_by_revision(1).comments + assert_equal "Initial commit.", @repository.changesets.find_by_revision('1').comments end def test_fetch_changesets_incremental @repository.fetch_changesets # Remove changesets with revision > 3 - @repository.changesets.find(:all, :conditions => 'revision > 3').each(&:destroy) + @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 3} @repository.reload assert_equal 3, @repository.changesets.count diff --git a/test/unit/repository_mercurial_test.rb b/test/unit/repository_mercurial_test.rb index e6cfdf9b2..21ddf1e3a 100644 --- a/test/unit/repository_mercurial_test.rb +++ b/test/unit/repository_mercurial_test.rb @@ -35,13 +35,13 @@ class RepositoryMercurialTest < Test::Unit::TestCase assert_equal 6, @repository.changesets.count assert_equal 11, @repository.changes.count - assert_equal "Initial import.\nThe repository contains 3 files.", @repository.changesets.find_by_revision(0).comments + assert_equal "Initial import.\nThe repository contains 3 files.", @repository.changesets.find_by_revision('0').comments end def test_fetch_changesets_incremental @repository.fetch_changesets # Remove changesets with revision > 2 - @repository.changesets.find(:all, :conditions => 'revision > 2').each(&:destroy) + @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 2} @repository.reload assert_equal 3, @repository.changesets.count diff --git a/test/unit/repository_subversion_test.rb b/test/unit/repository_subversion_test.rb index 879feece8..7a1c9df4a 100644 --- a/test/unit/repository_subversion_test.rb +++ b/test/unit/repository_subversion_test.rb @@ -35,13 +35,13 @@ class RepositorySubversionTest < Test::Unit::TestCase assert_equal 8, @repository.changesets.count assert_equal 16, @repository.changes.count - assert_equal 'Initial import.', @repository.changesets.find_by_revision(1).comments + assert_equal 'Initial import.', @repository.changesets.find_by_revision('1').comments end def test_fetch_changesets_incremental @repository.fetch_changesets # Remove changesets with revision > 5 - @repository.changesets.find(:all, :conditions => 'revision > 5').each(&:destroy) + @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 5} @repository.reload assert_equal 5, @repository.changesets.count diff --git a/test/unit/time_entry_test.rb b/test/unit/time_entry_test.rb new file mode 100644 index 000000000..f86e42eab --- /dev/null +++ b/test/unit/time_entry_test.rb @@ -0,0 +1,46 @@ +# redMine - project management software +# Copyright (C) 2006-2008 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.dirname(__FILE__) + '/../test_helper' + +class TimeEntryTest < Test::Unit::TestCase + fixtures :issues, :projects, :users, :time_entries + + def test_hours_format + assertions = { "2" => 2.0, + "21.1" => 21.1, + "2,1" => 2.1, + "7:12" => 7.2, + "10h" => 10.0, + "10 h" => 10.0, + "45m" => 0.75, + "45 m" => 0.75, + "3h15" => 3.25, + "3h 15" => 3.25, + "3 h 15" => 3.25, + "3 h 15m" => 3.25, + "3 h 15 m" => 3.25, + "3 hours" => 3.0, + "12min" => 0.2, + } + + assertions.each do |k, v| + t = TimeEntry.new(:hours => k) + assert_equal v, t.hours + end + end +end diff --git a/vendor/plugins/acts_as_event/lib/acts_as_event.rb b/vendor/plugins/acts_as_event/lib/acts_as_event.rb index a0d1822ad..d7f437a5e 100644 --- a/vendor/plugins/acts_as_event/lib/acts_as_event.rb +++ b/vendor/plugins/acts_as_event/lib/acts_as_event.rb @@ -25,11 +25,12 @@ module Redmine module ClassMethods def acts_as_event(options = {}) return if self.included_modules.include?(Redmine::Acts::Event::InstanceMethods) - options[:datetime] ||= 'created_on' - options[:title] ||= 'title' - options[:description] ||= 'description' - options[:author] ||= 'author' + options[:datetime] ||= :created_on + options[:title] ||= :title + options[:description] ||= :description + options[:author] ||= :author options[:url] ||= {:controller => 'welcome'} + options[:type] ||= self.name.underscore.dasherize cattr_accessor :event_options self.event_options = options send :include, Redmine::Acts::Event::InstanceMethods @@ -41,11 +42,17 @@ module Redmine base.extend ClassMethods end - %w(datetime title description author).each do |attr| + %w(datetime title description author type).each do |attr| src = <<-END_SRC def event_#{attr} option = event_options[:#{attr}] - option.is_a?(Proc) ? option.call(self) : send(option) + if option.is_a?(Proc) + option.call(self) + elsif option.is_a?(Symbol) + send(option) + else + option + end end END_SRC class_eval src, __FILE__, __LINE__ |