##// END OF EJS Templates
Merged Rails 2.2 branch. Redmine now requires Rails 2.2.2....
Jean-Philippe Lang -
r2430:fe28193e4eb9
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,234 +1,241
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'uri'
19 19 require 'cgi'
20 20
21 21 class ApplicationController < ActionController::Base
22 include Redmine::I18n
23
24 # In case the cookie store secret changes
25 rescue_from CGI::Session::CookieStore::TamperedWithCookie do |exception|
26 render :text => 'Your session was invalid and has been reset. Please, reload this page.', :status => 500
27 end
28
22 29 layout 'base'
23 30
24 31 before_filter :user_setup, :check_if_login_required, :set_localization
25 32 filter_parameter_logging :password
26 33
27 34 include Redmine::MenuManager::MenuController
28 35 helper Redmine::MenuManager::MenuHelper
29 36
30 37 REDMINE_SUPPORTED_SCM.each do |scm|
31 38 require_dependency "repository/#{scm.underscore}"
32 39 end
33 40
34 41 def current_role
35 42 @current_role ||= User.current.role_for_project(@project)
36 43 end
37 44
38 45 def user_setup
39 46 # Check the settings cache for each request
40 47 Setting.check_cache
41 48 # Find the current user
42 49 User.current = find_current_user
43 50 end
44 51
45 52 # Returns the current user or nil if no user is logged in
46 53 def find_current_user
47 54 if session[:user_id]
48 55 # existing session
49 56 (User.active.find(session[:user_id]) rescue nil)
50 57 elsif cookies[:autologin] && Setting.autologin?
51 58 # auto-login feature
52 59 User.find_by_autologin_key(cookies[:autologin])
53 60 elsif params[:key] && accept_key_auth_actions.include?(params[:action])
54 61 # RSS key authentication
55 62 User.find_by_rss_key(params[:key])
56 63 end
57 64 end
58 65
59 66 # check if login is globally required to access the application
60 67 def check_if_login_required
61 68 # no check needed if user is already logged in
62 69 return true if User.current.logged?
63 70 require_login if Setting.login_required?
64 71 end
65 72
66 73 def set_localization
67 User.current.language = nil unless User.current.logged?
68 lang = begin
69 if !User.current.language.blank? && GLoc.valid_language?(User.current.language)
70 User.current.language
71 elsif request.env['HTTP_ACCEPT_LANGUAGE']
72 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
73 if !accept_lang.blank? && (GLoc.valid_language?(accept_lang) || GLoc.valid_language?(accept_lang = accept_lang.split('-').first))
74 User.current.language = accept_lang
75 end
74 lang = nil
75 if User.current.logged?
76 lang = find_language(User.current.language)
77 end
78 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
79 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
80 if !accept_lang.blank?
81 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
76 82 end
77 rescue
78 nil
79 end || Setting.default_language
80 set_language_if_valid(lang)
83 end
84 lang ||= Setting.default_language
85 set_language_if_valid(lang)
81 86 end
82 87
83 88 def require_login
84 89 if !User.current.logged?
85 90 redirect_to :controller => "account", :action => "login", :back_url => url_for(params)
86 91 return false
87 92 end
88 93 true
89 94 end
90 95
91 96 def require_admin
92 97 return unless require_login
93 98 if !User.current.admin?
94 99 render_403
95 100 return false
96 101 end
97 102 true
98 103 end
99 104
100 105 def deny_access
101 106 User.current.logged? ? render_403 : require_login
102 107 end
103 108
104 109 # Authorize the user for the requested action
105 110 def authorize(ctrl = params[:controller], action = params[:action])
106 111 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project)
107 112 allowed ? true : deny_access
108 113 end
109 114
110 115 # make sure that the user is a member of the project (or admin) if project is private
111 116 # used as a before_filter for actions that do not require any particular permission on the project
112 117 def check_project_privacy
113 118 if @project && @project.active?
114 119 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
115 120 true
116 121 else
117 122 User.current.logged? ? render_403 : require_login
118 123 end
119 124 else
120 125 @project = nil
121 126 render_404
122 127 false
123 128 end
124 129 end
125 130
126 131 def redirect_back_or_default(default)
127 132 back_url = CGI.unescape(params[:back_url].to_s)
128 133 if !back_url.blank?
129 134 begin
130 135 uri = URI.parse(back_url)
131 136 # do not redirect user to another host or to the login or register page
132 137 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
133 138 redirect_to(back_url) and return
134 139 end
135 140 rescue URI::InvalidURIError
136 141 # redirect to default
137 142 end
138 143 end
139 144 redirect_to default
140 145 end
141 146
142 147 def render_403
143 148 @project = nil
144 149 render :template => "common/403", :layout => !request.xhr?, :status => 403
145 150 return false
146 151 end
147 152
148 153 def render_404
149 154 render :template => "common/404", :layout => !request.xhr?, :status => 404
150 155 return false
151 156 end
152 157
153 158 def render_error(msg)
154 159 flash.now[:error] = msg
155 render :nothing => true, :layout => !request.xhr?, :status => 500
160 render :text => '', :layout => !request.xhr?, :status => 500
156 161 end
157 162
158 163 def render_feed(items, options={})
159 164 @items = items || []
160 165 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
161 166 @items = @items.slice(0, Setting.feeds_limit.to_i)
162 167 @title = options[:title] || Setting.app_title
163 168 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
164 169 end
165 170
166 171 def self.accept_key_auth(*actions)
167 172 actions = actions.flatten.map(&:to_s)
168 173 write_inheritable_attribute('accept_key_auth_actions', actions)
169 174 end
170 175
171 176 def accept_key_auth_actions
172 177 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
173 178 end
174 179
175 180 # TODO: move to model
176 181 def attach_files(obj, attachments)
177 182 attached = []
178 183 unsaved = []
179 184 if attachments && attachments.is_a?(Hash)
180 185 attachments.each_value do |attachment|
181 186 file = attachment['file']
182 187 next unless file && file.size > 0
183 188 a = Attachment.create(:container => obj,
184 189 :file => file,
185 190 :description => attachment['description'].to_s.strip,
186 191 :author => User.current)
187 192 a.new_record? ? (unsaved << a) : (attached << a)
188 193 end
189 194 if unsaved.any?
190 195 flash[:warning] = l(:warning_attachments_not_saved, unsaved.size)
191 196 end
192 197 end
193 198 attached
194 199 end
195 200
196 201 # Returns the number of objects that should be displayed
197 202 # on the paginated list
198 203 def per_page_option
199 204 per_page = nil
200 205 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
201 206 per_page = params[:per_page].to_s.to_i
202 207 session[:per_page] = per_page
203 208 elsif session[:per_page]
204 209 per_page = session[:per_page]
205 210 else
206 211 per_page = Setting.per_page_options_array.first || 25
207 212 end
208 213 per_page
209 214 end
210 215
211 216 # qvalues http header parser
212 217 # code taken from webrick
213 218 def parse_qvalues(value)
214 219 tmp = []
215 220 if value
216 221 parts = value.split(/,\s*/)
217 222 parts.each {|part|
218 223 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
219 224 val = m[1]
220 225 q = (m[2] or 1).to_f
221 226 tmp.push([val, q])
222 227 end
223 228 }
224 229 tmp = tmp.sort_by{|val, q| -q}
225 230 tmp.collect!{|val, q| val}
226 231 end
227 232 return tmp
233 rescue
234 nil
228 235 end
229 236
230 237 # Returns a string that can be used as filename value in Content-Disposition header
231 238 def filename_for_content_disposition(name)
232 239 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
233 240 end
234 241 end
@@ -1,492 +1,496
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class IssuesController < ApplicationController
19 19 menu_item :new_issue, :only => :new
20 20
21 21 before_filter :find_issue, :only => [:show, :edit, :reply]
22 22 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
23 23 before_filter :find_project, :only => [:new, :update_form, :preview]
24 24 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
25 25 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
26 26 accept_key_auth :index, :changes
27 27
28 28 helper :journals
29 29 helper :projects
30 30 include ProjectsHelper
31 31 helper :custom_fields
32 32 include CustomFieldsHelper
33 33 helper :issue_relations
34 34 include IssueRelationsHelper
35 35 helper :watchers
36 36 include WatchersHelper
37 37 helper :attachments
38 38 include AttachmentsHelper
39 39 helper :queries
40 40 helper :sort
41 41 include SortHelper
42 42 include IssuesHelper
43 43 helper :timelog
44 44 include Redmine::Export::PDF
45 45
46 46 def index
47 47 retrieve_query
48 48 sort_init 'id', 'desc'
49 49 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
50 50
51 51 if @query.valid?
52 52 limit = per_page_option
53 53 respond_to do |format|
54 54 format.html { }
55 55 format.atom { }
56 56 format.csv { limit = Setting.issues_export_limit.to_i }
57 57 format.pdf { limit = Setting.issues_export_limit.to_i }
58 58 end
59 59 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
60 60 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
61 61 @issues = Issue.find :all, :order => sort_clause,
62 62 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
63 63 :conditions => @query.statement,
64 64 :limit => limit,
65 65 :offset => @issue_pages.current.offset
66 66 respond_to do |format|
67 67 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
68 68 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
69 69 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
70 70 format.pdf { send_data(issues_to_pdf(@issues, @project), :type => 'application/pdf', :filename => 'export.pdf') }
71 71 end
72 72 else
73 73 # Send html if the query is not valid
74 74 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
75 75 end
76 76 rescue ActiveRecord::RecordNotFound
77 77 render_404
78 78 end
79 79
80 80 def changes
81 81 retrieve_query
82 82 sort_init 'id', 'desc'
83 83 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
84 84
85 85 if @query.valid?
86 86 @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
87 87 :conditions => @query.statement,
88 88 :limit => 25,
89 89 :order => "#{Journal.table_name}.created_on DESC"
90 90 end
91 91 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
92 92 render :layout => false, :content_type => 'application/atom+xml'
93 93 rescue ActiveRecord::RecordNotFound
94 94 render_404
95 95 end
96 96
97 97 def show
98 98 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
99 99 @journals.each_with_index {|j,i| j.indice = i+1}
100 100 @journals.reverse! if User.current.wants_comments_in_reverse_order?
101 101 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
102 102 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
103 103 @priorities = Enumeration.priorities
104 104 @time_entry = TimeEntry.new
105 105 respond_to do |format|
106 106 format.html { render :template => 'issues/show.rhtml' }
107 107 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
108 108 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
109 109 end
110 110 end
111 111
112 112 # Add a new issue
113 113 # The new issue will be created from an existing one if copy_from parameter is given
114 114 def new
115 115 @issue = Issue.new
116 116 @issue.copy_from(params[:copy_from]) if params[:copy_from]
117 117 @issue.project = @project
118 118 # Tracker must be set before custom field values
119 119 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
120 120 if @issue.tracker.nil?
121 121 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
122 122 render :nothing => true, :layout => true
123 123 return
124 124 end
125 125 if params[:issue].is_a?(Hash)
126 126 @issue.attributes = params[:issue]
127 127 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
128 128 end
129 129 @issue.author = User.current
130 130
131 131 default_status = IssueStatus.default
132 132 unless default_status
133 133 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
134 134 render :nothing => true, :layout => true
135 135 return
136 136 end
137 137 @issue.status = default_status
138 138 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
139 139
140 140 if request.get? || request.xhr?
141 141 @issue.start_date ||= Date.today
142 142 else
143 143 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
144 144 # Check that the user is allowed to apply the requested status
145 145 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
146 146 if @issue.save
147 147 attach_files(@issue, params[:attachments])
148 148 flash[:notice] = l(:notice_successful_create)
149 149 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
150 150 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
151 151 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
152 152 { :action => 'show', :id => @issue })
153 153 return
154 154 end
155 155 end
156 156 @priorities = Enumeration.priorities
157 157 render :layout => !request.xhr?
158 158 end
159 159
160 160 # Attributes that can be updated on workflow transition (without :edit permission)
161 161 # TODO: make it configurable (at least per role)
162 162 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
163 163
164 164 def edit
165 165 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
166 166 @priorities = Enumeration.priorities
167 167 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
168 168 @time_entry = TimeEntry.new
169 169
170 170 @notes = params[:notes]
171 171 journal = @issue.init_journal(User.current, @notes)
172 172 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
173 173 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
174 174 attrs = params[:issue].dup
175 175 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
176 176 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
177 177 @issue.attributes = attrs
178 178 end
179 179
180 180 if request.post?
181 181 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
182 182 @time_entry.attributes = params[:time_entry]
183 183 attachments = attach_files(@issue, params[:attachments])
184 184 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
185 185
186 186 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
187 187
188 188 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
189 189 # Log spend time
190 190 if User.current.allowed_to?(:log_time, @project)
191 191 @time_entry.save
192 192 end
193 193 if !journal.new_record?
194 194 # Only send notification if something was actually changed
195 195 flash[:notice] = l(:notice_successful_update)
196 196 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
197 197 end
198 198 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
199 199 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
200 200 end
201 201 end
202 202 rescue ActiveRecord::StaleObjectError
203 203 # Optimistic locking exception
204 204 flash.now[:error] = l(:notice_locking_conflict)
205 205 end
206 206
207 207 def reply
208 208 journal = Journal.find(params[:journal_id]) if params[:journal_id]
209 209 if journal
210 210 user = journal.user
211 211 text = journal.notes
212 212 else
213 213 user = @issue.author
214 214 text = @issue.description
215 215 end
216 216 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
217 217 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
218 218 render(:update) { |page|
219 219 page.<< "$('notes').value = \"#{content}\";"
220 220 page.show 'update'
221 221 page << "Form.Element.focus('notes');"
222 222 page << "Element.scrollTo('update');"
223 223 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
224 224 }
225 225 end
226 226
227 227 # Bulk edit a set of issues
228 228 def bulk_edit
229 229 if request.post?
230 230 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
231 231 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
232 232 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
233 233 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
234 234 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
235 235 custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
236 236
237 237 unsaved_issue_ids = []
238 238 @issues.each do |issue|
239 239 journal = issue.init_journal(User.current, params[:notes])
240 240 issue.priority = priority if priority
241 241 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
242 242 issue.category = category if category || params[:category_id] == 'none'
243 243 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
244 244 issue.start_date = params[:start_date] unless params[:start_date].blank?
245 245 issue.due_date = params[:due_date] unless params[:due_date].blank?
246 246 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
247 247 issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
248 248 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
249 249 # Don't save any change to the issue if the user is not authorized to apply the requested status
250 250 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
251 251 # Send notification for each issue (if changed)
252 252 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
253 253 else
254 254 # Keep unsaved issue ids to display them in flash error
255 255 unsaved_issue_ids << issue.id
256 256 end
257 257 end
258 258 if unsaved_issue_ids.empty?
259 259 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
260 260 else
261 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
261 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
262 :total => @issues.size,
263 :ids => '#' + unsaved_issue_ids.join(', #'))
262 264 end
263 265 redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
264 266 return
265 267 end
266 268 # Find potential statuses the user could be allowed to switch issues to
267 269 @available_statuses = Workflow.find(:all, :include => :new_status,
268 270 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
269 271 @custom_fields = @project.issue_custom_fields.select {|f| f.field_format == 'list'}
270 272 end
271 273
272 274 def move
273 275 @allowed_projects = []
274 276 # find projects to which the user is allowed to move the issue
275 277 if User.current.admin?
276 278 # admin is allowed to move issues to any active (visible) project
277 279 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
278 280 else
279 281 User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
280 282 end
281 283 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
282 284 @target_project ||= @project
283 285 @trackers = @target_project.trackers
284 286 if request.post?
285 287 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
286 288 unsaved_issue_ids = []
287 289 @issues.each do |issue|
288 290 issue.init_journal(User.current)
289 291 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker, params[:copy_options])
290 292 end
291 293 if unsaved_issue_ids.empty?
292 294 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
293 295 else
294 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
296 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
297 :total => @issues.size,
298 :ids => '#' + unsaved_issue_ids.join(', #'))
295 299 end
296 300 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
297 301 return
298 302 end
299 303 render :layout => false if request.xhr?
300 304 end
301 305
302 306 def destroy
303 307 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
304 308 if @hours > 0
305 309 case params[:todo]
306 310 when 'destroy'
307 311 # nothing to do
308 312 when 'nullify'
309 313 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
310 314 when 'reassign'
311 315 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
312 316 if reassign_to.nil?
313 317 flash.now[:error] = l(:error_issue_not_found_in_project)
314 318 return
315 319 else
316 320 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
317 321 end
318 322 else
319 323 # display the destroy form
320 324 return
321 325 end
322 326 end
323 327 @issues.each(&:destroy)
324 328 redirect_to :action => 'index', :project_id => @project
325 329 end
326 330
327 331 def gantt
328 332 @gantt = Redmine::Helpers::Gantt.new(params)
329 333 retrieve_query
330 334 if @query.valid?
331 335 events = []
332 336 # Issues that have start and due dates
333 337 events += Issue.find(:all,
334 338 :order => "start_date, due_date",
335 339 :include => [:tracker, :status, :assigned_to, :priority, :project],
336 340 :conditions => ["(#{@query.statement}) AND (((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)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
337 341 )
338 342 # Issues that don't have a due date but that are assigned to a version with a date
339 343 events += Issue.find(:all,
340 344 :order => "start_date, effective_date",
341 345 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
342 346 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
343 347 )
344 348 # Versions
345 349 events += Version.find(:all, :include => :project,
346 350 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
347 351
348 352 @gantt.events = events
349 353 end
350 354
351 355 respond_to do |format|
352 356 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
353 357 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.png") } if @gantt.respond_to?('to_image')
354 358 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
355 359 end
356 360 end
357 361
358 362 def calendar
359 363 if params[:year] and params[:year].to_i > 1900
360 364 @year = params[:year].to_i
361 365 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
362 366 @month = params[:month].to_i
363 367 end
364 368 end
365 369 @year ||= Date.today.year
366 370 @month ||= Date.today.month
367 371
368 372 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
369 373 retrieve_query
370 374 if @query.valid?
371 375 events = []
372 376 events += Issue.find(:all,
373 377 :include => [:tracker, :status, :assigned_to, :priority, :project],
374 378 :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
375 379 )
376 380 events += Version.find(:all, :include => :project,
377 381 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
378 382
379 383 @calendar.events = events
380 384 end
381 385
382 386 render :layout => false if request.xhr?
383 387 end
384 388
385 389 def context_menu
386 390 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
387 391 if (@issues.size == 1)
388 392 @issue = @issues.first
389 393 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
390 394 end
391 395 projects = @issues.collect(&:project).compact.uniq
392 396 @project = projects.first if projects.size == 1
393 397
394 398 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
395 399 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
396 400 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
397 401 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
398 402 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
399 403 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
400 404 }
401 405 if @project
402 406 @assignables = @project.assignable_users
403 407 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
404 408 end
405 409
406 410 @priorities = Enumeration.priorities.reverse
407 411 @statuses = IssueStatus.find(:all, :order => 'position')
408 412 @back = request.env['HTTP_REFERER']
409 413
410 414 render :layout => false
411 415 end
412 416
413 417 def update_form
414 418 @issue = Issue.new(params[:issue])
415 419 render :action => :new, :layout => false
416 420 end
417 421
418 422 def preview
419 423 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
420 424 @attachements = @issue.attachments if @issue
421 425 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
422 426 render :partial => 'common/preview'
423 427 end
424 428
425 429 private
426 430 def find_issue
427 431 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
428 432 @project = @issue.project
429 433 rescue ActiveRecord::RecordNotFound
430 434 render_404
431 435 end
432 436
433 437 # Filter for bulk operations
434 438 def find_issues
435 439 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
436 440 raise ActiveRecord::RecordNotFound if @issues.empty?
437 441 projects = @issues.collect(&:project).compact.uniq
438 442 if projects.size == 1
439 443 @project = projects.first
440 444 else
441 445 # TODO: let users bulk edit/move/destroy issues from different projects
442 446 render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
443 447 end
444 448 rescue ActiveRecord::RecordNotFound
445 449 render_404
446 450 end
447 451
448 452 def find_project
449 453 @project = Project.find(params[:project_id])
450 454 rescue ActiveRecord::RecordNotFound
451 455 render_404
452 456 end
453 457
454 458 def find_optional_project
455 459 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
456 460 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
457 461 allowed ? true : deny_access
458 462 rescue ActiveRecord::RecordNotFound
459 463 render_404
460 464 end
461 465
462 466 # Retrieve query from session or build a new query
463 467 def retrieve_query
464 468 if !params[:query_id].blank?
465 469 cond = "project_id IS NULL"
466 470 cond << " OR project_id = #{@project.id}" if @project
467 471 @query = Query.find(params[:query_id], :conditions => cond)
468 472 @query.project = @project
469 473 session[:query] = {:id => @query.id, :project_id => @query.project_id}
470 474 else
471 475 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
472 476 # Give it a name, required to be valid
473 477 @query = Query.new(:name => "_")
474 478 @query.project = @project
475 479 if params[:fields] and params[:fields].is_a? Array
476 480 params[:fields].each do |field|
477 481 @query.add_filter(field, params[:operators][field], params[:values][field])
478 482 end
479 483 else
480 484 @query.available_filters.keys.each do |field|
481 485 @query.add_short_filter(field, params[field]) if params[field]
482 486 end
483 487 end
484 488 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
485 489 else
486 490 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
487 491 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
488 492 @query.project = @project
489 493 end
490 494 end
491 495 end
492 496 end
@@ -1,328 +1,327
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'SVG/Graph/Bar'
19 19 require 'SVG/Graph/BarHorizontal'
20 20 require 'digest/sha1'
21 21
22 22 class ChangesetNotFound < Exception; end
23 23 class InvalidRevisionParam < Exception; end
24 24
25 25 class RepositoriesController < ApplicationController
26 26 menu_item :repository
27 27 before_filter :find_repository, :except => :edit
28 28 before_filter :find_project, :only => :edit
29 29 before_filter :authorize
30 30 accept_key_auth :revisions
31 31
32 32 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
33 33
34 34 def edit
35 35 @repository = @project.repository
36 36 if !@repository
37 37 @repository = Repository.factory(params[:repository_scm])
38 38 @repository.project = @project if @repository
39 39 end
40 40 if request.post? && @repository
41 41 @repository.attributes = params[:repository]
42 42 @repository.save
43 43 end
44 44 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
45 45 end
46 46
47 47 def committers
48 48 @committers = @repository.committers
49 49 @users = @project.users
50 50 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
51 51 @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
52 52 @users.compact!
53 53 @users.sort!
54 54 if request.post? && params[:committers].is_a?(Hash)
55 55 # Build a hash with repository usernames as keys and corresponding user ids as values
56 56 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
57 57 flash[:notice] = l(:notice_successful_update)
58 58 redirect_to :action => 'committers', :id => @project
59 59 end
60 60 end
61 61
62 62 def destroy
63 63 @repository.destroy
64 64 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
65 65 end
66 66
67 67 def show
68 68 # check if new revisions have been committed in the repository
69 69 @repository.fetch_changesets if Setting.autofetch_changesets?
70 70 # root entries
71 71 @entries = @repository.entries('', @rev)
72 72 # latest changesets
73 73 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
74 74 show_error_not_found unless @entries || @changesets.any?
75 75 end
76 76
77 77 def browse
78 78 @entries = @repository.entries(@path, @rev)
79 79 if request.xhr?
80 80 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
81 81 else
82 82 show_error_not_found and return unless @entries
83 83 @properties = @repository.properties(@path, @rev)
84 84 render :action => 'browse'
85 85 end
86 86 end
87 87
88 88 def changes
89 89 @entry = @repository.entry(@path, @rev)
90 90 show_error_not_found and return unless @entry
91 91 @changesets = @repository.changesets_for_path(@path, :limit => Setting.repository_log_display_limit.to_i)
92 92 @properties = @repository.properties(@path, @rev)
93 93 end
94 94
95 95 def revisions
96 96 @changeset_count = @repository.changesets.count
97 97 @changeset_pages = Paginator.new self, @changeset_count,
98 98 per_page_option,
99 99 params['page']
100 100 @changesets = @repository.changesets.find(:all,
101 101 :limit => @changeset_pages.items_per_page,
102 102 :offset => @changeset_pages.current.offset,
103 103 :include => :user)
104 104
105 105 respond_to do |format|
106 106 format.html { render :layout => false if request.xhr? }
107 107 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
108 108 end
109 109 end
110 110
111 111 def entry
112 112 @entry = @repository.entry(@path, @rev)
113 113 show_error_not_found and return unless @entry
114 114
115 115 # If the entry is a dir, show the browser
116 116 browse and return if @entry.is_dir?
117 117
118 118 @content = @repository.cat(@path, @rev)
119 119 show_error_not_found and return unless @content
120 120 if 'raw' == params[:format] || @content.is_binary_data?
121 121 # Force the download if it's a binary file
122 122 send_data @content, :filename => @path.split('/').last
123 123 else
124 124 # Prevent empty lines when displaying a file with Windows style eol
125 125 @content.gsub!("\r\n", "\n")
126 126 end
127 127 end
128 128
129 129 def annotate
130 130 @entry = @repository.entry(@path, @rev)
131 131 show_error_not_found and return unless @entry
132 132
133 133 @annotate = @repository.scm.annotate(@path, @rev)
134 134 render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
135 135 end
136 136
137 137 def revision
138 138 @changeset = @repository.changesets.find_by_revision(@rev)
139 139 raise ChangesetNotFound unless @changeset
140 140
141 141 respond_to do |format|
142 142 format.html
143 143 format.js {render :layout => false}
144 144 end
145 145 rescue ChangesetNotFound
146 146 show_error_not_found
147 147 end
148 148
149 149 def diff
150 150 if params[:format] == 'diff'
151 151 @diff = @repository.diff(@path, @rev, @rev_to)
152 152 show_error_not_found and return unless @diff
153 153 filename = "changeset_r#{@rev}"
154 154 filename << "_r#{@rev_to}" if @rev_to
155 155 send_data @diff.join, :filename => "#{filename}.diff",
156 156 :type => 'text/x-patch',
157 157 :disposition => 'attachment'
158 158 else
159 159 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
160 160 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
161 161
162 162 # Save diff type as user preference
163 163 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
164 164 User.current.pref[:diff_type] = @diff_type
165 165 User.current.preference.save
166 166 end
167 167
168 168 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
169 169 unless read_fragment(@cache_key)
170 170 @diff = @repository.diff(@path, @rev, @rev_to)
171 171 show_error_not_found unless @diff
172 172 end
173 173 end
174 174 end
175 175
176 176 def stats
177 177 end
178 178
179 179 def graph
180 180 data = nil
181 181 case params[:graph]
182 182 when "commits_per_month"
183 183 data = graph_commits_per_month(@repository)
184 184 when "commits_per_author"
185 185 data = graph_commits_per_author(@repository)
186 186 end
187 187 if data
188 188 headers["Content-Type"] = "image/svg+xml"
189 189 send_data(data, :type => "image/svg+xml", :disposition => "inline")
190 190 else
191 191 render_404
192 192 end
193 193 end
194 194
195 195 private
196 196 def find_project
197 197 @project = Project.find(params[:id])
198 198 rescue ActiveRecord::RecordNotFound
199 199 render_404
200 200 end
201 201
202 202 REV_PARAM_RE = %r{^[a-f0-9]*$}
203 203
204 204 def find_repository
205 205 @project = Project.find(params[:id])
206 206 @repository = @project.repository
207 207 render_404 and return false unless @repository
208 208 @path = params[:path].join('/') unless params[:path].nil?
209 209 @path ||= ''
210 210 @rev = params[:rev]
211 211 @rev_to = params[:rev_to]
212 212 raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
213 213 rescue ActiveRecord::RecordNotFound
214 214 render_404
215 215 rescue InvalidRevisionParam
216 216 show_error_not_found
217 217 end
218 218
219 219 def show_error_not_found
220 220 render_error l(:error_scm_not_found)
221 221 end
222 222
223 223 # Handler for Redmine::Scm::Adapters::CommandFailed exception
224 224 def show_error_command_failed(exception)
225 225 render_error l(:error_scm_command_failed, exception.message)
226 226 end
227 227
228 228 def graph_commits_per_month(repository)
229 229 @date_to = Date.today
230 230 @date_from = @date_to << 11
231 231 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
232 232 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
233 233 commits_by_month = [0] * 12
234 234 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
235 235
236 236 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
237 237 changes_by_month = [0] * 12
238 238 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
239 239
240 240 fields = []
241 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
242 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
241 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
243 242
244 243 graph = SVG::Graph::Bar.new(
245 244 :height => 300,
246 245 :width => 800,
247 246 :fields => fields.reverse,
248 247 :stack => :side,
249 248 :scale_integers => true,
250 249 :step_x_labels => 2,
251 250 :show_data_values => false,
252 251 :graph_title => l(:label_commits_per_month),
253 252 :show_graph_title => true
254 253 )
255 254
256 255 graph.add_data(
257 256 :data => commits_by_month[0..11].reverse,
258 257 :title => l(:label_revision_plural)
259 258 )
260 259
261 260 graph.add_data(
262 261 :data => changes_by_month[0..11].reverse,
263 262 :title => l(:label_change_plural)
264 263 )
265 264
266 265 graph.burn
267 266 end
268 267
269 268 def graph_commits_per_author(repository)
270 269 commits_by_author = repository.changesets.count(:all, :group => :committer)
271 270 commits_by_author.sort! {|x, y| x.last <=> y.last}
272 271
273 272 changes_by_author = repository.changes.count(:all, :group => :committer)
274 273 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
275 274
276 275 fields = commits_by_author.collect {|r| r.first}
277 276 commits_data = commits_by_author.collect {|r| r.last}
278 277 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
279 278
280 279 fields = fields + [""]*(10 - fields.length) if fields.length<10
281 280 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
282 281 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
283 282
284 283 # Remove email adress in usernames
285 284 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
286 285
287 286 graph = SVG::Graph::BarHorizontal.new(
288 287 :height => 400,
289 288 :width => 800,
290 289 :fields => fields,
291 290 :stack => :side,
292 291 :scale_integers => true,
293 292 :show_data_values => false,
294 293 :rotate_y_labels => false,
295 294 :graph_title => l(:label_commits_per_author),
296 295 :show_graph_title => true
297 296 )
298 297
299 298 graph.add_data(
300 299 :data => commits_data,
301 300 :title => l(:label_revision_plural)
302 301 )
303 302
304 303 graph.add_data(
305 304 :data => changes_data,
306 305 :title => l(:label_change_plural)
307 306 )
308 307
309 308 graph.burn
310 309 end
311 310
312 311 end
313 312
314 313 class Date
315 314 def months_ago(date = Date.today)
316 315 (date.year - self.year)*12 + (date.month - self.month)
317 316 end
318 317
319 318 def weeks_ago(date = Date.today)
320 319 (date.year - self.year)*52 + (date.cweek - self.cweek)
321 320 end
322 321 end
323 322
324 323 class String
325 324 def with_leading_slash
326 325 starts_with?('/') ? self : "/#{self}"
327 326 end
328 327 end
@@ -1,721 +1,648
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'coderay'
19 19 require 'coderay/helpers/file_type'
20 20 require 'forwardable'
21 21 require 'cgi'
22 22
23 23 module ApplicationHelper
24 24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 26 include GravatarHelper::PublicMethods
26 27
27 28 extend Forwardable
28 29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29 30
30 31 def current_role
31 32 @current_role ||= User.current.role_for_project(@project)
32 33 end
33 34
34 35 # Return true if user is authorized for controller/action, otherwise false
35 36 def authorize_for(controller, action)
36 37 User.current.allowed_to?({:controller => controller, :action => action}, @project)
37 38 end
38 39
39 40 # Display a link if user is authorized
40 41 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
41 42 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
42 43 end
43 44
44 45 # Display a link to remote if user is authorized
45 46 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
46 47 url = options[:url] || {}
47 48 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
48 49 end
49 50
50 51 # Display a link to user's account page
51 52 def link_to_user(user, options={})
52 53 (user && !user.anonymous?) ? link_to(user.name(options[:format]), :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
53 54 end
54 55
55 56 def link_to_issue(issue, options={})
56 57 options[:class] ||= ''
57 58 options[:class] << ' issue'
58 59 options[:class] << ' closed' if issue.closed?
59 60 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
60 61 end
61 62
62 63 # Generates a link to an attachment.
63 64 # Options:
64 65 # * :text - Link text (default to attachment filename)
65 66 # * :download - Force download (default: false)
66 67 def link_to_attachment(attachment, options={})
67 68 text = options.delete(:text) || attachment.filename
68 69 action = options.delete(:download) ? 'download' : 'show'
69 70
70 71 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
71 72 end
72 73
73 74 def toggle_link(name, id, options={})
74 75 onclick = "Element.toggle('#{id}'); "
75 76 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
76 77 onclick << "return false;"
77 78 link_to(name, "#", :onclick => onclick)
78 79 end
79 80
80 81 def image_to_function(name, function, html_options = {})
81 82 html_options.symbolize_keys!
82 83 tag(:input, html_options.merge({
83 84 :type => "image", :src => image_path(name),
84 85 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
85 86 }))
86 87 end
87 88
88 89 def prompt_to_remote(name, text, param, url, html_options = {})
89 90 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
90 91 link_to name, {}, html_options
91 92 end
92
93 def format_date(date)
94 return nil unless date
95 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
96 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
97 date.strftime(@date_format)
98 end
99
100 def format_time(time, include_date = true)
101 return nil unless time
102 time = time.to_time if time.is_a?(String)
103 zone = User.current.time_zone
104 local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
105 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
106 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
107 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
108 end
109 93
110 94 def format_activity_title(text)
111 h(truncate_single_line(text, 100))
95 h(truncate_single_line(text, :length => 100))
112 96 end
113 97
114 98 def format_activity_day(date)
115 99 date == Date.today ? l(:label_today).titleize : format_date(date)
116 100 end
117 101
118 102 def format_activity_description(text)
119 h(truncate(text.to_s, 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
120 end
121
122 def distance_of_date_in_words(from_date, to_date = 0)
123 from_date = from_date.to_date if from_date.respond_to?(:to_date)
124 to_date = to_date.to_date if to_date.respond_to?(:to_date)
125 distance_in_days = (to_date - from_date).abs
126 lwr(:actionview_datehelper_time_in_words_day, distance_in_days)
103 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
127 104 end
128 105
129 106 def due_date_distance_in_words(date)
130 107 if date
131 108 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
132 109 end
133 110 end
134 111
135 112 def render_page_hierarchy(pages, node=nil)
136 113 content = ''
137 114 if pages[node]
138 115 content << "<ul class=\"pages-hierarchy\">\n"
139 116 pages[node].each do |page|
140 117 content << "<li>"
141 118 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
142 119 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
143 120 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
144 121 content << "</li>\n"
145 122 end
146 123 content << "</ul>\n"
147 124 end
148 125 content
149 126 end
150 127
151 128 # Renders flash messages
152 129 def render_flash_messages
153 130 s = ''
154 131 flash.each do |k,v|
155 132 s << content_tag('div', v, :class => "flash #{k}")
156 133 end
157 134 s
158 135 end
159 136
160 137 # Renders the project quick-jump box
161 138 def render_project_jump_box
162 139 # Retrieve them now to avoid a COUNT query
163 140 projects = User.current.projects.all
164 141 if projects.any?
165 142 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
166 143 "<option selected='selected'>#{ l(:label_jump_to_a_project) }</option>" +
167 144 '<option disabled="disabled">---</option>'
168 145 s << project_tree_options_for_select(projects) do |p|
169 146 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
170 147 end
171 148 s << '</select>'
172 149 s
173 150 end
174 151 end
175 152
176 153 def project_tree_options_for_select(projects, options = {})
177 154 s = ''
178 155 project_tree(projects) do |project, level|
179 156 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
180 157 tag_options = {:value => project.id, :selected => ((project == options[:selected]) ? 'selected' : nil)}
181 158 tag_options.merge!(yield(project)) if block_given?
182 159 s << content_tag('option', name_prefix + h(project), tag_options)
183 160 end
184 161 s
185 162 end
186 163
187 164 # Yields the given block for each project with its level in the tree
188 165 def project_tree(projects, &block)
189 166 ancestors = []
190 167 projects.sort_by(&:lft).each do |project|
191 168 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
192 169 ancestors.pop
193 170 end
194 171 yield project, ancestors.size
195 172 ancestors << project
196 173 end
197 174 end
198 175
199 176 def project_nested_ul(projects, &block)
200 177 s = ''
201 178 if projects.any?
202 179 ancestors = []
203 180 projects.sort_by(&:lft).each do |project|
204 181 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
205 182 s << "<ul>\n"
206 183 else
207 184 ancestors.pop
208 185 s << "</li>"
209 186 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
210 187 ancestors.pop
211 188 s << "</ul></li>\n"
212 189 end
213 190 end
214 191 s << "<li>"
215 192 s << yield(project).to_s
216 193 ancestors << project
217 194 end
218 195 s << ("</li></ul>\n" * ancestors.size)
219 196 end
220 197 s
221 198 end
222 199
223 200 # Truncates and returns the string as a single line
224 201 def truncate_single_line(string, *args)
225 202 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
226 203 end
227 204
228 205 def html_hours(text)
229 206 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
230 207 end
231 208
232 209 def authoring(created, author, options={})
233 210 time_tag = @project.nil? ? content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created)) :
234 211 link_to(distance_of_time_in_words(Time.now, created),
235 212 {:controller => 'projects', :action => 'activity', :id => @project, :from => created.to_date},
236 213 :title => format_time(created))
237 214 author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous')
238 l(options[:label] || :label_added_time_by, author_tag, time_tag)
239 end
240
241 def l_or_humanize(s, options={})
242 k = "#{options[:prefix]}#{s}".to_sym
243 l_has_string?(k) ? l(k) : s.to_s.humanize
244 end
245
246 def day_name(day)
247 l(:general_day_names).split(',')[day-1]
248 end
249
250 def month_name(month)
251 l(:actionview_datehelper_select_month_names).split(',')[month-1]
215 l(options[:label] || :label_added_time_by, :author => author_tag, :age => time_tag)
252 216 end
253 217
254 218 def syntax_highlight(name, content)
255 219 type = CodeRay::FileType[name]
256 220 type ? CodeRay.scan(content, type).html : h(content)
257 221 end
258 222
259 223 def to_path_param(path)
260 224 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
261 225 end
262 226
263 227 def pagination_links_full(paginator, count=nil, options={})
264 228 page_param = options.delete(:page_param) || :page
265 229 url_param = params.dup
266 230 # don't reuse query params if filters are present
267 231 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
268 232
269 233 html = ''
270 234 if paginator.current.previous
271 235 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
272 236 end
273 237
274 238 html << (pagination_links_each(paginator, options) do |n|
275 239 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
276 240 end || '')
277 241
278 242 if paginator.current.next
279 243 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
280 244 end
281 245
282 246 unless count.nil?
283 247 html << [
284 248 " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})",
285 249 per_page_links(paginator.items_per_page)
286 250 ].compact.join(' | ')
287 251 end
288 252
289 253 html
290 254 end
291 255
292 256 def per_page_links(selected=nil)
293 257 url_param = params.dup
294 258 url_param.clear if url_param.has_key?(:set_filter)
295 259
296 260 links = Setting.per_page_options_array.collect do |n|
297 261 n == selected ? n : link_to_remote(n, {:update => "content",
298 262 :url => params.dup.merge(:per_page => n),
299 263 :method => :get},
300 264 {:href => url_for(url_param.merge(:per_page => n))})
301 265 end
302 266 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
303 267 end
304 268
305 269 def breadcrumb(*args)
306 270 elements = args.flatten
307 271 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
308 272 end
309 273
310 274 def other_formats_links(&block)
311 concat('<p class="other-formats">' + l(:label_export_to), block.binding)
275 concat('<p class="other-formats">' + l(:label_export_to))
312 276 yield Redmine::Views::OtherFormatsBuilder.new(self)
313 concat('</p>', block.binding)
277 concat('</p>')
314 278 end
315 279
316 280 def page_header_title
317 281 if @project.nil? || @project.new_record?
318 282 h(Setting.app_title)
319 283 else
320 284 b = []
321 285 ancestors = (@project.root? ? [] : @project.ancestors.visible)
322 286 if ancestors.any?
323 287 root = ancestors.shift
324 288 b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
325 289 if ancestors.size > 2
326 290 b << '&#8230;'
327 291 ancestors = ancestors[-2, 2]
328 292 end
329 293 b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
330 294 end
331 295 b << h(@project)
332 296 b.join(' &#187; ')
333 297 end
334 298 end
335 299
336 300 def html_title(*args)
337 301 if args.empty?
338 302 title = []
339 303 title << @project.name if @project
340 304 title += @html_title if @html_title
341 305 title << Setting.app_title
342 306 title.compact.join(' - ')
343 307 else
344 308 @html_title ||= []
345 309 @html_title += args
346 310 end
347 311 end
348 312
349 313 def accesskey(s)
350 314 Redmine::AccessKeys.key_for s
351 315 end
352 316
353 317 # Formats text according to system settings.
354 318 # 2 ways to call this method:
355 319 # * with a String: textilizable(text, options)
356 320 # * with an object and one of its attribute: textilizable(issue, :description, options)
357 321 def textilizable(*args)
358 322 options = args.last.is_a?(Hash) ? args.pop : {}
359 323 case args.size
360 324 when 1
361 325 obj = options[:object]
362 326 text = args.shift
363 327 when 2
364 328 obj = args.shift
365 329 text = obj.send(args.shift).to_s
366 330 else
367 331 raise ArgumentError, 'invalid arguments to textilizable'
368 332 end
369 333 return '' if text.blank?
370 334
371 335 only_path = options.delete(:only_path) == false ? false : true
372 336
373 337 # when using an image link, try to use an attachment, if possible
374 338 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
375 339
376 340 if attachments
377 341 attachments = attachments.sort_by(&:created_on).reverse
378 342 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
379 343 style = $1
380 344 filename = $6.downcase
381 345 # search for the picture in attachments
382 346 if found = attachments.detect { |att| att.filename.downcase == filename }
383 347 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
384 348 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
385 349 alt = desc.blank? ? nil : "(#{desc})"
386 350 "!#{style}#{image_url}#{alt}!"
387 351 else
388 352 m
389 353 end
390 354 end
391 355 end
392 356
393 357 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
394 358
395 359 # different methods for formatting wiki links
396 360 case options[:wiki_links]
397 361 when :local
398 362 # used for local links to html files
399 363 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
400 364 when :anchor
401 365 # used for single-file wiki export
402 366 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
403 367 else
404 368 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
405 369 end
406 370
407 371 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
408 372
409 373 # Wiki links
410 374 #
411 375 # Examples:
412 376 # [[mypage]]
413 377 # [[mypage|mytext]]
414 378 # wiki links can refer other project wikis, using project name or identifier:
415 379 # [[project:]] -> wiki starting page
416 380 # [[project:|mytext]]
417 381 # [[project:mypage]]
418 382 # [[project:mypage|mytext]]
419 383 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
420 384 link_project = project
421 385 esc, all, page, title = $1, $2, $3, $5
422 386 if esc.nil?
423 387 if page =~ /^([^\:]+)\:(.*)$/
424 388 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
425 389 page = $2
426 390 title ||= $1 if page.blank?
427 391 end
428 392
429 393 if link_project && link_project.wiki
430 394 # extract anchor
431 395 anchor = nil
432 396 if page =~ /^(.+?)\#(.+)$/
433 397 page, anchor = $1, $2
434 398 end
435 399 # check if page exists
436 400 wiki_page = link_project.wiki.find_page(page)
437 401 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
438 402 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
439 403 else
440 404 # project or wiki doesn't exist
441 405 all
442 406 end
443 407 else
444 408 all
445 409 end
446 410 end
447 411
448 412 # Redmine links
449 413 #
450 414 # Examples:
451 415 # Issues:
452 416 # #52 -> Link to issue #52
453 417 # Changesets:
454 418 # r52 -> Link to revision 52
455 419 # commit:a85130f -> Link to scmid starting with a85130f
456 420 # Documents:
457 421 # document#17 -> Link to document with id 17
458 422 # document:Greetings -> Link to the document with title "Greetings"
459 423 # document:"Some document" -> Link to the document with title "Some document"
460 424 # Versions:
461 425 # version#3 -> Link to version with id 3
462 426 # version:1.0.0 -> Link to version named "1.0.0"
463 427 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
464 428 # Attachments:
465 429 # attachment:file.zip -> Link to the attachment of the current object named file.zip
466 430 # Source files:
467 431 # source:some/file -> Link to the file located at /some/file in the project's repository
468 432 # source:some/file@52 -> Link to the file's revision 52
469 433 # source:some/file#L120 -> Link to line 120 of the file
470 434 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
471 435 # export:some/file -> Force the download of the file
472 436 # Forum messages:
473 437 # message#1218 -> Link to message with id 1218
474 438 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m|
475 439 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
476 440 link = nil
477 441 if esc.nil?
478 442 if prefix.nil? && sep == 'r'
479 443 if project && (changeset = project.changesets.find_by_revision(oid))
480 444 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
481 445 :class => 'changeset',
482 :title => truncate_single_line(changeset.comments, 100))
446 :title => truncate_single_line(changeset.comments, :length => 100))
483 447 end
484 448 elsif sep == '#'
485 449 oid = oid.to_i
486 450 case prefix
487 451 when nil
488 452 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
489 453 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
490 454 :class => (issue.closed? ? 'issue closed' : 'issue'),
491 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
455 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
492 456 link = content_tag('del', link) if issue.closed?
493 457 end
494 458 when 'document'
495 459 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
496 460 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
497 461 :class => 'document'
498 462 end
499 463 when 'version'
500 464 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
501 465 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
502 466 :class => 'version'
503 467 end
504 468 when 'message'
505 469 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
506 link = link_to h(truncate(message.subject, 60)), {:only_path => only_path,
470 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
507 471 :controller => 'messages',
508 472 :action => 'show',
509 473 :board_id => message.board,
510 474 :id => message.root,
511 475 :anchor => (message.parent ? "message-#{message.id}" : nil)},
512 476 :class => 'message'
513 477 end
514 478 end
515 479 elsif sep == ':'
516 480 # removes the double quotes if any
517 481 name = oid.gsub(%r{^"(.*)"$}, "\\1")
518 482 case prefix
519 483 when 'document'
520 484 if project && document = project.documents.find_by_title(name)
521 485 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
522 486 :class => 'document'
523 487 end
524 488 when 'version'
525 489 if project && version = project.versions.find_by_name(name)
526 490 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
527 491 :class => 'version'
528 492 end
529 493 when 'commit'
530 494 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
531 495 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
532 496 :class => 'changeset',
533 :title => truncate_single_line(changeset.comments, 100)
497 :title => truncate_single_line(changeset.comments, :length => 100)
534 498 end
535 499 when 'source', 'export'
536 500 if project && project.repository
537 501 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
538 502 path, rev, anchor = $1, $3, $5
539 503 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
540 504 :path => to_path_param(path),
541 505 :rev => rev,
542 506 :anchor => anchor,
543 507 :format => (prefix == 'export' ? 'raw' : nil)},
544 508 :class => (prefix == 'export' ? 'source download' : 'source')
545 509 end
546 510 when 'attachment'
547 511 if attachments && attachment = attachments.detect {|a| a.filename == name }
548 512 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
549 513 :class => 'attachment'
550 514 end
551 515 end
552 516 end
553 517 end
554 518 leading + (link || "#{prefix}#{sep}#{oid}")
555 519 end
556 520
557 521 text
558 522 end
559 523
560 524 # Same as Rails' simple_format helper without using paragraphs
561 525 def simple_format_without_paragraph(text)
562 526 text.to_s.
563 527 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
564 528 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
565 529 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
566 530 end
567 531
568 def error_messages_for(object_name, options = {})
569 options = options.symbolize_keys
570 object = instance_variable_get("@#{object_name}")
571 if object && !object.errors.empty?
572 # build full_messages here with controller current language
573 full_messages = []
574 object.errors.each do |attr, msg|
575 next if msg.nil?
576 msg = [msg] unless msg.is_a?(Array)
577 if attr == "base"
578 full_messages << l(*msg)
579 else
580 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(*msg) unless attr == "custom_values"
581 end
582 end
583 # retrieve custom values error messages
584 if object.errors[:custom_values]
585 object.custom_values.each do |v|
586 v.errors.each do |attr, msg|
587 next if msg.nil?
588 msg = [msg] unless msg.is_a?(Array)
589 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(*msg)
590 end
591 end
592 end
593 content_tag("div",
594 content_tag(
595 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
596 ) +
597 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
598 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
599 )
600 else
601 ""
602 end
603 end
604
605 532 def lang_options_for_select(blank=true)
606 533 (blank ? [["(auto)", ""]] : []) +
607 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
534 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
608 535 end
609 536
610 537 def label_tag_for(name, option_tags = nil, options = {})
611 538 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
612 539 content_tag("label", label_text)
613 540 end
614 541
615 542 def labelled_tabular_form_for(name, object, options, &proc)
616 543 options[:html] ||= {}
617 544 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
618 545 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
619 546 end
620 547
621 548 def back_url_hidden_field_tag
622 549 back_url = params[:back_url] || request.env['HTTP_REFERER']
623 550 back_url = CGI.unescape(back_url.to_s)
624 551 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
625 552 end
626 553
627 554 def check_all_links(form_name)
628 555 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
629 556 " | " +
630 557 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
631 558 end
632 559
633 560 def progress_bar(pcts, options={})
634 561 pcts = [pcts, pcts] unless pcts.is_a?(Array)
635 562 pcts[1] = pcts[1] - pcts[0]
636 563 pcts << (100 - pcts[1] - pcts[0])
637 564 width = options[:width] || '100px;'
638 565 legend = options[:legend] || ''
639 566 content_tag('table',
640 567 content_tag('tr',
641 568 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0].floor}%;", :class => 'closed') : '') +
642 569 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1].floor}%;", :class => 'done') : '') +
643 570 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2].floor}%;", :class => 'todo') : '')
644 571 ), :class => 'progress', :style => "width: #{width};") +
645 572 content_tag('p', legend, :class => 'pourcent')
646 573 end
647 574
648 575 def context_menu_link(name, url, options={})
649 576 options[:class] ||= ''
650 577 if options.delete(:selected)
651 578 options[:class] << ' icon-checked disabled'
652 579 options[:disabled] = true
653 580 end
654 581 if options.delete(:disabled)
655 582 options.delete(:method)
656 583 options.delete(:confirm)
657 584 options.delete(:onclick)
658 585 options[:class] << ' disabled'
659 586 url = '#'
660 587 end
661 588 link_to name, url, options
662 589 end
663 590
664 591 def calendar_for(field_id)
665 592 include_calendar_headers_tags
666 593 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
667 594 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
668 595 end
669 596
670 597 def include_calendar_headers_tags
671 598 unless @calendar_headers_tags_included
672 599 @calendar_headers_tags_included = true
673 600 content_for :header_tags do
674 601 javascript_include_tag('calendar/calendar') +
675 602 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
676 603 javascript_include_tag('calendar/calendar-setup') +
677 604 stylesheet_link_tag('calendar')
678 605 end
679 606 end
680 607 end
681 608
682 609 def content_for(name, content = nil, &block)
683 610 @has_content ||= {}
684 611 @has_content[name] = true
685 612 super(name, content, &block)
686 613 end
687 614
688 615 def has_content?(name)
689 616 (@has_content && @has_content[name]) || false
690 617 end
691 618
692 619 # Returns the avatar image tag for the given +user+ if avatars are enabled
693 620 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
694 621 def avatar(user, options = { })
695 622 if Setting.gravatar_enabled?
696 623 email = nil
697 624 if user.respond_to?(:mail)
698 625 email = user.mail
699 626 elsif user.to_s =~ %r{<(.+?)>}
700 627 email = $1
701 628 end
702 629 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
703 630 end
704 631 end
705 632
706 633 private
707 634
708 635 def wiki_helper
709 636 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
710 637 extend helper
711 638 return self
712 639 end
713 640
714 641 def link_to_remote_content_update(text, url_params)
715 642 link_to_remote(text,
716 643 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
717 644 {:href => url_for(:params => url_params)}
718 645 )
719 646 end
720 647
721 648 end
@@ -1,196 +1,196
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'csv'
19 19
20 20 module IssuesHelper
21 21 include ApplicationHelper
22 22
23 23 def render_issue_tooltip(issue)
24 24 @cached_label_start_date ||= l(:field_start_date)
25 25 @cached_label_due_date ||= l(:field_due_date)
26 26 @cached_label_assigned_to ||= l(:field_assigned_to)
27 27 @cached_label_priority ||= l(:field_priority)
28 28
29 29 link_to_issue(issue) + ": #{h(issue.subject)}<br /><br />" +
30 30 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
31 31 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
32 32 "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
33 33 "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
34 34 end
35 35
36 36 # Returns a string of css classes that apply to the given issue
37 37 def css_issue_classes(issue)
38 38 s = "issue status-#{issue.status.position} priority-#{issue.priority.position}"
39 39 s << ' closed' if issue.closed?
40 40 s << ' overdue' if issue.overdue?
41 41 s
42 42 end
43 43
44 44 def sidebar_queries
45 45 unless @sidebar_queries
46 46 # User can see public queries and his own queries
47 47 visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
48 48 # Project specific queries and global queries
49 49 visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
50 50 @sidebar_queries = Query.find(:all,
51 51 :order => "name ASC",
52 52 :conditions => visible.conditions)
53 53 end
54 54 @sidebar_queries
55 55 end
56 56
57 57 def show_detail(detail, no_html=false)
58 58 case detail.property
59 59 when 'attr'
60 60 label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
61 61 case detail.prop_key
62 62 when 'due_date', 'start_date'
63 63 value = format_date(detail.value.to_date) if detail.value
64 64 old_value = format_date(detail.old_value.to_date) if detail.old_value
65 65 when 'project_id'
66 66 p = Project.find_by_id(detail.value) and value = p.name if detail.value
67 67 p = Project.find_by_id(detail.old_value) and old_value = p.name if detail.old_value
68 68 when 'status_id'
69 69 s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
70 70 s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
71 71 when 'tracker_id'
72 72 t = Tracker.find_by_id(detail.value) and value = t.name if detail.value
73 73 t = Tracker.find_by_id(detail.old_value) and old_value = t.name if detail.old_value
74 74 when 'assigned_to_id'
75 75 u = User.find_by_id(detail.value) and value = u.name if detail.value
76 76 u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
77 77 when 'priority_id'
78 78 e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
79 79 e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
80 80 when 'category_id'
81 81 c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
82 82 c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
83 83 when 'fixed_version_id'
84 84 v = Version.find_by_id(detail.value) and value = v.name if detail.value
85 85 v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
86 86 when 'estimated_hours'
87 87 value = "%0.02f" % detail.value.to_f unless detail.value.blank?
88 88 old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
89 89 end
90 90 when 'cf'
91 91 custom_field = CustomField.find_by_id(detail.prop_key)
92 92 if custom_field
93 93 label = custom_field.name
94 94 value = format_value(detail.value, custom_field.field_format) if detail.value
95 95 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
96 96 end
97 97 when 'attachment'
98 98 label = l(:label_attachment)
99 99 end
100 100 call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
101 101
102 102 label ||= detail.prop_key
103 103 value ||= detail.value
104 104 old_value ||= detail.old_value
105 105
106 106 unless no_html
107 107 label = content_tag('strong', label)
108 108 old_value = content_tag("i", h(old_value)) if detail.old_value
109 109 old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
110 110 if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
111 111 # Link to the attachment if it has not been removed
112 112 value = link_to_attachment(a)
113 113 else
114 114 value = content_tag("i", h(value)) if value
115 115 end
116 116 end
117 117
118 118 if !detail.value.blank?
119 119 case detail.property
120 120 when 'attr', 'cf'
121 121 if !detail.old_value.blank?
122 label + " " + l(:text_journal_changed, old_value, value)
122 label + " " + l(:text_journal_changed, :old => old_value, :new => value)
123 123 else
124 124 label + " " + l(:text_journal_set_to, value)
125 125 end
126 126 when 'attachment'
127 127 "#{label} #{value} #{l(:label_added)}"
128 128 end
129 129 else
130 130 case detail.property
131 131 when 'attr', 'cf'
132 132 label + " " + l(:text_journal_deleted) + " (#{old_value})"
133 133 when 'attachment'
134 134 "#{label} #{old_value} #{l(:label_deleted)}"
135 135 end
136 136 end
137 137 end
138 138
139 139 def issues_to_csv(issues, project = nil)
140 140 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
141 141 decimal_separator = l(:general_csv_decimal_separator)
142 142 export = StringIO.new
143 143 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
144 144 # csv header fields
145 145 headers = [ "#",
146 146 l(:field_status),
147 147 l(:field_project),
148 148 l(:field_tracker),
149 149 l(:field_priority),
150 150 l(:field_subject),
151 151 l(:field_assigned_to),
152 152 l(:field_category),
153 153 l(:field_fixed_version),
154 154 l(:field_author),
155 155 l(:field_start_date),
156 156 l(:field_due_date),
157 157 l(:field_done_ratio),
158 158 l(:field_estimated_hours),
159 159 l(:field_created_on),
160 160 l(:field_updated_on)
161 161 ]
162 162 # Export project custom fields if project is given
163 163 # otherwise export custom fields marked as "For all projects"
164 164 custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields
165 165 custom_fields.each {|f| headers << f.name}
166 166 # Description in the last column
167 167 headers << l(:field_description)
168 168 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
169 169 # csv lines
170 170 issues.each do |issue|
171 171 fields = [issue.id,
172 172 issue.status.name,
173 173 issue.project.name,
174 174 issue.tracker.name,
175 175 issue.priority.name,
176 176 issue.subject,
177 177 issue.assigned_to,
178 178 issue.category,
179 179 issue.fixed_version,
180 180 issue.author.name,
181 181 format_date(issue.start_date),
182 182 format_date(issue.due_date),
183 183 issue.done_ratio,
184 184 issue.estimated_hours.to_s.gsub('.', decimal_separator),
185 185 format_time(issue.created_on),
186 186 format_time(issue.updated_on)
187 187 ]
188 188 custom_fields.each {|f| fields << show_value(issue.custom_value_for(f)) }
189 189 fields << issue.description
190 190 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
191 191 end
192 192 end
193 193 export.rewind
194 194 export
195 195 end
196 196 end
@@ -1,28 +1,28
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module MessagesHelper
19 19
20 20 def link_to_message(message)
21 21 return '' unless message
22 link_to h(truncate(message.subject, 60)), :controller => 'messages',
22 link_to h(truncate(message.subject, :length => 60)), :controller => 'messages',
23 23 :action => 'show',
24 24 :board_id => message.board_id,
25 25 :id => message.root,
26 26 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
27 27 end
28 28 end
@@ -1,30 +1,30
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module SettingsHelper
19 19 def administration_settings_tabs
20 20 tabs = [{:name => 'general', :partial => 'settings/general', :label => :label_general},
21 21 {:name => 'display', :partial => 'settings/display', :label => :label_display},
22 22 {:name => 'authentication', :partial => 'settings/authentication', :label => :label_authentication},
23 23 {:name => 'projects', :partial => 'settings/projects', :label => :label_project_plural},
24 24 {:name => 'issues', :partial => 'settings/issues', :label => :label_issue_tracking},
25 {:name => 'notifications', :partial => 'settings/notifications', :label => l(:field_mail_notification)},
26 {:name => 'mail_handler', :partial => 'settings/mail_handler', :label => l(:label_incoming_emails)},
25 {:name => 'notifications', :partial => 'settings/notifications', :label => :field_mail_notification},
26 {:name => 'mail_handler', :partial => 'settings/mail_handler', :label => :label_incoming_emails},
27 27 {:name => 'repositories', :partial => 'settings/repositories', :label => :label_repository_plural}
28 28 ]
29 29 end
30 30 end
@@ -1,169 +1,169
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'iconv'
19 19
20 20 class Changeset < ActiveRecord::Base
21 21 belongs_to :repository
22 22 belongs_to :user
23 23 has_many :changes, :dependent => :delete_all
24 24 has_and_belongs_to_many :issues
25 25
26 26 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
27 27 :description => :long_comments,
28 28 :datetime => :committed_on,
29 29 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
30 30
31 31 acts_as_searchable :columns => 'comments',
32 32 :include => {:repository => :project},
33 33 :project_key => "#{Repository.table_name}.project_id",
34 34 :date_column => 'committed_on'
35 35
36 36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
37 37 :author_key => :user_id,
38 38 :find_options => {:include => {:repository => :project}}
39 39
40 40 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
41 41 validates_uniqueness_of :revision, :scope => :repository_id
42 42 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
43 43
44 44 def revision=(r)
45 45 write_attribute :revision, (r.nil? ? nil : r.to_s)
46 46 end
47 47
48 48 def comments=(comment)
49 49 write_attribute(:comments, Changeset.normalize_comments(comment))
50 50 end
51 51
52 52 def committed_on=(date)
53 53 self.commit_date = date
54 54 super
55 55 end
56 56
57 57 def project
58 58 repository.project
59 59 end
60 60
61 61 def author
62 62 user || committer.to_s.split('<').first
63 63 end
64 64
65 65 def before_create
66 66 self.user = repository.find_committer_user(committer)
67 67 end
68 68
69 69 def after_create
70 70 scan_comment_for_issue_ids
71 71 end
72 72 require 'pp'
73 73
74 74 def scan_comment_for_issue_ids
75 75 return if comments.blank?
76 76 # keywords used to reference issues
77 77 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
78 78 # keywords used to fix issues
79 79 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
80 80 # status and optional done ratio applied
81 81 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
82 82 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
83 83
84 84 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
85 85 return if kw_regexp.blank?
86 86
87 87 referenced_issues = []
88 88
89 89 if ref_keywords.delete('*')
90 90 # find any issue ID in the comments
91 91 target_issue_ids = []
92 92 comments.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
93 93 referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
94 94 end
95 95
96 96 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
97 97 action = match[0]
98 98 target_issue_ids = match[1].scan(/\d+/)
99 99 target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
100 100 if fix_status && fix_keywords.include?(action.downcase)
101 101 # update status of issues
102 102 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
103 103 target_issues.each do |issue|
104 104 # the issue may have been updated by the closure of another one (eg. duplicate)
105 105 issue.reload
106 106 # don't change the status is the issue is closed
107 107 next if issue.status.is_closed?
108 108 csettext = "r#{self.revision}"
109 109 if self.scmid && (! (csettext =~ /^r[0-9]+$/))
110 110 csettext = "commit:\"#{self.scmid}\""
111 111 end
112 journal = issue.init_journal(user || User.anonymous, l(:text_status_changed_by_changeset, csettext))
112 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext))
113 113 issue.status = fix_status
114 114 issue.done_ratio = done_ratio if done_ratio
115 115 issue.save
116 116 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
117 117 end
118 118 end
119 119 referenced_issues += target_issues
120 120 end
121 121
122 122 self.issues = referenced_issues.uniq
123 123 end
124 124
125 125 def short_comments
126 126 @short_comments || split_comments.first
127 127 end
128 128
129 129 def long_comments
130 130 @long_comments || split_comments.last
131 131 end
132 132
133 133 # Returns the previous changeset
134 134 def previous
135 135 @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
136 136 end
137 137
138 138 # Returns the next changeset
139 139 def next
140 140 @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
141 141 end
142 142
143 143 # Strips and reencodes a commit log before insertion into the database
144 144 def self.normalize_comments(str)
145 145 to_utf8(str.to_s.strip)
146 146 end
147 147
148 148 private
149 149
150 150 def split_comments
151 151 comments =~ /\A(.+?)\r?\n(.*)$/m
152 152 @short_comments = $1 || comments
153 153 @long_comments = $2.to_s.strip
154 154 return @short_comments, @long_comments
155 155 end
156 156
157 157 def self.to_utf8(str)
158 158 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
159 159 encoding = Setting.commit_logs_encoding.to_s.strip
160 160 unless encoding.blank? || encoding == 'UTF-8'
161 161 begin
162 162 return Iconv.conv('UTF-8', encoding, str)
163 163 rescue Iconv::Failure
164 164 # do nothing here
165 165 end
166 166 end
167 167 str
168 168 end
169 169 end
@@ -1,111 +1,111
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class CustomField < ActiveRecord::Base
19 19 has_many :custom_values, :dependent => :delete_all
20 20 acts_as_list :scope => 'type = \'#{self.class}\''
21 21 serialize :possible_values
22 22
23 23 FIELD_FORMATS = { "string" => { :name => :label_string, :order => 1 },
24 24 "text" => { :name => :label_text, :order => 2 },
25 25 "int" => { :name => :label_integer, :order => 3 },
26 26 "float" => { :name => :label_float, :order => 4 },
27 27 "list" => { :name => :label_list, :order => 5 },
28 28 "date" => { :name => :label_date, :order => 6 },
29 29 "bool" => { :name => :label_boolean, :order => 7 }
30 30 }.freeze
31 31
32 32 validates_presence_of :name, :field_format
33 33 validates_uniqueness_of :name, :scope => :type
34 34 validates_length_of :name, :maximum => 30
35 35 validates_format_of :name, :with => /^[\w\s\.\'\-]*$/i
36 36 validates_inclusion_of :field_format, :in => FIELD_FORMATS.keys
37 37
38 38 def initialize(attributes = nil)
39 39 super
40 40 self.possible_values ||= []
41 41 end
42 42
43 43 def before_validation
44 44 # make sure these fields are not searchable
45 45 self.searchable = false if %w(int float date bool).include?(field_format)
46 46 true
47 47 end
48 48
49 49 def validate
50 50 if self.field_format == "list"
51 errors.add(:possible_values, :activerecord_error_blank) if self.possible_values.nil? || self.possible_values.empty?
52 errors.add(:possible_values, :activerecord_error_invalid) unless self.possible_values.is_a? Array
51 errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
52 errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
53 53 end
54 54
55 55 # validate default value
56 56 v = CustomValue.new(:custom_field => self.clone, :value => default_value, :customized => nil)
57 57 v.custom_field.is_required = false
58 errors.add(:default_value, :activerecord_error_invalid) unless v.valid?
58 errors.add(:default_value, :invalid) unless v.valid?
59 59 end
60 60
61 61 # Makes possible_values accept a multiline string
62 62 def possible_values=(arg)
63 63 if arg.is_a?(Array)
64 64 write_attribute(:possible_values, arg.compact.collect(&:strip).select {|v| !v.blank?})
65 65 else
66 66 self.possible_values = arg.to_s.split(/[\n\r]+/)
67 67 end
68 68 end
69 69
70 70 # Returns a ORDER BY clause that can used to sort customized
71 71 # objects by their value of the custom field.
72 72 # Returns false, if the custom field can not be used for sorting.
73 73 def order_statement
74 74 case field_format
75 75 when 'string', 'text', 'list', 'date', 'bool'
76 76 # COALESCE is here to make sure that blank and NULL values are sorted equally
77 77 "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" +
78 78 " WHERE cv_sort.customized_type='#{self.class.customized_class.name}'" +
79 79 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
80 80 " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')"
81 81 when 'int', 'float'
82 82 # Make the database cast values into numeric
83 83 # Postgresql will raise an error if a value can not be casted!
84 84 # CustomValue validations should ensure that it doesn't occur
85 85 "(SELECT CAST(cv_sort.value AS decimal(60,3)) FROM #{CustomValue.table_name} cv_sort" +
86 86 " WHERE cv_sort.customized_type='#{self.class.customized_class.name}'" +
87 87 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
88 88 " AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)"
89 89 else
90 90 nil
91 91 end
92 92 end
93 93
94 94 def <=>(field)
95 95 position <=> field.position
96 96 end
97 97
98 98 def self.customized_class
99 99 self.name =~ /^(.+)CustomField$/
100 100 begin; $1.constantize; rescue nil; end
101 101 end
102 102
103 103 # to move in project_custom_field
104 104 def self.for_all
105 105 find(:all, :conditions => ["is_for_all=?", true], :order => 'position')
106 106 end
107 107
108 108 def type_name
109 109 nil
110 110 end
111 111 end
@@ -1,67 +1,67
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class CustomValue < ActiveRecord::Base
19 19 belongs_to :custom_field
20 20 belongs_to :customized, :polymorphic => true
21 21
22 22 def after_initialize
23 23 if custom_field && new_record? && (customized_type.blank? || (customized && customized.new_record?))
24 24 self.value ||= custom_field.default_value
25 25 end
26 26 end
27 27
28 28 # Returns true if the boolean custom value is true
29 29 def true?
30 30 self.value == '1'
31 31 end
32 32
33 33 def editable?
34 34 custom_field.editable?
35 35 end
36 36
37 37 def required?
38 38 custom_field.is_required?
39 39 end
40 40
41 41 def to_s
42 42 value.to_s
43 43 end
44 44
45 45 protected
46 46 def validate
47 47 if value.blank?
48 errors.add(:value, :activerecord_error_blank) if custom_field.is_required? and value.blank?
48 errors.add(:value, :blank) if custom_field.is_required? and value.blank?
49 49 else
50 errors.add(:value, :activerecord_error_invalid) unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp)
51 errors.add(:value, :activerecord_error_too_short) if custom_field.min_length > 0 and value.length < custom_field.min_length
52 errors.add(:value, :activerecord_error_too_long) if custom_field.max_length > 0 and value.length > custom_field.max_length
50 errors.add(:value, :invalid) unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp)
51 errors.add(:value, :too_short) if custom_field.min_length > 0 and value.length < custom_field.min_length
52 errors.add(:value, :too_long) if custom_field.max_length > 0 and value.length > custom_field.max_length
53 53
54 54 # Format specific validations
55 55 case custom_field.field_format
56 56 when 'int'
57 errors.add(:value, :activerecord_error_not_a_number) unless value =~ /^[+-]?\d+$/
57 errors.add(:value, :not_a_number) unless value =~ /^[+-]?\d+$/
58 58 when 'float'
59 begin; Kernel.Float(value); rescue; errors.add(:value, :activerecord_error_invalid) end
59 begin; Kernel.Float(value); rescue; errors.add(:value, :invalid) end
60 60 when 'date'
61 errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/
61 errors.add(:value, :not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/
62 62 when 'list'
63 errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.include?(value)
63 errors.add(:value, :inclusion) unless custom_field.possible_values.include?(value)
64 64 end
65 65 end
66 66 end
67 67 end
@@ -1,292 +1,292
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Issue < ActiveRecord::Base
19 19 belongs_to :project
20 20 belongs_to :tracker
21 21 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
22 22 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
23 23 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
24 24 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
25 25 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
26 26 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
27 27
28 28 has_many :journals, :as => :journalized, :dependent => :destroy
29 29 has_many :time_entries, :dependent => :delete_all
30 30 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
31 31
32 32 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
33 33 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
34 34
35 35 acts_as_attachable :after_remove => :attachment_removed
36 36 acts_as_customizable
37 37 acts_as_watchable
38 38 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
39 39 :include => [:project, :journals],
40 40 # sort by id so that limited eager loading doesn't break with postgresql
41 41 :order_column => "#{table_name}.id"
42 42 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
43 43 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
44 44 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
45 45
46 46 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
47 47 :author_key => :author_id
48 48
49 49 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
50 50 validates_length_of :subject, :maximum => 255
51 51 validates_inclusion_of :done_ratio, :in => 0..100
52 52 validates_numericality_of :estimated_hours, :allow_nil => true
53 53
54 54 named_scope :visible, lambda {|*args| { :include => :project,
55 55 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
56 56
57 57 # Returns true if usr or current user is allowed to view the issue
58 58 def visible?(usr=nil)
59 59 (usr || User.current).allowed_to?(:view_issues, self.project)
60 60 end
61 61
62 62 def after_initialize
63 63 if new_record?
64 64 # set default values for new records only
65 65 self.status ||= IssueStatus.default
66 66 self.priority ||= Enumeration.priorities.default
67 67 end
68 68 end
69 69
70 70 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
71 71 def available_custom_fields
72 72 (project && tracker) ? project.all_issue_custom_fields.select {|c| tracker.custom_fields.include? c } : []
73 73 end
74 74
75 75 def copy_from(arg)
76 76 issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
77 77 self.attributes = issue.attributes.dup
78 78 self.custom_values = issue.custom_values.collect {|v| v.clone}
79 79 self
80 80 end
81 81
82 82 # Moves/copies an issue to a new project and tracker
83 83 # Returns the moved/copied issue on success, false on failure
84 84 def move_to(new_project, new_tracker = nil, options = {})
85 85 options ||= {}
86 86 issue = options[:copy] ? self.clone : self
87 87 transaction do
88 88 if new_project && issue.project_id != new_project.id
89 89 # delete issue relations
90 90 unless Setting.cross_project_issue_relations?
91 91 issue.relations_from.clear
92 92 issue.relations_to.clear
93 93 end
94 94 # issue is moved to another project
95 95 # reassign to the category with same name if any
96 96 new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
97 97 issue.category = new_category
98 98 issue.fixed_version = nil
99 99 issue.project = new_project
100 100 end
101 101 if new_tracker
102 102 issue.tracker = new_tracker
103 103 end
104 104 if options[:copy]
105 105 issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
106 106 issue.status = self.status
107 107 end
108 108 if issue.save
109 109 unless options[:copy]
110 110 # Manually update project_id on related time entries
111 111 TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
112 112 end
113 113 else
114 114 Issue.connection.rollback_db_transaction
115 115 return false
116 116 end
117 117 end
118 118 return issue
119 119 end
120 120
121 121 def priority_id=(pid)
122 122 self.priority = nil
123 123 write_attribute(:priority_id, pid)
124 124 end
125 125
126 126 def estimated_hours=(h)
127 127 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
128 128 end
129 129
130 130 def validate
131 131 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
132 errors.add :due_date, :activerecord_error_not_a_date
132 errors.add :due_date, :not_a_date
133 133 end
134 134
135 135 if self.due_date and self.start_date and self.due_date < self.start_date
136 errors.add :due_date, :activerecord_error_greater_than_start_date
136 errors.add :due_date, :greater_than_start_date
137 137 end
138 138
139 139 if start_date && soonest_start && start_date < soonest_start
140 errors.add :start_date, :activerecord_error_invalid
140 errors.add :start_date, :invalid
141 141 end
142 142 end
143 143
144 144 def validate_on_create
145 errors.add :tracker_id, :activerecord_error_invalid unless project.trackers.include?(tracker)
145 errors.add :tracker_id, :invalid unless project.trackers.include?(tracker)
146 146 end
147 147
148 148 def before_create
149 149 # default assignment based on category
150 150 if assigned_to.nil? && category && category.assigned_to
151 151 self.assigned_to = category.assigned_to
152 152 end
153 153 end
154 154
155 155 def before_save
156 156 if @current_journal
157 157 # attributes changes
158 158 (Issue.column_names - %w(id description)).each {|c|
159 159 @current_journal.details << JournalDetail.new(:property => 'attr',
160 160 :prop_key => c,
161 161 :old_value => @issue_before_change.send(c),
162 162 :value => send(c)) unless send(c)==@issue_before_change.send(c)
163 163 }
164 164 # custom fields changes
165 165 custom_values.each {|c|
166 166 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
167 167 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
168 168 @current_journal.details << JournalDetail.new(:property => 'cf',
169 169 :prop_key => c.custom_field_id,
170 170 :old_value => @custom_values_before_change[c.custom_field_id],
171 171 :value => c.value)
172 172 }
173 173 @current_journal.save
174 174 end
175 175 # Save the issue even if the journal is not saved (because empty)
176 176 true
177 177 end
178 178
179 179 def after_save
180 180 # Reload is needed in order to get the right status
181 181 reload
182 182
183 183 # Update start/due dates of following issues
184 184 relations_from.each(&:set_issue_to_dates)
185 185
186 186 # Close duplicates if the issue was closed
187 187 if @issue_before_change && !@issue_before_change.closed? && self.closed?
188 188 duplicates.each do |duplicate|
189 189 # Reload is need in case the duplicate was updated by a previous duplicate
190 190 duplicate.reload
191 191 # Don't re-close it if it's already closed
192 192 next if duplicate.closed?
193 193 # Same user and notes
194 194 duplicate.init_journal(@current_journal.user, @current_journal.notes)
195 195 duplicate.update_attribute :status, self.status
196 196 end
197 197 end
198 198 end
199 199
200 200 def init_journal(user, notes = "")
201 201 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
202 202 @issue_before_change = self.clone
203 203 @issue_before_change.status = self.status
204 204 @custom_values_before_change = {}
205 205 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
206 206 # Make sure updated_on is updated when adding a note.
207 207 updated_on_will_change!
208 208 @current_journal
209 209 end
210 210
211 211 # Return true if the issue is closed, otherwise false
212 212 def closed?
213 213 self.status.is_closed?
214 214 end
215 215
216 216 # Returns true if the issue is overdue
217 217 def overdue?
218 218 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
219 219 end
220 220
221 221 # Users the issue can be assigned to
222 222 def assignable_users
223 223 project.assignable_users
224 224 end
225 225
226 226 # Returns an array of status that user is able to apply
227 227 def new_statuses_allowed_to(user)
228 228 statuses = status.find_new_statuses_allowed_to(user.role_for_project(project), tracker)
229 229 statuses << status unless statuses.empty?
230 230 statuses.uniq.sort
231 231 end
232 232
233 233 # Returns the mail adresses of users that should be notified for the issue
234 234 def recipients
235 235 recipients = project.recipients
236 236 # Author and assignee are always notified unless they have been locked
237 237 recipients << author.mail if author && author.active?
238 238 recipients << assigned_to.mail if assigned_to && assigned_to.active?
239 239 recipients.compact.uniq
240 240 end
241 241
242 242 def spent_hours
243 243 @spent_hours ||= time_entries.sum(:hours) || 0
244 244 end
245 245
246 246 def relations
247 247 (relations_from + relations_to).sort
248 248 end
249 249
250 250 def all_dependent_issues
251 251 dependencies = []
252 252 relations_from.each do |relation|
253 253 dependencies << relation.issue_to
254 254 dependencies += relation.issue_to.all_dependent_issues
255 255 end
256 256 dependencies
257 257 end
258 258
259 259 # Returns an array of issues that duplicate this one
260 260 def duplicates
261 261 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
262 262 end
263 263
264 264 # Returns the due date or the target due date if any
265 265 # Used on gantt chart
266 266 def due_before
267 267 due_date || (fixed_version ? fixed_version.effective_date : nil)
268 268 end
269 269
270 270 def duration
271 271 (start_date && due_date) ? due_date - start_date : 0
272 272 end
273 273
274 274 def soonest_start
275 275 @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
276 276 end
277 277
278 278 def to_s
279 279 "#{tracker} ##{id}: #{subject}"
280 280 end
281 281
282 282 private
283 283
284 284 # Callback on attachment deletion
285 285 def attachment_removed(obj)
286 286 journal = init_journal(User.current)
287 287 journal.details << JournalDetail.new(:property => 'attachment',
288 288 :prop_key => obj.id,
289 289 :old_value => obj.filename)
290 290 journal.save
291 291 end
292 292 end
@@ -1,81 +1,81
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class IssueRelation < ActiveRecord::Base
19 19 belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id'
20 20 belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id'
21 21
22 22 TYPE_RELATES = "relates"
23 23 TYPE_DUPLICATES = "duplicates"
24 24 TYPE_BLOCKS = "blocks"
25 25 TYPE_PRECEDES = "precedes"
26 26
27 27 TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 },
28 28 TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2 },
29 29 TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 },
30 30 TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 },
31 31 }.freeze
32 32
33 33 validates_presence_of :issue_from, :issue_to, :relation_type
34 34 validates_inclusion_of :relation_type, :in => TYPES.keys
35 35 validates_numericality_of :delay, :allow_nil => true
36 36 validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
37 37
38 38 attr_protected :issue_from_id, :issue_to_id
39 39
40 40 def validate
41 41 if issue_from && issue_to
42 errors.add :issue_to_id, :activerecord_error_invalid if issue_from_id == issue_to_id
43 errors.add :issue_to_id, :activerecord_error_not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations?
44 errors.add_to_base :activerecord_error_circular_dependency if issue_to.all_dependent_issues.include? issue_from
42 errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
43 errors.add :issue_to_id, :not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations?
44 errors.add_to_base :circular_dependency if issue_to.all_dependent_issues.include? issue_from
45 45 end
46 46 end
47 47
48 48 def other_issue(issue)
49 49 (self.issue_from_id == issue.id) ? issue_to : issue_from
50 50 end
51 51
52 52 def label_for(issue)
53 53 TYPES[relation_type] ? TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : :unknow
54 54 end
55 55
56 56 def before_save
57 57 if TYPE_PRECEDES == relation_type
58 58 self.delay ||= 0
59 59 else
60 60 self.delay = nil
61 61 end
62 62 set_issue_to_dates
63 63 end
64 64
65 65 def set_issue_to_dates
66 66 soonest_start = self.successor_soonest_start
67 67 if soonest_start && (!issue_to.start_date || issue_to.start_date < soonest_start)
68 68 issue_to.start_date, issue_to.due_date = successor_soonest_start, successor_soonest_start + issue_to.duration
69 69 issue_to.save
70 70 end
71 71 end
72 72
73 73 def successor_soonest_start
74 74 return nil unless (TYPE_PRECEDES == self.relation_type) && (issue_from.start_date || issue_from.due_date)
75 75 (issue_from.due_date || issue_from.start_date) + 1 + delay
76 76 end
77 77
78 78 def <=>(relation)
79 79 TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
80 80 end
81 81 end
@@ -1,239 +1,244
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class MailHandler < ActionMailer::Base
19 19 include ActionView::Helpers::SanitizeHelper
20 20
21 21 class UnauthorizedAction < StandardError; end
22 22 class MissingInformation < StandardError; end
23 23
24 24 attr_reader :email, :user
25 25
26 26 def self.receive(email, options={})
27 27 @@handler_options = options.dup
28 28
29 29 @@handler_options[:issue] ||= {}
30 30
31 31 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
32 32 @@handler_options[:allow_override] ||= []
33 33 # Project needs to be overridable if not specified
34 34 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
35 35 # Status overridable by default
36 36 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
37 37 super email
38 38 end
39 39
40 40 # Processes incoming emails
41 41 def receive(email)
42 42 @email = email
43 43 @user = User.active.find_by_mail(email.from.first.to_s.strip)
44 44 unless @user
45 45 # Unknown user => the email is ignored
46 46 # TODO: ability to create the user's account
47 47 logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info
48 48 return false
49 49 end
50 50 User.current = @user
51 51 dispatch
52 52 end
53 53
54 54 private
55 55
56 56 MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
57 57 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
58 58 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]+msg(\d+)\]}
59 59
60 60 def dispatch
61 61 headers = [email.in_reply_to, email.references].flatten.compact
62 62 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
63 63 klass, object_id = $1, $2.to_i
64 64 method_name = "receive_#{klass}_reply"
65 65 if self.class.private_instance_methods.include?(method_name)
66 66 send method_name, object_id
67 67 else
68 68 # ignoring it
69 69 end
70 70 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
71 71 receive_issue_reply(m[1].to_i)
72 72 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
73 73 receive_message_reply(m[1].to_i)
74 74 else
75 75 receive_issue
76 76 end
77 77 rescue ActiveRecord::RecordInvalid => e
78 78 # TODO: send a email to the user
79 79 logger.error e.message if logger
80 80 false
81 81 rescue MissingInformation => e
82 82 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
83 83 false
84 84 rescue UnauthorizedAction => e
85 85 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
86 86 false
87 87 end
88 88
89 89 # Creates a new issue
90 90 def receive_issue
91 91 project = target_project
92 92 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
93 93 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
94 94 priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
95 95 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
96 96
97 97 # check permission
98 98 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
99 99 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
100 100 # check workflow
101 101 if status && issue.new_statuses_allowed_to(user).include?(status)
102 102 issue.status = status
103 103 end
104 104 issue.subject = email.subject.chomp.toutf8
105 105 issue.description = plain_text_body
106 106 # custom fields
107 107 issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
108 108 if value = get_keyword(c.name, :override => true)
109 109 h[c.id] = value
110 110 end
111 111 h
112 112 end
113 113 issue.save!
114 114 add_attachments(issue)
115 115 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
116 116 # add To and Cc as watchers
117 117 add_watchers(issue)
118 118 # send notification after adding watchers so that they can reply to Redmine
119 119 Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
120 120 issue
121 121 end
122 122
123 123 def target_project
124 124 # TODO: other ways to specify project:
125 125 # * parse the email To field
126 126 # * specific project (eg. Setting.mail_handler_target_project)
127 127 target = Project.find_by_identifier(get_keyword(:project))
128 128 raise MissingInformation.new('Unable to determine target project') if target.nil?
129 129 target
130 130 end
131 131
132 132 # Adds a note to an existing issue
133 133 def receive_issue_reply(issue_id)
134 134 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
135 135
136 136 issue = Issue.find_by_id(issue_id)
137 137 return unless issue
138 138 # check permission
139 139 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
140 140 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
141 141
142 142 # add the note
143 143 journal = issue.init_journal(user, plain_text_body)
144 144 add_attachments(issue)
145 145 # check workflow
146 146 if status && issue.new_statuses_allowed_to(user).include?(status)
147 147 issue.status = status
148 148 end
149 149 issue.save!
150 150 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
151 151 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
152 152 journal
153 153 end
154 154
155 155 # Reply will be added to the issue
156 156 def receive_journal_reply(journal_id)
157 157 journal = Journal.find_by_id(journal_id)
158 158 if journal && journal.journalized_type == 'Issue'
159 159 receive_issue_reply(journal.journalized_id)
160 160 end
161 161 end
162 162
163 163 # Receives a reply to a forum message
164 164 def receive_message_reply(message_id)
165 165 message = Message.find_by_id(message_id)
166 166 if message
167 167 message = message.root
168 168 if user.allowed_to?(:add_messages, message.project) && !message.locked?
169 169 reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
170 170 :content => plain_text_body)
171 171 reply.author = user
172 172 reply.board = message.board
173 173 message.children << reply
174 174 add_attachments(reply)
175 175 reply
176 176 else
177 177 raise UnauthorizedAction
178 178 end
179 179 end
180 180 end
181 181
182 182 def add_attachments(obj)
183 183 if email.has_attachments?
184 184 email.attachments.each do |attachment|
185 185 Attachment.create(:container => obj,
186 186 :file => attachment,
187 187 :author => user,
188 188 :content_type => attachment.content_type)
189 189 end
190 190 end
191 191 end
192 192
193 193 # Adds To and Cc as watchers of the given object if the sender has the
194 194 # appropriate permission
195 195 def add_watchers(obj)
196 196 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
197 197 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
198 198 unless addresses.empty?
199 199 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
200 200 watchers.each {|w| obj.add_watcher(w)}
201 201 end
202 202 end
203 203 end
204 204
205 205 def get_keyword(attr, options={})
206 206 @keywords ||= {}
207 207 if @keywords.has_key?(attr)
208 208 @keywords[attr]
209 209 else
210 210 @keywords[attr] = begin
211 211 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}:[ \t]*(.+)\s*$/i, '')
212 212 $1.strip
213 213 elsif !@@handler_options[:issue][attr].blank?
214 214 @@handler_options[:issue][attr]
215 215 end
216 216 end
217 217 end
218 218 end
219 219
220 220 # Returns the text/plain part of the email
221 221 # If not found (eg. HTML-only email), returns the body with tags removed
222 222 def plain_text_body
223 223 return @plain_text_body unless @plain_text_body.nil?
224 224 parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
225 225 if parts.empty?
226 226 parts << @email
227 227 end
228 228 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
229 229 if plain_text_part.nil?
230 230 # no text/plain part found, assuming html-only email
231 231 # strip html tags and remove doctype directive
232 232 @plain_text_body = strip_tags(@email.body.to_s)
233 233 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
234 234 else
235 235 @plain_text_body = plain_text_part.body.to_s
236 236 end
237 237 @plain_text_body.strip!
238 238 end
239
240
241 def self.full_sanitizer
242 @full_sanitizer ||= HTML::FullSanitizer.new
243 end
239 244 end
@@ -1,306 +1,307
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Mailer < ActionMailer::Base
19 19 helper :application
20 20 helper :issues
21 21 helper :custom_fields
22 22
23 23 include ActionController::UrlWriter
24 include Redmine::I18n
24 25
25 26 def issue_add(issue)
26 27 redmine_headers 'Project' => issue.project.identifier,
27 28 'Issue-Id' => issue.id,
28 29 'Issue-Author' => issue.author.login
29 30 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
30 31 message_id issue
31 32 recipients issue.recipients
32 33 cc(issue.watcher_recipients - @recipients)
33 34 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
34 35 body :issue => issue,
35 36 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
36 37 end
37 38
38 39 def issue_edit(journal)
39 40 issue = journal.journalized
40 41 redmine_headers 'Project' => issue.project.identifier,
41 42 'Issue-Id' => issue.id,
42 43 'Issue-Author' => issue.author.login
43 44 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
44 45 message_id journal
45 46 references issue
46 47 @author = journal.user
47 48 recipients issue.recipients
48 49 # Watchers in cc
49 50 cc(issue.watcher_recipients - @recipients)
50 51 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
51 52 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
52 53 s << issue.subject
53 54 subject s
54 55 body :issue => issue,
55 56 :journal => journal,
56 57 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
57 58 end
58 59
59 60 def reminder(user, issues, days)
60 61 set_language_if_valid user.language
61 62 recipients user.mail
62 63 subject l(:mail_subject_reminder, issues.size)
63 64 body :issues => issues,
64 65 :days => days,
65 66 :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
66 67 end
67 68
68 69 def document_added(document)
69 70 redmine_headers 'Project' => document.project.identifier
70 71 recipients document.project.recipients
71 72 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
72 73 body :document => document,
73 74 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
74 75 end
75 76
76 77 def attachments_added(attachments)
77 78 container = attachments.first.container
78 79 added_to = ''
79 80 added_to_url = ''
80 81 case container.class.name
81 82 when 'Project'
82 83 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
83 84 added_to = "#{l(:label_project)}: #{container}"
84 85 when 'Version'
85 86 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
86 87 added_to = "#{l(:label_version)}: #{container.name}"
87 88 when 'Document'
88 89 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
89 90 added_to = "#{l(:label_document)}: #{container.title}"
90 91 end
91 92 redmine_headers 'Project' => container.project.identifier
92 93 recipients container.project.recipients
93 94 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
94 95 body :attachments => attachments,
95 96 :added_to => added_to,
96 97 :added_to_url => added_to_url
97 98 end
98 99
99 100 def news_added(news)
100 101 redmine_headers 'Project' => news.project.identifier
101 102 message_id news
102 103 recipients news.project.recipients
103 104 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
104 105 body :news => news,
105 106 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
106 107 end
107 108
108 109 def message_posted(message, recipients)
109 110 redmine_headers 'Project' => message.project.identifier,
110 111 'Topic-Id' => (message.parent_id || message.id)
111 112 message_id message
112 113 references message.parent unless message.parent.nil?
113 114 recipients(recipients)
114 115 subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
115 116 body :message => message,
116 117 :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
117 118 end
118 119
119 120 def account_information(user, password)
120 121 set_language_if_valid user.language
121 122 recipients user.mail
122 123 subject l(:mail_subject_register, Setting.app_title)
123 124 body :user => user,
124 125 :password => password,
125 126 :login_url => url_for(:controller => 'account', :action => 'login')
126 127 end
127 128
128 129 def account_activation_request(user)
129 130 # Send the email to all active administrators
130 131 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
131 132 subject l(:mail_subject_account_activation_request, Setting.app_title)
132 133 body :user => user,
133 134 :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
134 135 end
135 136
136 137 # A registered user's account was activated by an administrator
137 138 def account_activated(user)
138 139 set_language_if_valid user.language
139 140 recipients user.mail
140 141 subject l(:mail_subject_register, Setting.app_title)
141 142 body :user => user,
142 143 :login_url => url_for(:controller => 'account', :action => 'login')
143 144 end
144 145
145 146 def lost_password(token)
146 147 set_language_if_valid(token.user.language)
147 148 recipients token.user.mail
148 149 subject l(:mail_subject_lost_password, Setting.app_title)
149 150 body :token => token,
150 151 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
151 152 end
152 153
153 154 def register(token)
154 155 set_language_if_valid(token.user.language)
155 156 recipients token.user.mail
156 157 subject l(:mail_subject_register, Setting.app_title)
157 158 body :token => token,
158 159 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
159 160 end
160 161
161 162 def test(user)
162 163 set_language_if_valid(user.language)
163 164 recipients user.mail
164 165 subject 'Redmine test'
165 166 body :url => url_for(:controller => 'welcome')
166 167 end
167 168
168 169 # Overrides default deliver! method to prevent from sending an email
169 170 # with no recipient, cc or bcc
170 171 def deliver!(mail = @mail)
171 172 return false if (recipients.nil? || recipients.empty?) &&
172 173 (cc.nil? || cc.empty?) &&
173 174 (bcc.nil? || bcc.empty?)
174 175
175 176 # Set Message-Id and References
176 177 if @message_id_object
177 178 mail.message_id = self.class.message_id_for(@message_id_object)
178 179 end
179 180 if @references_objects
180 181 mail.references = @references_objects.collect {|o| self.class.message_id_for(o)}
181 182 end
182 183 super(mail)
183 184 end
184 185
185 186 # Sends reminders to issue assignees
186 187 # Available options:
187 188 # * :days => how many days in the future to remind about (defaults to 7)
188 189 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
189 190 # * :project => id or identifier of project to process (defaults to all projects)
190 191 def self.reminders(options={})
191 192 days = options[:days] || 7
192 193 project = options[:project] ? Project.find(options[:project]) : nil
193 194 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
194 195
195 196 s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
196 197 s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
197 198 s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
198 199 s << "#{Issue.table_name}.project_id = #{project.id}" if project
199 200 s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
200 201
201 202 issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
202 203 :conditions => s.conditions
203 204 ).group_by(&:assigned_to)
204 205 issues_by_assignee.each do |assignee, issues|
205 206 deliver_reminder(assignee, issues, days) unless assignee.nil?
206 207 end
207 208 end
208 209
209 210 private
210 211 def initialize_defaults(method_name)
211 212 super
212 213 set_language_if_valid Setting.default_language
213 214 from Setting.mail_from
214 215
215 216 # URL options
216 217 h = Setting.host_name
217 218 h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
218 219 default_url_options[:host] = h
219 220 default_url_options[:protocol] = Setting.protocol
220 221
221 222 # Common headers
222 223 headers 'X-Mailer' => 'Redmine',
223 224 'X-Redmine-Host' => Setting.host_name,
224 225 'X-Redmine-Site' => Setting.app_title
225 226 end
226 227
227 228 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
228 229 def redmine_headers(h)
229 230 h.each { |k,v| headers["X-Redmine-#{k}"] = v }
230 231 end
231 232
232 233 # Overrides the create_mail method
233 234 def create_mail
234 235 # Removes the current user from the recipients and cc
235 236 # if he doesn't want to receive notifications about what he does
236 237 @author ||= User.current
237 238 if @author.pref[:no_self_notified]
238 239 recipients.delete(@author.mail) if recipients
239 240 cc.delete(@author.mail) if cc
240 241 end
241 242 # Blind carbon copy recipients
242 243 if Setting.bcc_recipients?
243 244 bcc([recipients, cc].flatten.compact.uniq)
244 245 recipients []
245 246 cc []
246 247 end
247 248 super
248 249 end
249 250
250 251 # Renders a message with the corresponding layout
251 252 def render_message(method_name, body)
252 253 layout = method_name.to_s.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
253 254 body[:content_for_layout] = render(:file => method_name, :body => body)
254 255 ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true)
255 256 end
256 257
257 258 # for the case of plain text only
258 259 def body(*params)
259 260 value = super(*params)
260 261 if Setting.plain_text_mail?
261 262 templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}")
262 263 unless String === @body or templates.empty?
263 264 template = File.basename(templates.first)
264 265 @body[:content_for_layout] = render(:file => template, :body => @body)
265 266 @body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true)
266 267 return @body
267 268 end
268 269 end
269 270 return value
270 271 end
271 272
272 273 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
273 274 def self.controller_path
274 275 ''
275 276 end unless respond_to?('controller_path')
276 277
277 278 # Returns a predictable Message-Id for the given object
278 279 def self.message_id_for(object)
279 280 # id + timestamp should reduce the odds of a collision
280 281 # as far as we don't send multiple emails for the same object
281 282 hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{object.created_on.strftime("%Y%m%d%H%M%S")}"
282 283 host = Setting.mail_from.to_s.gsub(%r{^.*@}, '')
283 284 host = "#{::Socket.gethostname}.redmine" if host.empty?
284 285 "<#{hash}@#{host}>"
285 286 end
286 287
287 288 private
288 289
289 290 def message_id(object)
290 291 @message_id_object = object
291 292 end
292 293
293 294 def references(object)
294 295 @references_objects ||= []
295 296 @references_objects << object
296 297 end
297 298 end
298 299
299 300 # Patch TMail so that message_id is not overwritten
300 301 module TMail
301 302 class Mail
302 303 def add_message_id( fqdn = nil )
303 304 self.message_id ||= ::TMail::new_message_id(fqdn)
304 305 end
305 306 end
306 307 end
@@ -1,42 +1,42
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Member < ActiveRecord::Base
19 19 belongs_to :user
20 20 belongs_to :role
21 21 belongs_to :project
22 22
23 23 validates_presence_of :role, :user, :project
24 24 validates_uniqueness_of :user_id, :scope => :project_id
25 25
26 26 def validate
27 errors.add :role_id, :activerecord_error_invalid if role && !role.member?
27 errors.add :role_id, :invalid if role && !role.member?
28 28 end
29 29
30 30 def name
31 31 self.user.name
32 32 end
33 33
34 34 def <=>(member)
35 35 role == member.role ? (user <=> member.user) : (role <=> member.role)
36 36 end
37 37
38 38 def before_destroy
39 39 # remove category based auto assignments for this member
40 40 IssueCategory.update_all "assigned_to_id = NULL", ["project_id = ? AND assigned_to_id = ?", project.id, user.id]
41 41 end
42 42 end
@@ -1,323 +1,323
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Project < ActiveRecord::Base
19 19 # Project statuses
20 20 STATUS_ACTIVE = 1
21 21 STATUS_ARCHIVED = 9
22 22
23 23 has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
24 24 has_many :users, :through => :members
25 25 has_many :enabled_modules, :dependent => :delete_all
26 26 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
27 27 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
28 28 has_many :issue_changes, :through => :issues, :source => :journals
29 29 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
30 30 has_many :time_entries, :dependent => :delete_all
31 31 has_many :queries, :dependent => :delete_all
32 32 has_many :documents, :dependent => :destroy
33 33 has_many :news, :dependent => :delete_all, :include => :author
34 34 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
35 35 has_many :boards, :dependent => :destroy, :order => "position ASC"
36 36 has_one :repository, :dependent => :destroy
37 37 has_many :changesets, :through => :repository
38 38 has_one :wiki, :dependent => :destroy
39 39 # Custom field for the project issues
40 40 has_and_belongs_to_many :issue_custom_fields,
41 41 :class_name => 'IssueCustomField',
42 42 :order => "#{CustomField.table_name}.position",
43 43 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
44 44 :association_foreign_key => 'custom_field_id'
45 45
46 46 acts_as_nested_set :order => 'name', :dependent => :destroy
47 47 acts_as_attachable :view_permission => :view_files,
48 48 :delete_permission => :manage_files
49 49
50 50 acts_as_customizable
51 51 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
52 52 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
53 53 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
54 54 :author => nil
55 55
56 56 attr_protected :status, :enabled_module_names
57 57
58 58 validates_presence_of :name, :identifier
59 59 validates_uniqueness_of :name, :identifier
60 60 validates_associated :repository, :wiki
61 61 validates_length_of :name, :maximum => 30
62 62 validates_length_of :homepage, :maximum => 255
63 63 validates_length_of :identifier, :in => 2..20
64 64 validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
65 65
66 66 before_destroy :delete_all_members
67 67
68 68 named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
69 69 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
70 70 named_scope :public, { :conditions => { :is_public => true } }
71 71 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
72 72
73 73 def identifier=(identifier)
74 74 super unless identifier_frozen?
75 75 end
76 76
77 77 def identifier_frozen?
78 78 errors[:identifier].nil? && !(new_record? || identifier.blank?)
79 79 end
80 80
81 81 def issues_with_subprojects(include_subprojects=false)
82 82 conditions = nil
83 83 if include_subprojects
84 84 ids = [id] + descendants.collect(&:id)
85 85 conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
86 86 end
87 87 conditions ||= ["#{Project.table_name}.id = ?", id]
88 88 # Quick and dirty fix for Rails 2 compatibility
89 89 Issue.send(:with_scope, :find => { :conditions => conditions }) do
90 90 Version.send(:with_scope, :find => { :conditions => conditions }) do
91 91 yield
92 92 end
93 93 end
94 94 end
95 95
96 96 # returns latest created projects
97 97 # non public projects will be returned only if user is a member of those
98 98 def self.latest(user=nil, count=5)
99 99 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
100 100 end
101 101
102 102 def self.visible_by(user=nil)
103 103 user ||= User.current
104 104 if user && user.admin?
105 105 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
106 106 elsif user && user.memberships.any?
107 107 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
108 108 else
109 109 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
110 110 end
111 111 end
112 112
113 113 def self.allowed_to_condition(user, permission, options={})
114 114 statements = []
115 115 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
116 116 if perm = Redmine::AccessControl.permission(permission)
117 117 unless perm.project_module.nil?
118 118 # If the permission belongs to a project module, make sure the module is enabled
119 119 base_statement << " AND EXISTS (SELECT em.id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}' AND em.project_id=#{Project.table_name}.id)"
120 120 end
121 121 end
122 122 if options[:project]
123 123 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
124 124 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
125 125 base_statement = "(#{project_statement}) AND (#{base_statement})"
126 126 end
127 127 if user.admin?
128 128 # no restriction
129 129 else
130 130 statements << "1=0"
131 131 if user.logged?
132 132 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
133 133 allowed_project_ids = user.memberships.select {|m| m.role.allowed_to?(permission)}.collect {|m| m.project_id}
134 134 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
135 135 elsif Role.anonymous.allowed_to?(permission)
136 136 # anonymous user allowed on public project
137 137 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
138 138 else
139 139 # anonymous user is not authorized
140 140 end
141 141 end
142 142 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
143 143 end
144 144
145 145 def project_condition(with_subprojects)
146 146 cond = "#{Project.table_name}.id = #{id}"
147 147 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
148 148 cond
149 149 end
150 150
151 151 def self.find(*args)
152 152 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
153 153 project = find_by_identifier(*args)
154 154 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
155 155 project
156 156 else
157 157 super
158 158 end
159 159 end
160 160
161 161 def to_param
162 162 # id is used for projects with a numeric identifier (compatibility)
163 163 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
164 164 end
165 165
166 166 def active?
167 167 self.status == STATUS_ACTIVE
168 168 end
169 169
170 170 # Archives the project and its descendants recursively
171 171 def archive
172 172 # Archive subprojects if any
173 173 children.each do |subproject|
174 174 subproject.archive
175 175 end
176 176 update_attribute :status, STATUS_ARCHIVED
177 177 end
178 178
179 179 # Unarchives the project
180 180 # All its ancestors must be active
181 181 def unarchive
182 182 return false if ancestors.detect {|a| !a.active?}
183 183 update_attribute :status, STATUS_ACTIVE
184 184 end
185 185
186 186 # Returns an array of projects the project can be moved to
187 187 def possible_parents
188 188 @possible_parents ||= (Project.active.find(:all) - self_and_descendants)
189 189 end
190 190
191 191 # Sets the parent of the project
192 192 # Argument can be either a Project, a String, a Fixnum or nil
193 193 def set_parent!(p)
194 194 unless p.nil? || p.is_a?(Project)
195 195 if p.to_s.blank?
196 196 p = nil
197 197 else
198 198 p = Project.find_by_id(p)
199 199 return false unless p
200 200 end
201 201 end
202 202 if p == parent && !p.nil?
203 203 # Nothing to do
204 204 true
205 205 elsif p.nil? || (p.active? && move_possible?(p))
206 206 # Insert the project so that target's children or root projects stay alphabetically sorted
207 207 sibs = (p.nil? ? self.class.roots : p.children)
208 208 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
209 209 if to_be_inserted_before
210 210 move_to_left_of(to_be_inserted_before)
211 211 elsif p.nil?
212 212 if sibs.empty?
213 213 # move_to_root adds the project in first (ie. left) position
214 214 move_to_root
215 215 else
216 216 move_to_right_of(sibs.last) unless self == sibs.last
217 217 end
218 218 else
219 219 # move_to_child_of adds the project in last (ie.right) position
220 220 move_to_child_of(p)
221 221 end
222 222 true
223 223 else
224 224 # Can not move to the given target
225 225 false
226 226 end
227 227 end
228 228
229 229 # Returns an array of the trackers used by the project and its active sub projects
230 230 def rolled_up_trackers
231 231 @rolled_up_trackers ||=
232 232 Tracker.find(:all, :include => :projects,
233 233 :select => "DISTINCT #{Tracker.table_name}.*",
234 234 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
235 235 :order => "#{Tracker.table_name}.position")
236 236 end
237 237
238 238 # Deletes all project's members
239 239 def delete_all_members
240 240 Member.delete_all(['project_id = ?', id])
241 241 end
242 242
243 243 # Users issues can be assigned to
244 244 def assignable_users
245 245 members.select {|m| m.role.assignable?}.collect {|m| m.user}.sort
246 246 end
247 247
248 248 # Returns the mail adresses of users that should be always notified on project events
249 249 def recipients
250 250 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
251 251 end
252 252
253 253 # Returns an array of all custom fields enabled for project issues
254 254 # (explictly associated custom fields and custom fields enabled for all projects)
255 255 def all_issue_custom_fields
256 256 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
257 257 end
258 258
259 259 def project
260 260 self
261 261 end
262 262
263 263 def <=>(project)
264 264 name.downcase <=> project.name.downcase
265 265 end
266 266
267 267 def to_s
268 268 name
269 269 end
270 270
271 271 # Returns a short description of the projects (first lines)
272 272 def short_description(length = 255)
273 273 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
274 274 end
275 275
276 276 def allows_to?(action)
277 277 if action.is_a? Hash
278 278 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
279 279 else
280 280 allowed_permissions.include? action
281 281 end
282 282 end
283 283
284 284 def module_enabled?(module_name)
285 285 module_name = module_name.to_s
286 286 enabled_modules.detect {|m| m.name == module_name}
287 287 end
288 288
289 289 def enabled_module_names=(module_names)
290 290 if module_names && module_names.is_a?(Array)
291 291 module_names = module_names.collect(&:to_s)
292 292 # remove disabled modules
293 293 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
294 294 # add new modules
295 295 module_names.each {|name| enabled_modules << EnabledModule.new(:name => name)}
296 296 else
297 297 enabled_modules.clear
298 298 end
299 299 end
300 300
301 301 # Returns an auto-generated project identifier based on the last identifier used
302 302 def self.next_identifier
303 303 p = Project.find(:first, :order => 'created_on DESC')
304 304 p.nil? ? nil : p.identifier.to_s.succ
305 305 end
306 306
307 307 protected
308 308 def validate
309 errors.add(:identifier, :activerecord_error_invalid) if !identifier.blank? && identifier.match(/^\d*$/)
309 errors.add(:identifier, :invalid) if !identifier.blank? && identifier.match(/^\d*$/)
310 310 end
311 311
312 312 private
313 313 def allowed_permissions
314 314 @allowed_permissions ||= begin
315 315 module_names = enabled_modules.collect {|m| m.name}
316 316 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
317 317 end
318 318 end
319 319
320 320 def allowed_actions
321 321 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
322 322 end
323 323 end
@@ -1,413 +1,411
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class QueryColumn
19 19 attr_accessor :name, :sortable, :default_order
20 include GLoc
20 include Redmine::I18n
21 21
22 22 def initialize(name, options={})
23 23 self.name = name
24 24 self.sortable = options[:sortable]
25 25 self.default_order = options[:default_order]
26 26 end
27 27
28 28 def caption
29 set_language_if_valid(User.current.language)
30 29 l("field_#{name}")
31 30 end
32 31 end
33 32
34 33 class QueryCustomFieldColumn < QueryColumn
35 34
36 35 def initialize(custom_field)
37 36 self.name = "cf_#{custom_field.id}".to_sym
38 37 self.sortable = custom_field.order_statement || false
39 38 @cf = custom_field
40 39 end
41 40
42 41 def caption
43 42 @cf.name
44 43 end
45 44
46 45 def custom_field
47 46 @cf
48 47 end
49 48 end
50 49
51 50 class Query < ActiveRecord::Base
52 51 belongs_to :project
53 52 belongs_to :user
54 53 serialize :filters
55 54 serialize :column_names
56 55
57 56 attr_protected :project_id, :user_id
58 57
59 58 validates_presence_of :name, :on => :save
60 59 validates_length_of :name, :maximum => 255
61 60
62 61 @@operators = { "=" => :label_equals,
63 62 "!" => :label_not_equals,
64 63 "o" => :label_open_issues,
65 64 "c" => :label_closed_issues,
66 65 "!*" => :label_none,
67 66 "*" => :label_all,
68 67 ">=" => '>=',
69 68 "<=" => '<=',
70 69 "<t+" => :label_in_less_than,
71 70 ">t+" => :label_in_more_than,
72 71 "t+" => :label_in,
73 72 "t" => :label_today,
74 73 "w" => :label_this_week,
75 74 ">t-" => :label_less_than_ago,
76 75 "<t-" => :label_more_than_ago,
77 76 "t-" => :label_ago,
78 77 "~" => :label_contains,
79 78 "!~" => :label_not_contains }
80 79
81 80 cattr_reader :operators
82 81
83 82 @@operators_by_filter_type = { :list => [ "=", "!" ],
84 83 :list_status => [ "o", "=", "!", "c", "*" ],
85 84 :list_optional => [ "=", "!", "!*", "*" ],
86 85 :list_subprojects => [ "*", "!*", "=" ],
87 86 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
88 87 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
89 88 :string => [ "=", "~", "!", "!~" ],
90 89 :text => [ "~", "!~" ],
91 90 :integer => [ "=", ">=", "<=", "!*", "*" ] }
92 91
93 92 cattr_reader :operators_by_filter_type
94 93
95 94 @@available_columns = [
96 95 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"),
97 96 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"),
98 97 QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc'),
99 98 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
100 99 QueryColumn.new(:author),
101 100 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname"]),
102 101 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
103 102 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
104 103 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc'),
105 104 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
106 105 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
107 106 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
108 107 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
109 108 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
110 109 ]
111 110 cattr_reader :available_columns
112 111
113 112 def initialize(attributes = nil)
114 113 super attributes
115 114 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
116 set_language_if_valid(User.current.language)
117 115 end
118 116
119 117 def after_initialize
120 118 # Store the fact that project is nil (used in #editable_by?)
121 119 @is_for_all = project.nil?
122 120 end
123 121
124 122 def validate
125 123 filters.each_key do |field|
126 errors.add label_for(field), :activerecord_error_blank unless
124 errors.add label_for(field), :blank unless
127 125 # filter requires one or more values
128 126 (values_for(field) and !values_for(field).first.blank?) or
129 127 # filter doesn't require any value
130 128 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
131 129 end if filters
132 130 end
133 131
134 132 def editable_by?(user)
135 133 return false unless user
136 134 # Admin can edit them all and regular users can edit their private queries
137 135 return true if user.admin? || (!is_public && self.user_id == user.id)
138 136 # Members can not edit public queries that are for all project (only admin is allowed to)
139 137 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
140 138 end
141 139
142 140 def available_filters
143 141 return @available_filters if @available_filters
144 142
145 143 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
146 144
147 145 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
148 146 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
149 147 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI'], :order => 'position').collect{|s| [s.name, s.id.to_s] } },
150 148 "subject" => { :type => :text, :order => 8 },
151 149 "created_on" => { :type => :date_past, :order => 9 },
152 150 "updated_on" => { :type => :date_past, :order => 10 },
153 151 "start_date" => { :type => :date, :order => 11 },
154 152 "due_date" => { :type => :date, :order => 12 },
155 153 "estimated_hours" => { :type => :integer, :order => 13 },
156 154 "done_ratio" => { :type => :integer, :order => 14 }}
157 155
158 156 user_values = []
159 157 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
160 158 if project
161 159 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
162 160 else
163 161 # members of the user's projects
164 162 user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
165 163 end
166 164 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
167 165 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
168 166
169 167 if User.current.logged?
170 168 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
171 169 end
172 170
173 171 if project
174 172 # project specific filters
175 173 unless @project.issue_categories.empty?
176 174 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
177 175 end
178 176 unless @project.versions.empty?
179 177 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
180 178 end
181 179 unless @project.descendants.active.empty?
182 180 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
183 181 end
184 182 add_custom_fields_filters(@project.all_issue_custom_fields)
185 183 else
186 184 # global filters for cross project issue list
187 185 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
188 186 end
189 187 @available_filters
190 188 end
191 189
192 190 def add_filter(field, operator, values)
193 191 # values must be an array
194 192 return unless values and values.is_a? Array # and !values.first.empty?
195 193 # check if field is defined as an available filter
196 194 if available_filters.has_key? field
197 195 filter_options = available_filters[field]
198 196 # check if operator is allowed for that filter
199 197 #if @@operators_by_filter_type[filter_options[:type]].include? operator
200 198 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
201 199 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
202 200 #end
203 201 filters[field] = {:operator => operator, :values => values }
204 202 end
205 203 end
206 204
207 205 def add_short_filter(field, expression)
208 206 return unless expression
209 207 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
210 208 add_filter field, (parms[0] || "="), [parms[1] || ""]
211 209 end
212 210
213 211 def has_filter?(field)
214 212 filters and filters[field]
215 213 end
216 214
217 215 def operator_for(field)
218 216 has_filter?(field) ? filters[field][:operator] : nil
219 217 end
220 218
221 219 def values_for(field)
222 220 has_filter?(field) ? filters[field][:values] : nil
223 221 end
224 222
225 223 def label_for(field)
226 224 label = available_filters[field][:name] if available_filters.has_key?(field)
227 225 label ||= field.gsub(/\_id$/, "")
228 226 end
229 227
230 228 def available_columns
231 229 return @available_columns if @available_columns
232 230 @available_columns = Query.available_columns
233 231 @available_columns += (project ?
234 232 project.all_issue_custom_fields :
235 233 IssueCustomField.find(:all, :conditions => {:is_for_all => true})
236 234 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
237 235 end
238 236
239 237 def columns
240 238 if has_default_columns?
241 239 available_columns.select {|c| Setting.issue_list_default_columns.include?(c.name.to_s) }
242 240 else
243 241 # preserve the column_names order
244 242 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
245 243 end
246 244 end
247 245
248 246 def column_names=(names)
249 247 names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
250 248 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
251 249 write_attribute(:column_names, names)
252 250 end
253 251
254 252 def has_column?(column)
255 253 column_names && column_names.include?(column.name)
256 254 end
257 255
258 256 def has_default_columns?
259 257 column_names.nil? || column_names.empty?
260 258 end
261 259
262 260 def project_statement
263 261 project_clauses = []
264 262 if project && !@project.descendants.active.empty?
265 263 ids = [project.id]
266 264 if has_filter?("subproject_id")
267 265 case operator_for("subproject_id")
268 266 when '='
269 267 # include the selected subprojects
270 268 ids += values_for("subproject_id").each(&:to_i)
271 269 when '!*'
272 270 # main project only
273 271 else
274 272 # all subprojects
275 273 ids += project.descendants.collect(&:id)
276 274 end
277 275 elsif Setting.display_subprojects_issues?
278 276 ids += project.descendants.collect(&:id)
279 277 end
280 278 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
281 279 elsif project
282 280 project_clauses << "#{Project.table_name}.id = %d" % project.id
283 281 end
284 282 project_clauses << Project.allowed_to_condition(User.current, :view_issues)
285 283 project_clauses.join(' AND ')
286 284 end
287 285
288 286 def statement
289 287 # filters clauses
290 288 filters_clauses = []
291 289 filters.each_key do |field|
292 290 next if field == "subproject_id"
293 291 v = values_for(field).clone
294 292 next unless v and !v.empty?
295 293 operator = operator_for(field)
296 294
297 295 # "me" value subsitution
298 296 if %w(assigned_to_id author_id watcher_id).include?(field)
299 297 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
300 298 end
301 299
302 300 sql = ''
303 301 if field =~ /^cf_(\d+)$/
304 302 # custom field
305 303 db_table = CustomValue.table_name
306 304 db_field = 'value'
307 305 is_custom_filter = true
308 306 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 "
309 307 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
310 308 elsif field == 'watcher_id'
311 309 db_table = Watcher.table_name
312 310 db_field = 'user_id'
313 311 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
314 312 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
315 313 else
316 314 # regular field
317 315 db_table = Issue.table_name
318 316 db_field = field
319 317 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
320 318 end
321 319 filters_clauses << sql
322 320
323 321 end if filters and valid?
324 322
325 323 (filters_clauses << project_statement).join(' AND ')
326 324 end
327 325
328 326 private
329 327
330 328 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
331 329 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
332 330 sql = ''
333 331 case operator
334 332 when "="
335 333 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
336 334 when "!"
337 335 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
338 336 when "!*"
339 337 sql = "#{db_table}.#{db_field} IS NULL"
340 338 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
341 339 when "*"
342 340 sql = "#{db_table}.#{db_field} IS NOT NULL"
343 341 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
344 342 when ">="
345 343 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
346 344 when "<="
347 345 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
348 346 when "o"
349 347 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
350 348 when "c"
351 349 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
352 350 when ">t-"
353 351 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
354 352 when "<t-"
355 353 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
356 354 when "t-"
357 355 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
358 356 when ">t+"
359 357 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
360 358 when "<t+"
361 359 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
362 360 when "t+"
363 361 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
364 362 when "t"
365 363 sql = date_range_clause(db_table, db_field, 0, 0)
366 364 when "w"
367 365 from = l(:general_first_day_of_week) == '7' ?
368 366 # week starts on sunday
369 367 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
370 368 # week starts on monday (Rails default)
371 369 Time.now.at_beginning_of_week
372 370 sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
373 371 when "~"
374 372 sql = "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(value.first)}%'"
375 373 when "!~"
376 374 sql = "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(value.first)}%'"
377 375 end
378 376
379 377 return sql
380 378 end
381 379
382 380 def add_custom_fields_filters(custom_fields)
383 381 @available_filters ||= {}
384 382
385 383 custom_fields.select(&:is_filter?).each do |field|
386 384 case field.field_format
387 385 when "text"
388 386 options = { :type => :text, :order => 20 }
389 387 when "list"
390 388 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
391 389 when "date"
392 390 options = { :type => :date, :order => 20 }
393 391 when "bool"
394 392 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
395 393 else
396 394 options = { :type => :string, :order => 20 }
397 395 end
398 396 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
399 397 end
400 398 end
401 399
402 400 # Returns a SQL clause for a date or datetime field.
403 401 def date_range_clause(table, field, from, to)
404 402 s = []
405 403 if from
406 404 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
407 405 end
408 406 if to
409 407 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
410 408 end
411 409 s.join(' AND ')
412 410 end
413 411 end
@@ -1,180 +1,180
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Repository < ActiveRecord::Base
19 19 belongs_to :project
20 20 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
21 21 has_many :changes, :through => :changesets
22 22
23 23 # Raw SQL to delete changesets and changes in the database
24 24 # has_many :changesets, :dependent => :destroy is too slow for big repositories
25 25 before_destroy :clear_changesets
26 26
27 27 # Checks if the SCM is enabled when creating a repository
28 validate_on_create { |r| r.errors.add(:type, :activerecord_error_invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
28 validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
29 29
30 30 # Removes leading and trailing whitespace
31 31 def url=(arg)
32 32 write_attribute(:url, arg ? arg.to_s.strip : nil)
33 33 end
34 34
35 35 # Removes leading and trailing whitespace
36 36 def root_url=(arg)
37 37 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
38 38 end
39 39
40 40 def scm
41 41 @scm ||= self.scm_adapter.new url, root_url, login, password
42 42 update_attribute(:root_url, @scm.root_url) if root_url.blank?
43 43 @scm
44 44 end
45 45
46 46 def scm_name
47 47 self.class.scm_name
48 48 end
49 49
50 50 def supports_cat?
51 51 scm.supports_cat?
52 52 end
53 53
54 54 def supports_annotate?
55 55 scm.supports_annotate?
56 56 end
57 57
58 58 def entry(path=nil, identifier=nil)
59 59 scm.entry(path, identifier)
60 60 end
61 61
62 62 def entries(path=nil, identifier=nil)
63 63 scm.entries(path, identifier)
64 64 end
65 65
66 66 def properties(path, identifier=nil)
67 67 scm.properties(path, identifier)
68 68 end
69 69
70 70 def cat(path, identifier=nil)
71 71 scm.cat(path, identifier)
72 72 end
73 73
74 74 def diff(path, rev, rev_to)
75 75 scm.diff(path, rev, rev_to)
76 76 end
77 77
78 78 # Default behaviour: we search in cached changesets
79 79 def changesets_for_path(path, options={})
80 80 path = "/#{path}" unless path.starts_with?('/')
81 81 Change.find(:all, :include => {:changeset => :user},
82 82 :conditions => ["repository_id = ? AND path = ?", id, path],
83 83 :order => "committed_on DESC, #{Changeset.table_name}.id DESC",
84 84 :limit => options[:limit]).collect(&:changeset)
85 85 end
86 86
87 87 # Returns a path relative to the url of the repository
88 88 def relative_path(path)
89 89 path
90 90 end
91 91
92 92 def latest_changeset
93 93 @latest_changeset ||= changesets.find(:first)
94 94 end
95 95
96 96 def scan_changesets_for_issue_ids
97 97 self.changesets.each(&:scan_comment_for_issue_ids)
98 98 end
99 99
100 100 # Returns an array of committers usernames and associated user_id
101 101 def committers
102 102 @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
103 103 end
104 104
105 105 # Maps committers username to a user ids
106 106 def committer_ids=(h)
107 107 if h.is_a?(Hash)
108 108 committers.each do |committer, user_id|
109 109 new_user_id = h[committer]
110 110 if new_user_id && (new_user_id.to_i != user_id.to_i)
111 111 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
112 112 Changeset.update_all("user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", ["repository_id = ? AND committer = ?", id, committer])
113 113 end
114 114 end
115 115 @committers = nil
116 116 true
117 117 else
118 118 false
119 119 end
120 120 end
121 121
122 122 # Returns the Redmine User corresponding to the given +committer+
123 123 # It will return nil if the committer is not yet mapped and if no User
124 124 # with the same username or email was found
125 125 def find_committer_user(committer)
126 126 if committer
127 127 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
128 128 if c && c.user
129 129 c.user
130 130 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
131 131 username, email = $1.strip, $3
132 132 u = User.find_by_login(username)
133 133 u ||= User.find_by_mail(email) unless email.blank?
134 134 u
135 135 end
136 136 end
137 137 end
138 138
139 139 # fetch new changesets for all repositories
140 140 # can be called periodically by an external script
141 141 # eg. ruby script/runner "Repository.fetch_changesets"
142 142 def self.fetch_changesets
143 143 find(:all).each(&:fetch_changesets)
144 144 end
145 145
146 146 # scan changeset comments to find related and fixed issues for all repositories
147 147 def self.scan_changesets_for_issue_ids
148 148 find(:all).each(&:scan_changesets_for_issue_ids)
149 149 end
150 150
151 151 def self.scm_name
152 152 'Abstract'
153 153 end
154 154
155 155 def self.available_scm
156 156 subclasses.collect {|klass| [klass.scm_name, klass.name]}
157 157 end
158 158
159 159 def self.factory(klass_name, *args)
160 160 klass = "Repository::#{klass_name}".constantize
161 161 klass.new(*args)
162 162 rescue
163 163 nil
164 164 end
165 165
166 166 private
167 167
168 168 def before_save
169 169 # Strips url and root_url
170 170 url.strip!
171 171 root_url.strip!
172 172 true
173 173 end
174 174
175 175 def clear_changesets
176 176 connection.delete("DELETE FROM changes WHERE changes.changeset_id IN (SELECT changesets.id FROM changesets WHERE changesets.repository_id = #{id})")
177 177 connection.delete("DELETE FROM changesets_issues WHERE changesets_issues.changeset_id IN (SELECT changesets.id FROM changesets WHERE changesets.repository_id = #{id})")
178 178 connection.delete("DELETE FROM changesets WHERE changesets.repository_id = #{id}")
179 179 end
180 180 end
@@ -1,79 +1,79
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class TimeEntry < ActiveRecord::Base
19 19 # could have used polymorphic association
20 20 # project association here allows easy loading of time entries at project level with one database trip
21 21 belongs_to :project
22 22 belongs_to :issue
23 23 belongs_to :user
24 24 belongs_to :activity, :class_name => 'Enumeration', :foreign_key => :activity_id
25 25
26 26 attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
27 27
28 28 acts_as_customizable
29 acts_as_event :title => Proc.new {|o| "#{o.user}: #{lwr(:label_f_hour, o.hours)} (#{(o.issue || o.project).event_title})"},
29 acts_as_event :title => Proc.new {|o| "#{o.user}: #{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"},
30 30 :url => Proc.new {|o| {:controller => 'timelog', :action => 'details', :project_id => o.project}},
31 31 :author => :user,
32 32 :description => :comments
33 33
34 34 validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
35 validates_numericality_of :hours, :allow_nil => true, :message => :activerecord_error_invalid
35 validates_numericality_of :hours, :allow_nil => true, :message => :invalid
36 36 validates_length_of :comments, :maximum => 255, :allow_nil => true
37 37
38 38 def after_initialize
39 39 if new_record? && self.activity.nil?
40 40 if default_activity = Enumeration.activities.default
41 41 self.activity_id = default_activity.id
42 42 end
43 43 end
44 44 end
45 45
46 46 def before_validation
47 47 self.project = issue.project if issue && project.nil?
48 48 end
49 49
50 50 def validate
51 errors.add :hours, :activerecord_error_invalid if hours && (hours < 0 || hours >= 1000)
52 errors.add :project_id, :activerecord_error_invalid if project.nil?
53 errors.add :issue_id, :activerecord_error_invalid if (issue_id && !issue) || (issue && project!=issue.project)
51 errors.add :hours, :invalid if hours && (hours < 0 || hours >= 1000)
52 errors.add :project_id, :invalid if project.nil?
53 errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project)
54 54 end
55 55
56 56 def hours=(h)
57 57 write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h)
58 58 end
59 59
60 60 # tyear, tmonth, tweek assigned where setting spent_on attributes
61 61 # these attributes make time aggregations easier
62 62 def spent_on=(date)
63 63 super
64 64 self.tyear = spent_on ? spent_on.year : nil
65 65 self.tmonth = spent_on ? spent_on.month : nil
66 66 self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
67 67 end
68 68
69 69 # Returns true if the time entry can be edited by usr, otherwise false
70 70 def editable_by?(usr)
71 71 (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
72 72 end
73 73
74 74 def self.visible_by(usr)
75 75 with_scope(:find => { :conditions => Project.allowed_to_condition(usr, :view_time_entries) }) do
76 76 yield
77 77 end
78 78 end
79 79 end
@@ -1,143 +1,143
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Version < ActiveRecord::Base
19 19 before_destroy :check_integrity
20 20 belongs_to :project
21 21 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
22 22 acts_as_attachable :view_permission => :view_files,
23 23 :delete_permission => :manage_files
24 24
25 25 validates_presence_of :name
26 26 validates_uniqueness_of :name, :scope => [:project_id]
27 27 validates_length_of :name, :maximum => 60
28 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => 'activerecord_error_not_a_date', :allow_nil => true
28 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
29 29
30 30 def start_date
31 31 effective_date
32 32 end
33 33
34 34 def due_date
35 35 effective_date
36 36 end
37 37
38 38 # Returns the total estimated time for this version
39 39 def estimated_hours
40 40 @estimated_hours ||= fixed_issues.sum(:estimated_hours).to_f
41 41 end
42 42
43 43 # Returns the total reported time for this version
44 44 def spent_hours
45 45 @spent_hours ||= TimeEntry.sum(:hours, :include => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
46 46 end
47 47
48 48 # Returns true if the version is completed: due date reached and no open issues
49 49 def completed?
50 50 effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
51 51 end
52 52
53 53 def completed_pourcent
54 54 if issues_count == 0
55 55 0
56 56 elsif open_issues_count == 0
57 57 100
58 58 else
59 59 issues_progress(false) + issues_progress(true)
60 60 end
61 61 end
62 62
63 63 def closed_pourcent
64 64 if issues_count == 0
65 65 0
66 66 else
67 67 issues_progress(false)
68 68 end
69 69 end
70 70
71 71 # Returns true if the version is overdue: due date reached and some open issues
72 72 def overdue?
73 73 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
74 74 end
75 75
76 76 # Returns assigned issues count
77 77 def issues_count
78 78 @issue_count ||= fixed_issues.count
79 79 end
80 80
81 81 def open_issues_count
82 82 @open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status)
83 83 end
84 84
85 85 def closed_issues_count
86 86 @closed_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, true], :include => :status)
87 87 end
88 88
89 89 def wiki_page
90 90 if project.wiki && !wiki_page_title.blank?
91 91 @wiki_page ||= project.wiki.find_page(wiki_page_title)
92 92 end
93 93 @wiki_page
94 94 end
95 95
96 96 def to_s; name end
97 97
98 98 # Versions are sorted by effective_date and name
99 99 # Those with no effective_date are at the end, sorted by name
100 100 def <=>(version)
101 101 if self.effective_date
102 102 version.effective_date ? (self.effective_date == version.effective_date ? self.name <=> version.name : self.effective_date <=> version.effective_date) : -1
103 103 else
104 104 version.effective_date ? 1 : (self.name <=> version.name)
105 105 end
106 106 end
107 107
108 108 private
109 109 def check_integrity
110 110 raise "Can't delete version" if self.fixed_issues.find(:first)
111 111 end
112 112
113 113 # Returns the average estimated time of assigned issues
114 114 # or 1 if no issue has an estimated time
115 115 # Used to weigth unestimated issues in progress calculation
116 116 def estimated_average
117 117 if @estimated_average.nil?
118 118 average = fixed_issues.average(:estimated_hours).to_f
119 119 if average == 0
120 120 average = 1
121 121 end
122 122 @estimated_average = average
123 123 end
124 124 @estimated_average
125 125 end
126 126
127 127 # Returns the total progress of open or closed issues
128 128 def issues_progress(open)
129 129 @issues_progress ||= {}
130 130 @issues_progress[open] ||= begin
131 131 progress = 0
132 132 if issues_count > 0
133 133 ratio = open ? 'done_ratio' : 100
134 134 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
135 135 :include => :status,
136 136 :conditions => ["is_closed = ?", !open]).to_f
137 137
138 138 progress = done / (estimated_average * issues_count)
139 139 end
140 140 progress
141 141 end
142 142 end
143 143 end
@@ -1,30 +1,30
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Watcher < ActiveRecord::Base
19 19 belongs_to :watchable, :polymorphic => true
20 20 belongs_to :user
21 21
22 22 validates_presence_of :user
23 23 validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id]
24 24
25 25 protected
26 26
27 27 def validate
28 errors.add :user_id, :activerecord_error_invalid unless user.nil? || user.active?
28 errors.add :user_id, :invalid unless user.nil? || user.active?
29 29 end
30 30 end
@@ -1,188 +1,188
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'diff'
19 19 require 'enumerator'
20 20
21 21 class WikiPage < ActiveRecord::Base
22 22 belongs_to :wiki
23 23 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
24 24 acts_as_attachable :delete_permission => :delete_wiki_pages_attachments
25 25 acts_as_tree :order => 'title'
26 26
27 27 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
28 28 :description => :text,
29 29 :datetime => :created_on,
30 30 :url => Proc.new {|o| {:controller => 'wiki', :id => o.wiki.project_id, :page => o.title}}
31 31
32 32 acts_as_searchable :columns => ['title', 'text'],
33 33 :include => [{:wiki => :project}, :content],
34 34 :project_key => "#{Wiki.table_name}.project_id"
35 35
36 36 attr_accessor :redirect_existing_links
37 37
38 38 validates_presence_of :title
39 39 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
40 40 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
41 41 validates_associated :content
42 42
43 43 def title=(value)
44 44 value = Wiki.titleize(value)
45 45 @previous_title = read_attribute(:title) if @previous_title.blank?
46 46 write_attribute(:title, value)
47 47 end
48 48
49 49 def before_save
50 50 self.title = Wiki.titleize(title)
51 51 # Manage redirects if the title has changed
52 52 if !@previous_title.blank? && (@previous_title != title) && !new_record?
53 53 # Update redirects that point to the old title
54 54 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
55 55 r.redirects_to = title
56 56 r.title == r.redirects_to ? r.destroy : r.save
57 57 end
58 58 # Remove redirects for the new title
59 59 wiki.redirects.find_all_by_title(title).each(&:destroy)
60 60 # Create a redirect to the new title
61 61 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
62 62 @previous_title = nil
63 63 end
64 64 end
65 65
66 66 def before_destroy
67 67 # Remove redirects to this page
68 68 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
69 69 end
70 70
71 71 def pretty_title
72 72 WikiPage.pretty_title(title)
73 73 end
74 74
75 75 def content_for_version(version=nil)
76 76 result = content.versions.find_by_version(version.to_i) if version
77 77 result ||= content
78 78 result
79 79 end
80 80
81 81 def diff(version_to=nil, version_from=nil)
82 82 version_to = version_to ? version_to.to_i : self.content.version
83 83 version_from = version_from ? version_from.to_i : version_to - 1
84 84 version_to, version_from = version_from, version_to unless version_from < version_to
85 85
86 86 content_to = content.versions.find_by_version(version_to)
87 87 content_from = content.versions.find_by_version(version_from)
88 88
89 89 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
90 90 end
91 91
92 92 def annotate(version=nil)
93 93 version = version ? version.to_i : self.content.version
94 94 c = content.versions.find_by_version(version)
95 95 c ? WikiAnnotate.new(c) : nil
96 96 end
97 97
98 98 def self.pretty_title(str)
99 99 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
100 100 end
101 101
102 102 def project
103 103 wiki.project
104 104 end
105 105
106 106 def text
107 107 content.text if content
108 108 end
109 109
110 110 # Returns true if usr is allowed to edit the page, otherwise false
111 111 def editable_by?(usr)
112 112 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
113 113 end
114 114
115 115 def attachments_deletable?(usr=User.current)
116 116 editable_by?(usr) && super(usr)
117 117 end
118 118
119 119 def parent_title
120 120 @parent_title || (self.parent && self.parent.pretty_title)
121 121 end
122 122
123 123 def parent_title=(t)
124 124 @parent_title = t
125 125 parent_page = t.blank? ? nil : self.wiki.find_page(t)
126 126 self.parent = parent_page
127 127 end
128 128
129 129 protected
130 130
131 131 def validate
132 errors.add(:parent_title, :activerecord_error_invalid) if !@parent_title.blank? && parent.nil?
133 errors.add(:parent_title, :activerecord_error_circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
134 errors.add(:parent_title, :activerecord_error_not_same_project) if parent && (parent.wiki_id != wiki_id)
132 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
133 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
134 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
135 135 end
136 136 end
137 137
138 138 class WikiDiff
139 139 attr_reader :diff, :words, :content_to, :content_from
140 140
141 141 def initialize(content_to, content_from)
142 142 @content_to = content_to
143 143 @content_from = content_from
144 144 @words = content_to.text.split(/(\s+)/)
145 145 @words = @words.select {|word| word != ' '}
146 146 words_from = content_from.text.split(/(\s+)/)
147 147 words_from = words_from.select {|word| word != ' '}
148 148 @diff = words_from.diff @words
149 149 end
150 150 end
151 151
152 152 class WikiAnnotate
153 153 attr_reader :lines, :content
154 154
155 155 def initialize(content)
156 156 @content = content
157 157 current = content
158 158 current_lines = current.text.split(/\r?\n/)
159 159 @lines = current_lines.collect {|t| [nil, nil, t]}
160 160 positions = []
161 161 current_lines.size.times {|i| positions << i}
162 162 while (current.previous)
163 163 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
164 164 d.each_slice(3) do |s|
165 165 sign, line = s[0], s[1]
166 166 if sign == '+' && positions[line] && positions[line] != -1
167 167 if @lines[positions[line]][0].nil?
168 168 @lines[positions[line]][0] = current.version
169 169 @lines[positions[line]][1] = current.author
170 170 end
171 171 end
172 172 end
173 173 d.each_slice(3) do |s|
174 174 sign, line = s[0], s[1]
175 175 if sign == '-'
176 176 positions.insert(line, -1)
177 177 else
178 178 positions[line] = nil
179 179 end
180 180 end
181 181 positions.compact!
182 182 # Stop if every line is annotated
183 183 break unless @lines.detect { |line| line[0].nil? }
184 184 current = current.previous
185 185 end
186 186 @lines.each { |line| line[0] ||= current.version }
187 187 end
188 188 end
@@ -1,39 +1,39
1 1 <table class="cal">
2 2 <thead>
3 3 <tr><td></td><% 7.times do |i| %><th><%= day_name( (calendar.first_wday+i)%7 ) %></th><% end %></tr>
4 4 </thead>
5 5 <tbody>
6 6 <tr>
7 7 <% day = calendar.startdt
8 8 while day <= calendar.enddt %>
9 9 <%= "<th>#{day.cweek}</th>" if day.cwday == calendar.first_wday %>
10 10 <td class="<%= day.month==calendar.month ? 'even' : 'odd' %><%= ' today' if Date.today == day %>">
11 11 <p class="day-num"><%= day.day %></p>
12 12 <% calendar.events_on(day).each do |i| %>
13 13 <% if i.is_a? Issue %>
14 14 <div class="tooltip">
15 15 <%= if day == i.start_date && day == i.due_date
16 16 image_tag('arrow_bw.png')
17 17 elsif day == i.start_date
18 18 image_tag('arrow_from.png')
19 19 elsif day == i.due_date
20 20 image_tag('arrow_to.png')
21 21 end %>
22 22 <%= h("#{i.project} -") unless @project && @project == i.project %>
23 <%= link_to_issue i %>: <%= h(truncate(i.subject, 30)) %>
23 <%= link_to_issue i %>: <%= h(truncate(i.subject, :length => 30)) %>
24 24 <span class="tip"><%= render_issue_tooltip i %></span>
25 25 </div>
26 26 <% else %>
27 27 <span class="icon icon-package">
28 28 <%= h("#{i.project} -") unless @project && @project == i.project %>
29 29 <%= link_to_version i%>
30 30 </span>
31 31 <% end %>
32 32 <% end %>
33 33 </td>
34 34 <%= '</tr><tr>' if day.cwday==calendar.last_wday and day!=calendar.enddt %>
35 35 <% day = day + 1
36 36 end %>
37 37 </tr>
38 38 </tbody>
39 39 </table>
@@ -1,31 +1,31
1 1 xml.instruct!
2 2 xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
3 xml.title truncate_single_line(@title, 100)
3 xml.title truncate_single_line(@title, :length => 100)
4 4 xml.link "rel" => "self", "href" => url_for(params.merge({:format => nil, :only_path => false}))
5 5 xml.link "rel" => "alternate", "href" => url_for(:controller => 'welcome', :only_path => false)
6 6 xml.id url_for(:controller => 'welcome', :only_path => false)
7 7 xml.updated((@items.first ? @items.first.event_datetime : Time.now).xmlschema)
8 8 xml.author { xml.name "#{Setting.app_title}" }
9 9 xml.generator(:uri => Redmine::Info.url) { xml.text! Redmine::Info.app_name; }
10 10 @items.each do |item|
11 11 xml.entry do
12 12 url = url_for(item.event_url(:only_path => false))
13 13 if @project
14 xml.title truncate_single_line(item.event_title, 100)
14 xml.title truncate_single_line(item.event_title, :length => 100)
15 15 else
16 xml.title truncate_single_line("#{item.project} - #{item.event_title}", 100)
16 xml.title truncate_single_line("#{item.project} - #{item.event_title}", :length => 100)
17 17 end
18 18 xml.link "rel" => "alternate", "href" => url
19 19 xml.id url
20 20 xml.updated item.event_datetime.xmlschema
21 21 author = item.event_author if item.respond_to?(:event_author)
22 22 xml.author do
23 23 xml.name(author)
24 24 xml.email(author.mail) if author.respond_to?(:mail) && !author.mail.blank?
25 25 end if author
26 26 xml.content "type" => "html" do
27 27 xml.text! textilizable(item.event_description)
28 28 end
29 29 end
30 30 end
31 31 end
@@ -1,58 +1,58
1 1 <h2><%=l(:label_custom_field_plural)%></h2>
2 2
3 3 <% selected_tab = params[:tab] ? params[:tab].to_s : custom_fields_tabs.first[:name] %>
4 4
5 5 <div class="tabs">
6 6 <ul>
7 7 <% custom_fields_tabs.each do |tab| -%>
8 8 <li><%= link_to l(tab[:label]), { :tab => tab[:name] },
9 9 :id => "tab-#{tab[:name]}",
10 10 :class => (tab[:name] != selected_tab ? nil : 'selected'),
11 11 :onclick => "showTab('#{tab[:name]}'); this.blur(); return false;" %></li>
12 12 <% end -%>
13 13 </ul>
14 14 </div>
15 15
16 16 <% custom_fields_tabs.each do |tab| %>
17 17 <div id="tab-content-<%= tab[:name] %>" class="tab-content" style="<%= tab[:name] != selected_tab ? 'display:none' : nil %>">
18 18 <table class="list">
19 19 <thead><tr>
20 20 <th width="30%"><%=l(:field_name)%></th>
21 21 <th><%=l(:field_field_format)%></th>
22 22 <th><%=l(:field_is_required)%></th>
23 23 <% if tab[:name] == 'IssueCustomField' %>
24 24 <th><%=l(:field_is_for_all)%></th>
25 25 <th><%=l(:label_used_by)%></th>
26 26 <% end %>
27 27 <th><%=l(:button_sort)%></th>
28 28 <th width="10%"></th>
29 29 </tr></thead>
30 30 <tbody>
31 31 <% (@custom_fields_by_type[tab[:name]] || []).sort.each do |custom_field| -%>
32 32 <tr class="<%= cycle("odd", "even") %>">
33 33 <td><%= link_to custom_field.name, :action => 'edit', :id => custom_field %></td>
34 34 <td align="center"><%= l(CustomField::FIELD_FORMATS[custom_field.field_format][:name]) %></td>
35 35 <td align="center"><%= image_tag 'true.png' if custom_field.is_required? %></td>
36 36 <% if tab[:name] == 'IssueCustomField' %>
37 37 <td align="center"><%= image_tag 'true.png' if custom_field.is_for_all? %></td>
38 <td align="center"><%= custom_field.projects.count.to_s + ' ' + lwr(:label_project, custom_field.projects.count) if custom_field.is_a? IssueCustomField and !custom_field.is_for_all? %></td>
38 <td align="center"><%= l(:label_x_projects, :count => custom_field.projects.count) if custom_field.is_a? IssueCustomField and !custom_field.is_for_all? %></td>
39 39 <% end %>
40 40 <td align="center" style="width:15%;">
41 41 <%= link_to image_tag('2uparrow.png', :alt => l(:label_sort_highest)), {:action => 'move', :id => custom_field, :position => 'highest'}, :method => :post, :title => l(:label_sort_highest) %>
42 42 <%= link_to image_tag('1uparrow.png', :alt => l(:label_sort_higher)), {:action => 'move', :id => custom_field, :position => 'higher'}, :method => :post, :title => l(:label_sort_higher) %> -
43 43 <%= link_to image_tag('1downarrow.png', :alt => l(:label_sort_lower)), {:action => 'move', :id => custom_field, :position => 'lower'}, :method => :post, :title => l(:label_sort_lower) %>
44 44 <%= link_to image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), {:action => 'move', :id => custom_field, :position => 'lowest'}, :method => :post, :title => l(:label_sort_lowest) %>
45 45 </td>
46 46 <td align="center">
47 47 <%= button_to l(:button_delete), { :action => 'destroy', :id => custom_field }, :confirm => l(:text_are_you_sure), :class => "button-small" %>
48 48 </td>
49 49 </tr>
50 50 <% end; reset_cycle %>
51 51 </tbody>
52 52 </table>
53 53
54 54 <p><%= link_to l(:label_custom_field_new), {:action => 'new', :type => tab[:name]}, :class => 'icon icon-add' %></p>
55 55 </div>
56 56 <% end %>
57 57
58 58 <% html_title(l(:label_custom_field_plural)) -%>
@@ -1,3 +1,3
1 1 <p><%= link_to h(document.title), :controller => 'documents', :action => 'show', :id => document %><br />
2 <% unless document.description.blank? %><%=h(truncate(document.description, 250)) %><br /><% end %>
2 <% unless document.description.blank? %><%=h(truncate(document.description, :length => 250)) %><br /><% end %>
3 3 <em><%= format_time(document.created_on) %></em></p> No newline at end of file
@@ -1,32 +1,32
1 1 <div class="contextual">
2 2 <% if authorize_for('issue_relations', 'new') %>
3 3 <%= toggle_link l(:button_add), 'new-relation-form'%>
4 4 <% end %>
5 5 </div>
6 6
7 7 <p><strong><%=l(:label_related_issues)%></strong></p>
8 8
9 9 <% if @issue.relations.any? %>
10 10 <table style="width:100%">
11 11 <% @issue.relations.select {|r| r.other_issue(@issue).visible? }.each do |relation| %>
12 12 <tr>
13 <td><%= l(relation.label_for(@issue)) %> <%= "(#{lwr(:actionview_datehelper_time_in_words_day, relation.delay)})" if relation.delay && relation.delay != 0 %>
13 <td><%= l(relation.label_for(@issue)) %> <%= "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)})" if relation.delay && relation.delay != 0 %>
14 14 <%= h(relation.other_issue(@issue).project) + ' - ' if Setting.cross_project_issue_relations? %> <%= link_to_issue relation.other_issue(@issue) %></td>
15 15 <td><%=h relation.other_issue(@issue).subject %></td>
16 16 <td><%= relation.other_issue(@issue).status.name %></td>
17 17 <td><%= format_date(relation.other_issue(@issue).start_date) %></td>
18 18 <td><%= format_date(relation.other_issue(@issue).due_date) %></td>
19 19 <td><%= link_to_remote(image_tag('delete.png'), { :url => {:controller => 'issue_relations', :action => 'destroy', :issue_id => @issue, :id => relation},
20 20 :method => :post
21 21 }, :title => l(:label_relation_delete)) if authorize_for('issue_relations', 'destroy') %></td>
22 22 </tr>
23 23 <% end %>
24 24 </table>
25 25 <% end %>
26 26
27 27 <% remote_form_for(:relation, @relation,
28 28 :url => {:controller => 'issue_relations', :action => 'new', :issue_id => @issue},
29 29 :method => :post,
30 30 :html => {:id => 'new-relation-form', :style => (@relation ? '' : 'display: none;')}) do |f| %>
31 31 <%= render :partial => 'issue_relations/form', :locals => {:f => f}%>
32 32 <% end %>
@@ -1,125 +1,125
1 1 <div class="contextual">
2 2 <%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
3 3 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time-add' %>
4 4 <%= watcher_tag(@issue, User.current) %>
5 5 <%= link_to_if_authorized l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %>
6 6 <%= link_to_if_authorized l(:button_move), {:controller => 'issues', :action => 'move', :id => @issue }, :class => 'icon icon-move' %>
7 7 <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
8 8 </div>
9 9
10 10 <h2><%= @issue.tracker.name %> #<%= @issue.id %></h2>
11 11
12 12 <div class="<%= css_issue_classes(@issue) %>">
13 13 <%= avatar(@issue.author, :size => "64") %>
14 14 <h3><%=h @issue.subject %></h3>
15 15 <p class="author">
16 16 <%= authoring @issue.created_on, @issue.author %>.
17 17 <%= l(:label_updated_time, distance_of_time_in_words(Time.now, @issue.updated_on)) + '.' if @issue.created_on != @issue.updated_on %>
18 18 </p>
19 19
20 20 <table width="100%">
21 21 <tr>
22 22 <td style="width:15%" class="status"><b><%=l(:field_status)%>:</b></td><td style="width:35%" class="status"><%= @issue.status.name %></td>
23 23 <td style="width:15%" class="start-date"><b><%=l(:field_start_date)%>:</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td>
24 24 </tr>
25 25 <tr>
26 26 <td class="priority"><b><%=l(:field_priority)%>:</b></td><td class="priority"><%= @issue.priority.name %></td>
27 27 <td class="due-date"><b><%=l(:field_due_date)%>:</b></td><td class="due-date"><%= format_date(@issue.due_date) %></td>
28 28 </tr>
29 29 <tr>
30 30 <td class="assigned-to"><b><%=l(:field_assigned_to)%>:</b></td><td><%= avatar(@issue.assigned_to, :size => "14") %><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
31 31 <td class="progress"><b><%=l(:field_done_ratio)%>:</b></td><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
32 32 </tr>
33 33 <tr>
34 34 <td class="category"><b><%=l(:field_category)%>:</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
35 35 <% if User.current.allowed_to?(:view_time_entries, @project) %>
36 36 <td class="spent-time"><b><%=l(:label_spent_time)%>:</b></td>
37 <td class="spent-hours"><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}) : "-" %></td>
37 <td class="spent-hours"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}) : "-" %></td>
38 38 <% end %>
39 39 </tr>
40 40 <tr>
41 41 <td class="fixed-version"><b><%=l(:field_fixed_version)%>:</b></td><td><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
42 42 <% if @issue.estimated_hours %>
43 <td class="estimated-hours"><b><%=l(:field_estimated_hours)%>:</b></td><td><%= lwr(:label_f_hour, @issue.estimated_hours) %></td>
43 <td class="estimated-hours"><b><%=l(:field_estimated_hours)%>:</b></td><td><%= l_hours(@issue.estimated_hours) %></td>
44 44 <% end %>
45 45 </tr>
46 46 <tr>
47 47 <% n = 0 -%>
48 48 <% @issue.custom_values.each do |value| -%>
49 49 <td valign="top"><b><%=h value.custom_field.name %>:</b></td><td valign="top"><%= simple_format(h(show_value(value))) %></td>
50 50 <% n = n + 1
51 51 if (n > 1)
52 52 n = 0 %>
53 53 </tr><tr>
54 54 <%end
55 55 end %>
56 56 </tr>
57 57 <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
58 58 </table>
59 59 <hr />
60 60
61 61 <div class="contextual">
62 62 <%= link_to_remote_if_authorized(l(:button_quote), { :url => {:action => 'reply', :id => @issue} }, :class => 'icon icon-comment') unless @issue.description.blank? %>
63 63 </div>
64 64
65 65 <p><strong><%=l(:field_description)%></strong></p>
66 66 <div class="wiki">
67 67 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
68 68 </div>
69 69
70 70 <%= link_to_attachments @issue %>
71 71
72 72 <% if authorize_for('issue_relations', 'new') || @issue.relations.any? %>
73 73 <hr />
74 74 <div id="relations">
75 75 <%= render :partial => 'relations' %>
76 76 </div>
77 77 <% end %>
78 78
79 79 <% if User.current.allowed_to?(:add_issue_watchers, @project) ||
80 80 (@issue.watchers.any? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
81 81 <hr />
82 82 <div id="watchers">
83 83 <%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %>
84 84 </div>
85 85 <% end %>
86 86
87 87 </div>
88 88
89 89 <% if @issue.changesets.any? && User.current.allowed_to?(:view_changesets, @project) %>
90 90 <div id="issue-changesets">
91 91 <h3><%=l(:label_associated_revisions)%></h3>
92 92 <%= render :partial => 'changesets', :locals => { :changesets => @issue.changesets} %>
93 93 </div>
94 94 <% end %>
95 95
96 96 <% if @journals.any? %>
97 97 <div id="history">
98 98 <h3><%=l(:label_history)%></h3>
99 99 <%= render :partial => 'history', :locals => { :journals => @journals } %>
100 100 </div>
101 101 <% end %>
102 102 <div style="clear: both;"></div>
103 103
104 104 <% if authorize_for('issues', 'edit') %>
105 105 <div id="update" style="display:none;">
106 106 <h3><%= l(:button_update) %></h3>
107 107 <%= render :partial => 'edit' %>
108 108 </div>
109 109 <% end %>
110 110
111 111 <% other_formats_links do |f| %>
112 112 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
113 113 <%= f.link_to 'PDF' %>
114 114 <% end %>
115 115
116 116 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
117 117
118 118 <% content_for :sidebar do %>
119 119 <%= render :partial => 'issues/sidebar' %>
120 120 <% end %>
121 121
122 122 <% content_for :header_tags do %>
123 123 <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
124 124 <%= stylesheet_link_tag 'scm' %>
125 125 <% end %>
@@ -1,3 +1,3
1 <%= l(:text_issue_added, "##{@issue.id}", @issue.author) %>
1 <%= l(:text_issue_added, :id => "##{@issue.id}", :author => @issue.author) %>
2 2 <hr />
3 3 <%= render :partial => "issue_text_html", :locals => { :issue => @issue, :issue_url => @issue_url } %>
@@ -1,4 +1,4
1 <%= l(:text_issue_added, "##{@issue.id}", @issue.author) %>
1 <%= l(:text_issue_added, :id => "##{@issue.id}", :author => @issue.author) %>
2 2
3 3 ----------------------------------------
4 4 <%= render :partial => "issue_text_plain", :locals => { :issue => @issue, :issue_url => @issue_url } %>
@@ -1,11 +1,11
1 <%= l(:text_issue_updated, "##{@issue.id}", @journal.user) %>
1 <%= l(:text_issue_updated, :id => "##{@issue.id}", :author => @journal.user) %>
2 2
3 3 <ul>
4 4 <% for detail in @journal.details %>
5 5 <li><%= show_detail(detail, true) %></li>
6 6 <% end %>
7 7 </ul>
8 8
9 9 <%= textilizable(@journal, :notes, :only_path => false) %>
10 10 <hr />
11 11 <%= render :partial => "issue_text_html", :locals => { :issue => @issue, :issue_url => @issue_url } %>
@@ -1,9 +1,9
1 <%= l(:text_issue_updated, "##{@issue.id}", @journal.user) %>
1 <%= l(:text_issue_updated, :id => "##{@issue.id}", :author => @journal.user) %>
2 2
3 3 <% for detail in @journal.details -%>
4 4 <%= show_detail(detail, true) %>
5 5 <% end -%>
6 6
7 7 <%= @journal.notes if @journal.notes? %>
8 8 ----------------------------------------
9 9 <%= render :partial => "issue_text_plain", :locals => { :issue => @issue, :issue_url => @issue_url } %>
@@ -1,9 +1,9
1 <p><%= l(:mail_body_reminder, @issues.size, @days) %></p>
1 <p><%= l(:mail_body_reminder, :count => @issues.size, :days => @days) %></p>
2 2
3 3 <ul>
4 4 <% @issues.each do |issue| -%>
5 5 <li><%=h "#{issue.project} - #{issue.tracker} ##{issue.id}: #{issue.subject}" %></li>
6 6 <% end -%>
7 7 </ul>
8 8
9 9 <p><%= link_to l(:label_issue_view_all), @issues_url %></p>
@@ -1,7 +1,7
1 <%= l(:mail_body_reminder, @issues.size, @days) %>:
1 <%= l(:mail_body_reminder, :count => @issues.size, :days => @days) %>:
2 2
3 3 <% @issues.each do |issue| -%>
4 4 * <%= "#{issue.project} - #{issue.tracker} ##{issue.id}: #{issue.subject}" %>
5 5 <% end -%>
6 6
7 7 <%= @issues_url %>
@@ -1,10 +1,11
1 <h3><%=l(:label_watched_issues)%></h3>
1 <h3><%=l(:label_watched_issues)%> (<%= Issue.visible.count(:include => :watchers,
2 :conditions => ["#{Watcher.table_name}.user_id = ?", user.id]) %>)</h3>
2 3 <% watched_issues = Issue.visible.find(:all,
3 4 :include => [:status, :project, :tracker, :watchers],
4 5 :limit => 10,
5 6 :conditions => ["#{Watcher.table_name}.user_id = ?", user.id],
6 7 :order => "#{Issue.table_name}.updated_on DESC") %>
7 8 <%= render :partial => 'issues/list_simple', :locals => { :issues => watched_issues } %>
8 9 <% if watched_issues.length > 0 %>
9 10 <p class="small"><%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :set_filter => 1, :watcher_id => 'me' %></p>
10 11 <% end %>
@@ -1,6 +1,6
1 1 <p><%= link_to(h(news.project.name), :controller => 'projects', :action => 'show', :id => news.project) + ': ' unless @project %>
2 2 <%= link_to h(news.title), :controller => 'news', :action => 'show', :id => news %>
3 <%= "(#{news.comments_count} #{lwr(:label_comment, news.comments_count).downcase})" if news.comments_count > 0 %>
3 <%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %>
4 4 <br />
5 5 <% unless news.summary.blank? %><span class="summary"><%=h news.summary %></span><br /><% end %>
6 6 <span class="author"><%= authoring news.created_on, news.author %></span></p>
@@ -1,50 +1,50
1 1 <div class="contextual">
2 2 <%= link_to_if_authorized(l(:label_news_new),
3 3 {:controller => 'news', :action => 'new', :project_id => @project},
4 4 :class => 'icon icon-add',
5 5 :onclick => 'Element.show("add-news"); Form.Element.focus("news_title"); return false;') if @project %>
6 6 </div>
7 7
8 8 <div id="add-news" style="display:none;">
9 9 <h2><%=l(:label_news_new)%></h2>
10 10 <% labelled_tabular_form_for :news, @news, :url => { :controller => 'news', :action => 'new', :project_id => @project },
11 11 :html => { :id => 'news-form' } do |f| %>
12 12 <%= render :partial => 'news/form', :locals => { :f => f } %>
13 13 <%= submit_tag l(:button_create) %>
14 14 <%= link_to_remote l(:label_preview),
15 15 { :url => { :controller => 'news', :action => 'preview', :project_id => @project },
16 16 :method => 'post',
17 17 :update => 'preview',
18 18 :with => "Form.serialize('news-form')"
19 19 }, :accesskey => accesskey(:preview) %> |
20 20 <%= link_to l(:button_cancel), "#", :onclick => 'Element.hide("add-news")' %>
21 21 <% end if @project %>
22 22 <div id="preview" class="wiki"></div>
23 23 </div>
24 24
25 25 <h2><%=l(:label_news_plural)%></h2>
26 26
27 27 <% if @newss.empty? %>
28 28 <p class="nodata"><%= l(:label_no_data) %></p>
29 29 <% else %>
30 30 <% @newss.each do |news| %>
31 31 <h3><%= link_to(h(news.project.name), :controller => 'projects', :action => 'show', :id => news.project) + ': ' unless news.project == @project %>
32 32 <%= link_to h(news.title), :controller => 'news', :action => 'show', :id => news %>
33 <%= "(#{news.comments_count} #{lwr(:label_comment, news.comments_count).downcase})" if news.comments_count > 0 %></h3>
33 <%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %></h3>
34 34 <p class="author"><%= authoring news.created_on, news.author %></p>
35 35 <div class="wiki">
36 36 <%= textilizable(news.description) %>
37 37 </div>
38 38 <% end %>
39 39 <% end %>
40 40 <p class="pagination"><%= pagination_links_full @news_pages %></p>
41 41
42 42 <% other_formats_links do |f| %>
43 43 <%= f.link_to 'Atom', :url => {:project_id => @project, :key => User.current.rss_key} %>
44 44 <% end %>
45 45
46 46 <% content_for :header_tags do %>
47 47 <%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :page => nil, :key => User.current.rss_key})) %>
48 48 <% end %>
49 49
50 50 <% html_title(l(:label_news_plural)) -%>
@@ -1,49 +1,49
1 1 <%= error_messages_for 'project' %>
2 2
3 3 <div class="box">
4 4 <!--[form:project]-->
5 5 <p><%= f.text_field :name, :required => true %><br /><em><%= l(:text_caracters_maximum, 30) %></em></p>
6 6
7 7 <% if User.current.admin? && !@project.possible_parents.empty? %>
8 8 <p><label><%= l(:field_parent) %></label><%= parent_project_select_tag(@project) %></p>
9 9 <% end %>
10 10
11 11 <p><%= f.text_area :description, :rows => 5, :class => 'wiki-edit' %></p>
12 12 <p><%= f.text_field :identifier, :required => true, :disabled => @project.identifier_frozen? %>
13 13 <% unless @project.identifier_frozen? %>
14 <br /><em><%= l(:text_length_between, 2, 20) %> <%= l(:text_project_identifier_info) %></em>
14 <br /><em><%= l(:text_length_between, :min => 2, :max => 20) %> <%= l(:text_project_identifier_info) %></em>
15 15 <% end %></p>
16 16 <p><%= f.text_field :homepage, :size => 60 %></p>
17 17 <p><%= f.check_box :is_public %></p>
18 18 <%= wikitoolbar_for 'project_description' %>
19 19
20 20 <% @project.custom_field_values.each do |value| %>
21 21 <p><%= custom_field_tag_with_label :project, value %></p>
22 22 <% end %>
23 23 <%= call_hook(:view_projects_form, :project => @project, :form => f) %>
24 24 </div>
25 25
26 26 <% unless @trackers.empty? %>
27 27 <fieldset class="box"><legend><%=l(:label_tracker_plural)%></legend>
28 28 <% @trackers.each do |tracker| %>
29 29 <label class="floating">
30 30 <%= check_box_tag 'project[tracker_ids][]', tracker.id, @project.trackers.include?(tracker) %>
31 31 <%= tracker %>
32 32 </label>
33 33 <% end %>
34 34 <%= hidden_field_tag 'project[tracker_ids][]', '' %>
35 35 </fieldset>
36 36 <% end %>
37 37
38 38 <% unless @issue_custom_fields.empty? %>
39 39 <fieldset class="box"><legend><%=l(:label_custom_field_plural)%></legend>
40 40 <% @issue_custom_fields.each do |custom_field| %>
41 41 <label class="floating">
42 42 <%= check_box_tag 'project[issue_custom_field_ids][]', custom_field.id, (@project.all_issue_custom_fields.include? custom_field), (custom_field.is_for_all? ? {:disabled => "disabled"} : {}) %>
43 43 <%= custom_field.name %>
44 44 </label>
45 45 <% end %>
46 46 <%= hidden_field_tag 'project[issue_custom_field_ids][]', '' %>
47 47 </fieldset>
48 48 <% end %>
49 49 <!--[eoform:project]-->
@@ -1,17 +1,17
1 1 <h2><%=l(:label_project_new)%></h2>
2 2
3 3 <% labelled_tabular_form_for :project, @project, :url => { :action => "add" } do |f| %>
4 4 <%= render :partial => 'form', :locals => { :f => f } %>
5 5
6 6 <fieldset class="box"><legend><%= l(:label_module_plural) %></legend>
7 7 <% Redmine::AccessControl.available_project_modules.each do |m| %>
8 8 <label class="floating">
9 9 <%= check_box_tag 'enabled_modules[]', m, @project.module_enabled?(m) %>
10 <%= (l_has_string?("project_module_#{m}".to_sym) ? l("project_module_#{m}".to_sym) : m.to_s.humanize) %>
10 <%= l_or_humanize(m, :prefix => "project_module_") %>
11 11 </label>
12 12 <% end %>
13 13 </fieldset>
14 14
15 15 <%= submit_tag l(:button_save) %>
16 16 <%= javascript_tag "Form.Element.focus('project_name');" %>
17 17 <% end %>
@@ -1,17 +1,17
1 1 <% form_for :project, @project,
2 2 :url => { :action => 'modules', :id => @project },
3 3 :html => {:id => 'modules-form'} do |f| %>
4 4
5 5 <div class=box>
6 6 <strong><%= l(:text_select_project_modules) %></strong>
7 7
8 8 <% Redmine::AccessControl.available_project_modules.each do |m| %>
9 9 <p><label><%= check_box_tag 'enabled_modules[]', m, @project.module_enabled?(m) -%>
10 <%= (l_has_string?("project_module_#{m}".to_sym) ? l("project_module_#{m}".to_sym) : m.to_s.humanize) %></label></p>
10 <%= l_or_humanize(m, :prefix => "project_module_") %></label></p>
11 11 <% end %>
12 12 </div>
13 13
14 14 <p><%= check_all_links 'modules-form' %></p>
15 15 <p><%= submit_tag l(:button_save) %></p>
16 16
17 17 <% end %>
@@ -1,78 +1,79
1 1 <h2><%=l(:label_overview)%></h2>
2 2
3 3 <div class="splitcontentleft">
4 4 <%= textilizable @project.description %>
5 5 <ul>
6 6 <% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= link_to(h(@project.homepage), @project.homepage) %></li><% end %>
7 7 <% if @subprojects.any? %>
8 8 <li><%=l(:label_subproject_plural)%>:
9 9 <%= @subprojects.collect{|p| link_to(h(p), :action => 'show', :id => p)}.join(", ") %></li>
10 10 <% end %>
11 11 <% @project.custom_values.each do |custom_value| %>
12 12 <% if !custom_value.value.empty? %>
13 13 <li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
14 14 <% end %>
15 15 <% end %>
16 16 </ul>
17 17
18 18 <% if User.current.allowed_to?(:view_issues, @project) %>
19 19 <div class="box">
20 20 <h3 class="icon22 icon22-tracker"><%=l(:label_issue_tracking)%></h3>
21 21 <ul>
22 22 <% for tracker in @trackers %>
23 23 <li><%= link_to tracker.name, :controller => 'issues', :action => 'index', :project_id => @project,
24 24 :set_filter => 1,
25 25 "tracker_id" => tracker.id %>:
26 <%= @open_issues_by_tracker[tracker] || 0 %> <%= lwr(:label_open_issues, @open_issues_by_tracker[tracker] || 0) %>
27 <%= l(:label_on) %> <%= @total_issues_by_tracker[tracker] || 0 %></li>
26 <%= l(:label_x_open_issues_abbr_on_total, :count => @open_issues_by_tracker[tracker].to_i,
27 :total => @total_issues_by_tracker[tracker].to_i) %>
28 </li>
28 29 <% end %>
29 30 </ul>
30 31 <p><%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 %></p>
31 32 </div>
32 33 <% end %>
33 34 </div>
34 35
35 36 <div class="splitcontentright">
36 37 <% if @members_by_role.any? %>
37 38 <div class="box">
38 39 <h3 class="icon22 icon22-users"><%=l(:label_member_plural)%></h3>
39 40 <p><% @members_by_role.keys.sort.each do |role| %>
40 41 <%= role.name %>:
41 42 <%= @members_by_role[role].collect(&:user).sort.collect{|u| link_to_user u}.join(", ") %>
42 43 <br />
43 44 <% end %></p>
44 45 </div>
45 46 <% end %>
46 47
47 48 <% if @news.any? && authorize_for('news', 'index') %>
48 49 <div class="box">
49 50 <h3><%=l(:label_news_latest)%></h3>
50 51 <%= render :partial => 'news/news', :collection => @news %>
51 52 <p><%= link_to l(:label_news_view_all), :controller => 'news', :action => 'index', :project_id => @project %></p>
52 53 </div>
53 54 <% end %>
54 55 </div>
55 56
56 57 <% content_for :sidebar do %>
57 58 <% planning_links = []
58 59 planning_links << link_to_if_authorized(l(:label_calendar), :controller => 'issues', :action => 'calendar', :project_id => @project)
59 60 planning_links << link_to_if_authorized(l(:label_gantt), :controller => 'issues', :action => 'gantt', :project_id => @project)
60 61 planning_links.compact!
61 62 unless planning_links.empty? %>
62 63 <h3><%= l(:label_planning) %></h3>
63 64 <p><%= planning_links.join(' | ') %></p>
64 65 <% end %>
65 66
66 67 <% if @total_hours && User.current.allowed_to?(:view_time_entries, @project) %>
67 68 <h3><%= l(:label_spent_time) %></h3>
68 <p><span class="icon icon-time"><%= lwr(:label_f_hour, @total_hours) %></span></p>
69 <p><span class="icon icon-time"><%= l_hours(@total_hours) %></span></p>
69 70 <p><%= link_to(l(:label_details), {:controller => 'timelog', :action => 'details', :project_id => @project}) %> |
70 71 <%= link_to(l(:label_report), {:controller => 'timelog', :action => 'report', :project_id => @project}) %></p>
71 72 <% end %>
72 73 <% end %>
73 74
74 75 <% content_for :header_tags do %>
75 76 <%= auto_discovery_link_tag(:atom, {:action => 'activity', :id => @project, :format => 'atom', :key => User.current.rss_key}) %>
76 77 <% end %>
77 78
78 79 <% html_title(l(:label_overview)) -%>
@@ -1,24 +1,24
1 1 <% @entries.each do |entry| %>
2 2 <% tr_id = Digest::MD5.hexdigest(entry.path)
3 3 depth = params[:depth].to_i %>
4 4 <tr id="<%= tr_id %>" class="<%= params[:parent_id] %> entry <%= entry.kind %>">
5 5 <td style="padding-left: <%=18 * depth%>px;" class="filename">
6 6 <% if entry.is_dir? %>
7 7 <span class="expander" onclick="<%= remote_function :url => {:action => 'browse', :id => @project, :path => to_path_param(entry.path), :rev => @rev, :depth => (depth + 1), :parent_id => tr_id},
8 8 :update => { :success => tr_id },
9 9 :position => :after,
10 10 :success => "scmEntryLoaded('#{tr_id}')",
11 11 :condition => "scmEntryClick('#{tr_id}')"%>">&nbsp</span>
12 12 <% end %>
13 13 <%= link_to h(entry.name),
14 14 {:action => (entry.is_dir? ? 'browse' : 'changes'), :id => @project, :path => to_path_param(entry.path), :rev => @rev},
15 15 :class => (entry.is_dir? ? 'icon icon-folder' : 'icon icon-file')%>
16 16 </td>
17 17 <td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
18 18 <% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %>
19 19 <td class="revision"><%= link_to(format_revision(entry.lastrev.name), :action => 'revision', :id => @project, :rev => entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %></td>
20 20 <td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
21 21 <td class="author"><%= changeset.nil? ? h(entry.lastrev.author.to_s.split('<').first) : changeset.author if entry.lastrev %></td>
22 <td class="comments"><%=h truncate(changeset.comments, 50) unless changeset.nil? %></td>
22 <td class="comments"><%=h truncate(changeset.comments, :length => 50) unless changeset.nil? %></td>
23 23 </tr>
24 24 <% end %>
@@ -1,51 +1,51
1 1 <h2><%= l(:label_search) %></h2>
2 2
3 3 <div class="box">
4 4 <% form_tag({}, :method => :get) do %>
5 5 <p><%= text_field_tag 'q', @question, :size => 60, :id => 'search-input' %>
6 6 <%= javascript_tag "Field.focus('search-input')" %>
7 7 <%= project_select_tag %>
8 8 <label><%= check_box_tag 'all_words', 1, @all_words %> <%= l(:label_all_words) %></label>
9 9 <label><%= check_box_tag 'titles_only', 1, @titles_only %> <%= l(:label_search_titles_only) %></label>
10 10 </p>
11 11 <p>
12 12 <% @object_types.each do |t| %>
13 13 <label><%= check_box_tag t, 1, @scope.include?(t) %> <%= type_label(t) %></label>
14 14 <% end %>
15 15 </p>
16 16
17 17 <p><%= submit_tag l(:button_submit), :name => 'submit' %></p>
18 18 <% end %>
19 19 </div>
20 20
21 21 <% if @results %>
22 22 <div id="search-results-counts">
23 23 <%= render_results_by_type(@results_by_type) unless @scope.size == 1 %>
24 24 </div>
25 25
26 26 <h3><%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)</h3>
27 27 <dl id="search-results">
28 28 <% @results.each do |e| %>
29 <dt class="<%= e.event_type %>"><%= content_tag('span', h(e.project), :class => 'project') unless @project == e.project %> <%= link_to highlight_tokens(truncate(e.event_title, 255), @tokens), e.event_url %></dt>
29 <dt class="<%= e.event_type %>"><%= content_tag('span', h(e.project), :class => 'project') unless @project == e.project %> <%= link_to highlight_tokens(truncate(e.event_title, :length => 255), @tokens), e.event_url %></dt>
30 30 <dd><span class="description"><%= highlight_tokens(e.event_description, @tokens) %></span>
31 31 <span class="author"><%= format_time(e.event_datetime) %></span></dd>
32 32 <% end %>
33 33 </dl>
34 34 <% end %>
35 35
36 36 <p><center>
37 37 <% if @pagination_previous_date %>
38 38 <%= link_to_remote ('&#171; ' + l(:label_previous)),
39 39 {:update => :content,
40 40 :url => params.merge(:previous => 1, :offset => @pagination_previous_date.strftime("%Y%m%d%H%M%S"))
41 41 }, :href => url_for(params.merge(:previous => 1, :offset => @pagination_previous_date.strftime("%Y%m%d%H%M%S"))) %>&nbsp;
42 42 <% end %>
43 43 <% if @pagination_next_date %>
44 44 <%= link_to_remote (l(:label_next) + ' &#187;'),
45 45 {:update => :content,
46 46 :url => params.merge(:previous => nil, :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S"))
47 47 }, :href => url_for(params.merge(:previous => nil, :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S"))) %>
48 48 <% end %>
49 49 </center></p>
50 50
51 51 <% html_title(l(:label_search)) -%>
@@ -1,30 +1,30
1 1 <% form_tag({:action => 'edit', :tab => 'authentication'}) do %>
2 2
3 3 <div class="box tabular settings">
4 4 <p><label><%= l(:setting_login_required) %></label>
5 5 <%= check_box_tag 'settings[login_required]', 1, Setting.login_required? %><%= hidden_field_tag 'settings[login_required]', 0 %></p>
6 6
7 7 <p><label><%= l(:setting_autologin) %></label>
8 <%= select_tag 'settings[autologin]', options_for_select( [[l(:label_disabled), "0"]] + [1, 7, 30, 365].collect{|days| [lwr(:actionview_datehelper_time_in_words_day, days), days.to_s]}, Setting.autologin) %></p>
8 <%= select_tag 'settings[autologin]', options_for_select( [[l(:label_disabled), "0"]] + [1, 7, 30, 365].collect{|days| [l('datetime.distance_in_words.x_days', :count => days), days.to_s]}, Setting.autologin) %></p>
9 9
10 10 <p><label><%= l(:setting_self_registration) %></label>
11 11 <%= select_tag 'settings[self_registration]',
12 12 options_for_select( [[l(:label_disabled), "0"],
13 13 [l(:label_registration_activation_by_email), "1"],
14 14 [l(:label_registration_manual_activation), "2"],
15 15 [l(:label_registration_automatic_activation), "3"]
16 16 ], Setting.self_registration ) %></p>
17 17
18 18 <p><label><%= l(:label_password_lost) %></label>
19 19 <%= check_box_tag 'settings[lost_password]', 1, Setting.lost_password? %><%= hidden_field_tag 'settings[lost_password]', 0 %></p>
20 20
21 21 <p><label><%= l(:setting_openid) %></label>
22 22 <%= check_box_tag 'settings[openid]', 1, Setting.openid?, :disabled => !Object.const_defined?(:OpenID) %><%= hidden_field_tag 'settings[openid]', 0 %></p>
23 23 </div>
24 24
25 25 <div style="float:right;">
26 26 <%= link_to l(:label_ldap_authentication), :controller => 'auth_sources', :action => 'list' %>
27 27 </div>
28 28
29 29 <%= submit_tag l(:button_save) %>
30 30 <% end %>
@@ -1,41 +1,41
1 1 <table class="list time-entries">
2 2 <thead>
3 3 <tr>
4 4 <%= sort_header_tag('spent_on', :caption => l(:label_date), :default_order => 'desc') %>
5 5 <%= sort_header_tag('user', :caption => l(:label_member)) %>
6 6 <%= sort_header_tag('activity', :caption => l(:label_activity)) %>
7 7 <%= sort_header_tag('project', :caption => l(:label_project)) %>
8 8 <%= sort_header_tag('issue', :caption => l(:label_issue), :default_order => 'desc') %>
9 9 <th><%= l(:field_comments) %></th>
10 10 <%= sort_header_tag('hours', :caption => l(:field_hours)) %>
11 11 <th></th>
12 12 </tr>
13 13 </thead>
14 14 <tbody>
15 15 <% entries.each do |entry| -%>
16 16 <tr class="time-entry <%= cycle("odd", "even") %>">
17 17 <td class="spent_on"><%= format_date(entry.spent_on) %></td>
18 18 <td class="user"><%=h entry.user %></td>
19 19 <td class="activity"><%=h entry.activity %></td>
20 20 <td class="project"><%=h entry.project %></td>
21 21 <td class="subject">
22 22 <% if entry.issue -%>
23 <%= link_to_issue entry.issue %>: <%= h(truncate(entry.issue.subject, 50)) -%>
23 <%= link_to_issue entry.issue %>: <%= h(truncate(entry.issue.subject, :length => 50)) -%>
24 24 <% end -%>
25 25 </td>
26 26 <td class="comments"><%=h entry.comments %></td>
27 27 <td class="hours"><%= html_hours("%.2f" % entry.hours) %></td>
28 28 <td align="center">
29 29 <% if entry.editable_by?(User.current) -%>
30 30 <%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => entry, :project_id => nil},
31 31 :title => l(:button_edit) %>
32 32 <%= link_to image_tag('delete.png'), {:controller => 'timelog', :action => 'destroy', :id => entry, :project_id => nil},
33 33 :confirm => l(:text_are_you_sure),
34 34 :method => :post,
35 35 :title => l(:button_delete) %>
36 36 <% end -%>
37 37 </td>
38 38 </tr>
39 39 <% end -%>
40 40 </tbody>
41 41 </table>
@@ -1,35 +1,35
1 1 <div class="contextual">
2 2 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time-add' %>
3 3 </div>
4 4
5 5 <%= render_timelog_breadcrumb %>
6 6
7 7 <h2><%= l(:label_spent_time) %></h2>
8 8
9 9 <% form_remote_tag( :url => {}, :html => {:method => :get}, :method => :get, :update => 'content' ) do %>
10 10 <%# TOOD: remove the project_id and issue_id hidden fields, that information is
11 11 already in the URI %>
12 12 <%= hidden_field_tag 'project_id', params[:project_id] %>
13 13 <%= hidden_field_tag 'issue_id', params[:issue_id] if @issue %>
14 14 <%= render :partial => 'date_range' %>
15 15 <% end %>
16 16
17 17 <div class="total-hours">
18 <p><%= l(:label_total) %>: <%= html_hours(lwr(:label_f_hour, @total_hours)) %></p>
18 <p><%= l(:label_total) %>: <%= html_hours(l_hours(@total_hours)) %></p>
19 19 </div>
20 20
21 21 <% unless @entries.empty? %>
22 22 <%= render :partial => 'list', :locals => { :entries => @entries }%>
23 23 <p class="pagination"><%= pagination_links_full @entry_pages, @entry_count %></p>
24 24
25 25 <% other_formats_links do |f| %>
26 26 <%= f.link_to 'Atom', :url => params.merge({:issue_id => @issue, :key => User.current.rss_key}) %>
27 27 <%= f.link_to 'CSV', :url => params %>
28 28 <% end %>
29 29 <% end %>
30 30
31 31 <% html_title l(:label_spent_time), l(:label_details) %>
32 32
33 33 <% content_for :header_tags do %>
34 34 <%= auto_discovery_link_tag(:atom, {:issue_id => @issue, :format => 'atom', :key => User.current.rss_key}, :title => l(:label_spent_time)) %>
35 35 <% end %>
@@ -1,75 +1,75
1 1 <div class="contextual">
2 2 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time-add' %>
3 3 </div>
4 4
5 5 <%= render_timelog_breadcrumb %>
6 6
7 7 <h2><%= l(:label_spent_time) %></h2>
8 8
9 9 <% form_remote_tag(:url => {}, :html => {:method => :get}, :method => :get, :update => 'content') do %>
10 10 <% @criterias.each do |criteria| %>
11 11 <%= hidden_field_tag 'criterias[]', criteria, :id => nil %>
12 12 <% end %>
13 13 <%# TODO: get rid of the project_id field, that should already be in the URL %>
14 14 <%= hidden_field_tag 'project_id', params[:project_id] %>
15 15 <%= render :partial => 'date_range' %>
16 16
17 17 <p><%= l(:label_details) %>: <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'],
18 18 [l(:label_month), 'month'],
19 19 [l(:label_week), 'week'],
20 20 [l(:label_day_plural).titleize, 'day']], @columns),
21 21 :onchange => "this.form.onsubmit();" %>
22 22
23 <%= l(:button_add) %>: <%= select_tag('criterias[]', options_for_select([[]] + (@available_criterias.keys - @criterias).collect{|k| [l(@available_criterias[k][:label]), k]}),
23 <%= l(:button_add) %>: <%= select_tag('criterias[]', options_for_select([[]] + (@available_criterias.keys - @criterias).collect{|k| [l_or_humanize(@available_criterias[k][:label]), k]}),
24 24 :onchange => "this.form.onsubmit();",
25 25 :style => 'width: 200px',
26 26 :id => nil,
27 27 :disabled => (@criterias.length >= 3)) %>
28 28 <%= link_to_remote l(:button_clear), {:url => {:project_id => @project, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @columns},
29 29 :method => :get,
30 30 :update => 'content'
31 31 }, :class => 'icon icon-reload' %></p>
32 32 <% end %>
33 33
34 34 <% unless @criterias.empty? %>
35 35 <div class="total-hours">
36 <p><%= l(:label_total) %>: <%= html_hours(lwr(:label_f_hour, @total_hours)) %></p>
36 <p><%= l(:label_total) %>: <%= html_hours(l_hours(@total_hours)) %></p>
37 37 </div>
38 38
39 39 <% unless @hours.empty? %>
40 40 <table class="list" id="time-report">
41 41 <thead>
42 42 <tr>
43 43 <% @criterias.each do |criteria| %>
44 <th><%= l(@available_criterias[criteria][:label]) %></th>
44 <th><%= l_or_humanize(@available_criterias[criteria][:label]) %></th>
45 45 <% end %>
46 46 <% columns_width = (40 / (@periods.length+1)).to_i %>
47 47 <% @periods.each do |period| %>
48 48 <th class="period" width="<%= columns_width %>%"><%= period %></th>
49 49 <% end %>
50 50 <th class="total" width="<%= columns_width %>%"><%= l(:label_total) %></th>
51 51 </tr>
52 52 </thead>
53 53 <tbody>
54 54 <%= render :partial => 'report_criteria', :locals => {:criterias => @criterias, :hours => @hours, :level => 0} %>
55 55 <tr class="total">
56 56 <td><%= l(:label_total) %></td>
57 57 <%= '<td></td>' * (@criterias.size - 1) %>
58 58 <% total = 0 -%>
59 59 <% @periods.each do |period| -%>
60 60 <% sum = sum_hours(select_hours(@hours, @columns, period.to_s)); total += sum -%>
61 61 <td class="hours"><%= html_hours("%.2f" % sum) if sum > 0 %></td>
62 62 <% end -%>
63 63 <td class="hours"><%= html_hours("%.2f" % total) if total > 0 %></td>
64 64 </tr>
65 65 </tbody>
66 66 </table>
67 67
68 68 <% other_formats_links do |f| %>
69 69 <%= f.link_to 'CSV', :url => params %>
70 70 <% end %>
71 71 <% end %>
72 72 <% end %>
73 73
74 74 <% html_title l(:label_spent_time), l(:label_report) %>
75 75
@@ -1,22 +1,20
1 1 <% if version.completed? %>
2 2 <p><%= format_date(version.effective_date) %></p>
3 3 <% elsif version.effective_date %>
4 4 <p><strong><%= due_date_distance_in_words(version.effective_date) %></strong> (<%= format_date(version.effective_date) %>)</p>
5 5 <% end %>
6 6
7 7 <p><%=h version.description %></p>
8 8
9 9 <% if version.fixed_issues.count > 0 %>
10 10 <%= progress_bar([version.closed_pourcent, version.completed_pourcent], :width => '40em', :legend => ('%0.0f%' % version.completed_pourcent)) %>
11 11 <p class="progress-info">
12 <%= link_to(version.closed_issues_count, :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1) %>
13 <%= lwr(:label_closed_issues, version.closed_issues_count) %>
12 <%= link_to_if(version.closed_issues_count > 0, l(:label_x_closed_issues_abbr, :count => version.closed_issues_count), :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1) %>
14 13 (<%= '%0.0f' % (version.closed_issues_count.to_f / version.fixed_issues.count * 100) %>%)
15 14 &#160;
16 <%= link_to(version.open_issues_count, :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1) %>
17 <%= lwr(:label_open_issues, version.open_issues_count)%>
15 <%= link_to_if(version.open_issues_count > 0, l(:label_x_open_issues_abbr, :count => version.open_issues_count), :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1) %>
18 16 (<%= '%0.0f' % (version.open_issues_count.to_f / version.fixed_issues.count * 100) %>%)
19 17 </p>
20 18 <% else %>
21 19 <p><em><%= l(:label_roadmap_no_issues) %></em></p>
22 20 <% end %>
@@ -1,50 +1,50
1 1 <div class="contextual">
2 2 <%= link_to_if_authorized l(:button_edit), {:controller => 'versions', :action => 'edit', :id => @version}, :class => 'icon icon-edit' %>
3 3 </div>
4 4
5 5 <h2><%= h(@version.name) %></h2>
6 6
7 7 <div id="version-summary">
8 8 <% if @version.estimated_hours > 0 || User.current.allowed_to?(:view_time_entries, @project) %>
9 9 <fieldset><legend><%= l(:label_time_tracking) %></legend>
10 10 <table>
11 11 <tr>
12 12 <td width="130px" align="right"><%= l(:field_estimated_hours) %></td>
13 <td width="240px" class="total-hours"width="130px" align="right"><%= html_hours(lwr(:label_f_hour, @version.estimated_hours)) %></td>
13 <td width="240px" class="total-hours"width="130px" align="right"><%= html_hours(l_hours(@version.estimated_hours)) %></td>
14 14 </tr>
15 15 <% if User.current.allowed_to?(:view_time_entries, @project) %>
16 16 <tr>
17 17 <td width="130px" align="right"><%= l(:label_spent_time) %></td>
18 <td width="240px" class="total-hours"><%= html_hours(lwr(:label_f_hour, @version.spent_hours)) %></td>
18 <td width="240px" class="total-hours"><%= html_hours(l_hours(@version.spent_hours)) %></td>
19 19 </tr>
20 20 <% end %>
21 21 </table>
22 22 </fieldset>
23 23 <% end %>
24 24
25 25 <div id="status_by">
26 26 <%= render_issue_status_by(@version, params[:status_by]) if @version.fixed_issues.count > 0 %>
27 27 </div>
28 28 </div>
29 29
30 30 <div id="roadmap">
31 31 <%= render :partial => 'versions/overview', :locals => {:version => @version} %>
32 32 <%= render(:partial => "wiki/content", :locals => {:content => @version.wiki_page.content}) if @version.wiki_page %>
33 33
34 34 <% issues = @version.fixed_issues.find(:all,
35 35 :include => [:status, :tracker],
36 36 :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") %>
37 37 <% if issues.size > 0 %>
38 38 <fieldset class="related-issues"><legend><%= l(:label_related_issues) %></legend>
39 39 <ul>
40 40 <% issues.each do |issue| -%>
41 41 <li><%= link_to_issue(issue) %>: <%=h issue.subject %></li>
42 42 <% end -%>
43 43 </ul>
44 44 </fieldset>
45 45 <% end %>
46 46 </div>
47 47
48 48 <%= call_hook :view_versions_show_bottom, :version => @version %>
49 49
50 50 <% html_title @version.name %>
@@ -1,109 +1,109
1 1 # Don't change this file!
2 2 # Configure your app in config/environment.rb and config/environments/*.rb
3 3
4 4 RAILS_ROOT = File.expand_path("#{File.dirname(__FILE__)}/..") unless defined?(RAILS_ROOT)
5 5
6 6 module Rails
7 7 class << self
8 8 def boot!
9 9 unless booted?
10 10 preinitialize
11 11 pick_boot.run
12 12 end
13 13 end
14 14
15 15 def booted?
16 16 defined? Rails::Initializer
17 17 end
18 18
19 19 def pick_boot
20 20 (vendor_rails? ? VendorBoot : GemBoot).new
21 21 end
22 22
23 23 def vendor_rails?
24 24 File.exist?("#{RAILS_ROOT}/vendor/rails")
25 25 end
26 26
27 27 def preinitialize
28 28 load(preinitializer_path) if File.exist?(preinitializer_path)
29 29 end
30 30
31 31 def preinitializer_path
32 32 "#{RAILS_ROOT}/config/preinitializer.rb"
33 33 end
34 34 end
35 35
36 36 class Boot
37 37 def run
38 38 load_initializer
39 39 Rails::Initializer.run(:set_load_path)
40 40 end
41 41 end
42 42
43 43 class VendorBoot < Boot
44 44 def load_initializer
45 45 require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
46 46 Rails::Initializer.run(:install_gem_spec_stubs)
47 47 end
48 48 end
49 49
50 50 class GemBoot < Boot
51 51 def load_initializer
52 52 self.class.load_rubygems
53 53 load_rails_gem
54 54 require 'initializer'
55 55 end
56 56
57 57 def load_rails_gem
58 58 if version = self.class.gem_version
59 59 gem 'rails', version
60 60 else
61 61 gem 'rails'
62 62 end
63 63 rescue Gem::LoadError => load_error
64 64 $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
65 65 exit 1
66 66 end
67 67
68 68 class << self
69 69 def rubygems_version
70 Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion
70 Gem::RubyGemsVersion rescue nil
71 71 end
72 72
73 73 def gem_version
74 74 if defined? RAILS_GEM_VERSION
75 75 RAILS_GEM_VERSION
76 76 elsif ENV.include?('RAILS_GEM_VERSION')
77 77 ENV['RAILS_GEM_VERSION']
78 78 else
79 79 parse_gem_version(read_environment_rb)
80 80 end
81 81 end
82 82
83 83 def load_rubygems
84 84 require 'rubygems'
85
86 unless rubygems_version >= '0.9.4'
87 $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.)
85 min_version = '1.3.1'
86 unless rubygems_version >= min_version
87 $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
88 88 exit 1
89 89 end
90 90
91 91 rescue LoadError
92 $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org)
92 $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
93 93 exit 1
94 94 end
95 95
96 96 def parse_gem_version(text)
97 97 $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
98 98 end
99 99
100 100 private
101 101 def read_environment_rb
102 102 File.read("#{RAILS_ROOT}/config/environment.rb")
103 103 end
104 104 end
105 105 end
106 106 end
107 107
108 108 # All that for this:
109 109 Rails.boot!
@@ -1,57 +1,52
1 1 # Be sure to restart your web server when you modify this file.
2 2
3 3 # Uncomment below to force Rails into production mode when
4 4 # you don't control web/app server and can't set it the proper way
5 5 # ENV['RAILS_ENV'] ||= 'production'
6 6
7 7 # Specifies gem version of Rails to use when vendor/rails is not present
8 RAILS_GEM_VERSION = '2.1.2' unless defined? RAILS_GEM_VERSION
8 RAILS_GEM_VERSION = '2.2.2' unless defined? RAILS_GEM_VERSION
9 9
10 10 # Bootstrap the Rails environment, frameworks, and default configuration
11 11 require File.join(File.dirname(__FILE__), 'boot')
12 12
13 13 # Load Engine plugin if available
14 14 begin
15 15 require File.join(File.dirname(__FILE__), '../vendor/plugins/engines/boot')
16 16 rescue LoadError
17 17 # Not available
18 18 end
19 19
20 20 Rails::Initializer.run do |config|
21 21 # Settings in config/environments/* take precedence those specified here
22 22
23 23 # Skip frameworks you're not going to use
24 24 # config.frameworks -= [ :action_web_service, :action_mailer ]
25 25
26 26 # Add additional load paths for sweepers
27 27 config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
28 28
29 29 # Force all environments to use the same logger level
30 30 # (by default production uses :info, the others :debug)
31 31 # config.log_level = :debug
32 32
33 # Use the database for sessions instead of the file system
34 # (create the session table with 'rake db:sessions:create')
35 # config.action_controller.session_store = :active_record_store
36 config.action_controller.session_store = :PStore
37
38 33 # Enable page/fragment caching by setting a file-based store
39 34 # (remember to create the caching directory and make it readable to the application)
40 35 # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
41 36
42 37 # Activate observers that should always be running
43 38 # config.active_record.observers = :cacher, :garbage_collector
44 39 config.active_record.observers = :message_observer
45 40
46 41 # Make Active Record use UTC-base instead of local time
47 42 # config.active_record.default_timezone = :utc
48 43
49 44 # Use Active Record's schema dumper instead of SQL when creating the test database
50 45 # (enables use of different database adapters for development and test environments)
51 46 # config.active_record.schema_format = :ruby
52 47
53 48 # Deliveries are disabled by default. Do NOT modify this section.
54 49 # Define your email configuration in email.yml instead.
55 50 # It will automatically turn deliveries on
56 51 config.action_mailer.perform_deliveries = false
57 52 end
@@ -1,17 +1,22
1 1 # Settings specified here will take precedence over those in config/environment.rb
2 2
3 3 # The test environment is used exclusively to run your application's
4 4 # test suite. You never need to work with it otherwise. Remember that
5 5 # your test database is "scratch space" for the test suite and is wiped
6 6 # and recreated between test runs. Don't rely on the data there!
7 7 config.cache_classes = true
8 8
9 9 # Log error messages when you accidentally call methods on nil.
10 10 config.whiny_nils = true
11 11
12 12 # Show full error reports and disable caching
13 13 config.action_controller.consider_all_requests_local = true
14 14 config.action_controller.perform_caching = false
15 15
16 16 config.action_mailer.perform_deliveries = true
17 17 config.action_mailer.delivery_method = :test
18
19 config.action_controller.session = {
20 :session_key => "_test_session",
21 :secret => "some secret phrase for the tests."
22 }
@@ -1,17 +1,22
1 1 # Settings specified here will take precedence over those in config/environment.rb
2 2
3 3 # The test environment is used exclusively to run your application's
4 4 # test suite. You never need to work with it otherwise. Remember that
5 5 # your test database is "scratch space" for the test suite and is wiped
6 6 # and recreated between test runs. Don't rely on the data there!
7 7 config.cache_classes = true
8 8
9 9 # Log error messages when you accidentally call methods on nil.
10 10 config.whiny_nils = true
11 11
12 12 # Show full error reports and disable caching
13 13 config.action_controller.consider_all_requests_local = true
14 14 config.action_controller.perform_caching = false
15 15
16 16 config.action_mailer.perform_deliveries = true
17 17 config.action_mailer.delivery_method = :test
18
19 config.action_controller.session = {
20 :session_key => "_test_session",
21 :secret => "some secret phrase for the tests."
22 }
@@ -1,17 +1,22
1 1 # Settings specified here will take precedence over those in config/environment.rb
2 2
3 3 # The test environment is used exclusively to run your application's
4 4 # test suite. You never need to work with it otherwise. Remember that
5 5 # your test database is "scratch space" for the test suite and is wiped
6 6 # and recreated between test runs. Don't rely on the data there!
7 7 config.cache_classes = true
8 8
9 9 # Log error messages when you accidentally call methods on nil.
10 10 config.whiny_nils = true
11 11
12 12 # Show full error reports and disable caching
13 13 config.action_controller.consider_all_requests_local = true
14 14 config.action_controller.perform_caching = false
15 15
16 16 config.action_mailer.perform_deliveries = true
17 17 config.action_mailer.delivery_method = :test
18
19 config.action_controller.session = {
20 :session_key => "_test_session",
21 :secret => "some secret phrase for the tests."
22 }
@@ -1,31 +1,50
1 1
2 ActiveRecord::Errors.default_error_messages = {
3 :inclusion => "activerecord_error_inclusion",
4 :exclusion => "activerecord_error_exclusion",
5 :invalid => "activerecord_error_invalid",
6 :confirmation => "activerecord_error_confirmation",
7 :accepted => "activerecord_error_accepted",
8 :empty => "activerecord_error_empty",
9 :blank => "activerecord_error_blank",
10 :too_long => "activerecord_error_too_long",
11 :too_short => "activerecord_error_too_short",
12 :wrong_length => "activerecord_error_wrong_length",
13 :taken => "activerecord_error_taken",
14 :not_a_number => "activerecord_error_not_a_number"
15 } if ActiveRecord::Errors.respond_to?('default_error_messages=')
2 require 'activerecord'
3
4 module ActiveRecord
5 class Base
6 include Redmine::I18n
7
8 # Translate attribute names for validation errors display
9 def self.human_attribute_name(attr)
10 l("field_#{attr.to_s.gsub(/_id$/, '')}")
11 end
12 end
13 end
14
15 module ActionView
16 module Helpers
17 module DateHelper
18 # distance_of_time_in_words breaks when difference is greater than 30 years
19 def distance_of_date_in_words(from_date, to_date = 0, options = {})
20 from_date = from_date.to_date if from_date.respond_to?(:to_date)
21 to_date = to_date.to_date if to_date.respond_to?(:to_date)
22 distance_in_days = (to_date - from_date).abs
23
24 I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
25 case distance_in_days
26 when 0..60 then locale.t :x_days, :count => distance_in_days
27 when 61..720 then locale.t :about_x_months, :count => (distance_in_days / 30).round
28 else locale.t :over_x_years, :count => (distance_in_days / 365).round
29 end
30 end
31 end
32 end
33 end
34 end
16 35
17 36 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" }
18 37
19 38 # Adds :async_smtp and :async_sendmail delivery methods
20 39 # to perform email deliveries asynchronously
21 40 module AsynchronousMailer
22 41 %w(smtp sendmail).each do |type|
23 42 define_method("perform_delivery_async_#{type}") do |mail|
24 43 Thread.start do
25 44 send "perform_delivery_#{type}", mail
26 45 end
27 46 end
28 47 end
29 48 end
30 49
31 50 ActionMailer::Base.send :include, AsynchronousMailer
@@ -1,7 +1,3
1 GLoc.set_config :default_language => :en
2 GLoc.clear_strings
3 GLoc.set_kcode
4 GLoc.load_localized_strings
5 GLoc.set_config(:raise_string_not_found_errors => false)
1 I18n.default_locale = 'en'
6 2
7 3 require 'redmine'
This diff has been collapsed as it changes many lines, (1478 lines changed) Show them Hide them
@@ -1,709 +1,773
1 _gloc_rule_default: '|n| n==1 ? "" : "_plural" '
1 bg:
2 date:
3 formats:
4 # Use the strftime parameters for formats.
5 # When no format has been given, it uses default.
6 # You can provide other formats here if you like!
7 default: "%Y-%m-%d"
8 short: "%b %d"
9 long: "%B %d, %Y"
10
11 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
12 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
13
14 # Don't forget the nil at the beginning; there's no such thing as a 0th month
15 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
16 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
17 # Used in date_select and datime_select.
18 order: [ :year, :month, :day ]
2 19
3 actionview_datehelper_select_day_prefix:
4 actionview_datehelper_select_month_names: Януари,Февруари,Март,Април,Май,Юни,Юли,Август,Септември,Октомври,Ноември,Декември
5 actionview_datehelper_select_month_names_abbr: Яну,Фев,Мар,Апр,Май,Юни,Юли,Авг,Сеп,Окт,Ное,Дек
6 actionview_datehelper_select_month_prefix:
7 actionview_datehelper_select_year_prefix:
8 actionview_datehelper_time_in_words_day: 1 ден
9 actionview_datehelper_time_in_words_day_plural: %d дни
10 actionview_datehelper_time_in_words_hour_about: около час
11 actionview_datehelper_time_in_words_hour_about_plural: около %d часа
12 actionview_datehelper_time_in_words_hour_about_single: около час
13 actionview_datehelper_time_in_words_minute: 1 минута
14 actionview_datehelper_time_in_words_minute_half: половин минута
15 actionview_datehelper_time_in_words_minute_less_than: по-малко от минута
16 actionview_datehelper_time_in_words_minute_plural: %d минути
17 actionview_datehelper_time_in_words_minute_single: 1 минута
18 actionview_datehelper_time_in_words_second_less_than: по-малко от секунда
19 actionview_datehelper_time_in_words_second_less_than_plural: по-малко от %d секунди
20 actionview_instancetag_blank_option: Изберете
20 time:
21 formats:
22 default: "%a, %d %b %Y %H:%M:%S %z"
23 short: "%d %b %H:%M"
24 long: "%B %d, %Y %H:%M"
25 am: "am"
26 pm: "pm"
27
28 datetime:
29 distance_in_words:
30 half_a_minute: "half a minute"
31 less_than_x_seconds:
32 one: "less than 1 second"
33 other: "less than {{count}} seconds"
34 x_seconds:
35 one: "1 second"
36 other: "{{count}} seconds"
37 less_than_x_minutes:
38 one: "less than a minute"
39 other: "less than {{count}} minutes"
40 x_minutes:
41 one: "1 minute"
42 other: "{{count}} minutes"
43 about_x_hours:
44 one: "about 1 hour"
45 other: "about {{count}} hours"
46 x_days:
47 one: "1 day"
48 other: "{{count}} days"
49 about_x_months:
50 one: "about 1 month"
51 other: "about {{count}} months"
52 x_months:
53 one: "1 month"
54 other: "{{count}} months"
55 about_x_years:
56 one: "about 1 year"
57 other: "about {{count}} years"
58 over_x_years:
59 one: "over 1 year"
60 other: "over {{count}} years"
61
62 # Used in array.to_sentence.
63 support:
64 array:
65 sentence_connector: "and"
66 skip_last_comma: false
67
68 activerecord:
69 errors:
70 messages:
71 inclusion: "не съществува в списъка"
72 exclusion: запазено"
73 invalid: невалидно"
74 confirmation: "липсва одобрение"
75 accepted: "трябва да се приеме"
76 empty: "не може да е празно"
77 blank: "не може да е празно"
78 too_long: прекалено дълго"
79 too_short: прекалено късо"
80 wrong_length: с грешна дължина"
81 taken: "вече съществува"
82 not_a_number: "не е число"
83 not_a_date: невалидна дата"
84 greater_than: "must be greater than {{count}}"
85 greater_than_or_equal_to: "must be greater than or equal to {{count}}"
86 equal_to: "must be equal to {{count}}"
87 less_than: "must be less than {{count}}"
88 less_than_or_equal_to: "must be less than or equal to {{count}}"
89 odd: "must be odd"
90 even: "must be even"
91 greater_than_start_date: "трябва да е след началната дата"
92 not_same_project: "не е от същия проект"
93 circular_dependency: "Тази релация ще доведе до безкрайна зависимост"
21 94
22 activerecord_error_inclusion: не съществува в списъка
23 activerecord_error_exclusion: е запазено
24 activerecord_error_invalid: е невалидно
25 activerecord_error_confirmation: липсва одобрение
26 activerecord_error_accepted: трябва да се приеме
27 activerecord_error_empty: не може да е празно
28 activerecord_error_blank: не може да е празно
29 activerecord_error_too_long: е прекалено дълго
30 activerecord_error_too_short: е прекалено късо
31 activerecord_error_wrong_length: е с грешна дължина
32 activerecord_error_taken: вече съществува
33 activerecord_error_not_a_number: не е число
34 activerecord_error_not_a_date: е невалидна дата
35 activerecord_error_greater_than_start_date: трябва да е след началната дата
36 activerecord_error_not_same_project: не е от същия проект
37 activerecord_error_circular_dependency: Тази релация ще доведе до безкрайна зависимост
38
39 general_fmt_age: %d yr
40 general_fmt_age_plural: %d yrs
41 general_fmt_date: %%d.%%m.%%Y
42 general_fmt_datetime: %%d.%%m.%%Y %%H:%%M
43 general_fmt_datetime_short: %%b %%d, %%H:%%M
44 general_fmt_time: %%H:%%M
45 general_text_No: 'Не'
46 general_text_Yes: 'Да'
47 general_text_no: 'не'
48 general_text_yes: 'да'
49 general_lang_name: 'Bulgarian'
50 general_csv_separator: ','
51 general_csv_decimal_separator: '.'
52 general_csv_encoding: UTF-8
53 general_pdf_encoding: UTF-8
54 general_day_names: Понеделник,Вторник,Сряда,Четвъртък,Петък,Събота,Неделя
55 general_first_day_of_week: '1'
56
57 notice_account_updated: Профилът е обновен успешно.
58 notice_account_invalid_creditentials: Невалиден потребител или парола.
59 notice_account_password_updated: Паролата е успешно променена.
60 notice_account_wrong_password: Грешна парола
61 notice_account_register_done: Профилът е създаден успешно.
62 notice_account_unknown_email: Непознат e-mail.
63 notice_can_t_change_password: Този профил е с външен метод за оторизация. Невъзможна смяна на паролата.
64 notice_account_lost_email_sent: Изпратен ви е e-mail с инструкции за избор на нова парола.
65 notice_account_activated: Профилът ви е активиран. Вече може да влезете в системата.
66 notice_successful_create: Успешно създаване.
67 notice_successful_update: Успешно обновяване.
68 notice_successful_delete: Успешно изтриване.
69 notice_successful_connection: Успешно свързване.
70 notice_file_not_found: Несъществуваща или преместена страница.
71 notice_locking_conflict: Друг потребител променя тези данни в момента.
72 notice_not_authorized: Нямате право на достъп до тази страница.
73 notice_email_sent: Изпратен e-mail на %s
74 notice_email_error: Грешка при изпращане на e-mail (%s)
75 notice_feeds_access_key_reseted: Вашия ключ за RSS достъп беше променен.
76
77 error_scm_not_found: Несъществуващ обект в хранилището.
78 error_scm_command_failed: "Грешка при опит за комуникация с хранилище: %s"
79
80 mail_subject_lost_password: Вашата парола (%s)
81 mail_body_lost_password: 'За да смените паролата си, използвайте следния линк:'
82 mail_subject_register: Активация на профил (%s)
83 mail_body_register: 'За да активирате профила си използвайте следния линк:'
84
85 gui_validation_error: 1 грешка
86 gui_validation_error_plural: %d грешки
87
88 field_name: Име
89 field_description: Описание
90 field_summary: Групиран изглед
91 field_is_required: Задължително
92 field_firstname: Име
93 field_lastname: Фамилия
94 field_mail: Email
95 field_filename: Файл
96 field_filesize: Големина
97 field_downloads: Downloads
98 field_author: Автор
99 field_created_on: От дата
100 field_updated_on: Обновена
101 field_field_format: Тип
102 field_is_for_all: За всички проекти
103 field_possible_values: Възможни стойности
104 field_regexp: Регулярен израз
105 field_min_length: Мин. дължина
106 field_max_length: Макс. дължина
107 field_value: Стойност
108 field_category: Категория
109 field_title: Заглавие
110 field_project: Проект
111 field_issue: Задача
112 field_status: Статус
113 field_notes: Бележка
114 field_is_closed: Затворена задача
115 field_is_default: Статус по подразбиране
116 field_tracker: Тракер
117 field_subject: Относно
118 field_due_date: Крайна дата
119 field_assigned_to: Възложена на
120 field_priority: Приоритет
121 field_fixed_version: Планувана версия
122 field_user: Потребител
123 field_role: Роля
124 field_homepage: Начална страница
125 field_is_public: Публичен
126 field_parent: Подпроект на
127 field_is_in_chlog: Да се вижда ли в Изменения
128 field_is_in_roadmap: Да се вижда ли в Пътна карта
129 field_login: Потребител
130 field_mail_notification: Известия по пощата
131 field_admin: Администратор
132 field_last_login_on: Последно свързване
133 field_language: Език
134 field_effective_date: Дата
135 field_password: Парола
136 field_new_password: Нова парола
137 field_password_confirmation: Потвърждение
138 field_version: Версия
139 field_type: Тип
140 field_host: Хост
141 field_port: Порт
142 field_account: Профил
143 field_base_dn: Base DN
144 field_attr_login: Login attribute
145 field_attr_firstname: Firstname attribute
146 field_attr_lastname: Lastname attribute
147 field_attr_mail: Email attribute
148 field_onthefly: Динамично създаване на потребител
149 field_start_date: Начална дата
150 field_done_ratio: %% Прогрес
151 field_auth_source: Начин на оторизация
152 field_hide_mail: Скрий e-mail адреса ми
153 field_comments: Коментар
154 field_url: Адрес
155 field_start_page: Начална страница
156 field_subproject: Подпроект
157 field_hours: Часове
158 field_activity: Дейност
159 field_spent_on: Дата
160 field_identifier: Идентификатор
161 field_is_filter: Използва се за филтър
162 field_issue_to_id: Свързана задача
163 field_delay: Отместване
164 field_assignable: Възможно е възлагане на задачи за тази роля
165 field_redirect_existing_links: Пренасочване на съществуващи линкове
166 field_estimated_hours: Изчислено време
167 field_default_value: Стойност по подразбиране
168
169 setting_app_title: Заглавие
170 setting_app_subtitle: Описание
171 setting_welcome_text: Допълнителен текст
172 setting_default_language: Език по подразбиране
173 setting_login_required: Изискване за вход в системата
174 setting_self_registration: Регистрация от потребители
175 setting_attachment_max_size: Максимална големина на прикачен файл
176 setting_issues_export_limit: Лимит за експорт на задачи
177 setting_mail_from: E-mail адрес за емисии
178 setting_host_name: Хост
179 setting_text_formatting: Форматиране на текста
180 setting_wiki_compression: Wiki компресиране на историята
181 setting_feeds_limit: Лимит на Feeds
182 setting_autofetch_changesets: Автоматично обработване на ревизиите
183 setting_sys_api_enabled: Разрешаване на WS за управление
184 setting_commit_ref_keywords: Отбелязващи ключови думи
185 setting_commit_fix_keywords: Приключващи ключови думи
186 setting_autologin: Автоматичен вход
187 setting_date_format: Формат на датата
188 setting_cross_project_issue_relations: Релации на задачи между проекти
189
190 label_user: Потребител
191 label_user_plural: Потребители
192 label_user_new: Нов потребител
193 label_project: Проект
194 label_project_new: Нов проект
195 label_project_plural: Проекти
196 label_project_all: Всички проекти
197 label_project_latest: Последни проекти
198 label_issue: Задача
199 label_issue_new: Нова задача
200 label_issue_plural: Задачи
201 label_issue_view_all: Всички задачи
202 label_document: Документ
203 label_document_new: Нов документ
204 label_document_plural: Документи
205 label_role: Роля
206 label_role_plural: Роли
207 label_role_new: Нова роля
208 label_role_and_permissions: Роли и права
209 label_member: Член
210 label_member_new: Нов член
211 label_member_plural: Членове
212 label_tracker: Тракер
213 label_tracker_plural: Тракери
214 label_tracker_new: Нов тракер
215 label_workflow: Работен процес
216 label_issue_status: Статус на задача
217 label_issue_status_plural: Статуси на задачи
218 label_issue_status_new: Нов статус
219 label_issue_category: Категория задача
220 label_issue_category_plural: Категории задачи
221 label_issue_category_new: Нова категория
222 label_custom_field: Потребителско поле
223 label_custom_field_plural: Потребителски полета
224 label_custom_field_new: Ново потребителско поле
225 label_enumerations: Списъци
226 label_enumeration_new: Нова стойност
227 label_information: Информация
228 label_information_plural: Информация
229 label_please_login: Вход
230 label_register: Регистрация
231 label_password_lost: Забравена парола
232 label_home: Начало
233 label_my_page: Лична страница
234 label_my_account: Профил
235 label_my_projects: Проекти, в които участвам
236 label_administration: Администрация
237 label_login: Вход
238 label_logout: Изход
239 label_help: Помощ
240 label_reported_issues: Публикувани задачи
241 label_assigned_to_me_issues: Възложени на мен
242 label_last_login: Последно свързване
243 label_last_updates: Последно обновена
244 label_last_updates_plural: %d последно обновени
245 label_registered_on: Регистрация
246 label_activity: Дейност
247 label_new: Нов
248 label_logged_as: Логнат като
249 label_environment: Среда
250 label_authentication: Оторизация
251 label_auth_source: Начин на оторозация
252 label_auth_source_new: Нов начин на оторизация
253 label_auth_source_plural: Начини на оторизация
254 label_subproject_plural: Подпроекти
255 label_min_max_length: Мин. - Макс. дължина
256 label_list: Списък
257 label_date: Дата
258 label_integer: Целочислен
259 label_boolean: Чекбокс
260 label_string: Текст
261 label_text: Дълъг текст
262 label_attribute: Атрибут
263 label_attribute_plural: Атрибути
264 label_download: %d Download
265 label_download_plural: %d Downloads
266 label_no_data: Няма изходни данни
267 label_change_status: Промяна на статуса
268 label_history: История
269 label_attachment: Файл
270 label_attachment_new: Нов файл
271 label_attachment_delete: Изтриване
272 label_attachment_plural: Файлове
273 label_report: Справка
274 label_report_plural: Справки
275 label_news: Новини
276 label_news_new: Добави
277 label_news_plural: Новини
278 label_news_latest: Последни новини
279 label_news_view_all: Виж всички
280 label_change_log: Изменения
281 label_settings: Настройки
282 label_overview: Общ изглед
283 label_version: Версия
284 label_version_new: Нова версия
285 label_version_plural: Версии
286 label_confirmation: Одобрение
287 label_export_to: Експорт към
288 label_read: Read...
289 label_public_projects: Публични проекти
290 label_open_issues: отворена
291 label_open_issues_plural: отворени
292 label_closed_issues: затворена
293 label_closed_issues_plural: затворени
294 label_total: Общо
295 label_permissions: Права
296 label_current_status: Текущ статус
297 label_new_statuses_allowed: Позволени статуси
298 label_all: всички
299 label_none: никакви
300 label_next: Следващ
301 label_previous: Предишен
302 label_used_by: Използва се от
303 label_details: Детайли
304 label_add_note: Добавяне на бележка
305 label_per_page: На страница
306 label_calendar: Календар
307 label_months_from: месеца от
308 label_gantt: Gantt
309 label_internal: Вътрешен
310 label_last_changes: последни %d промени
311 label_change_view_all: Виж всички промени
312 label_personalize_page: Персонализиране
313 label_comment: Коментар
314 label_comment_plural: Коментари
315 label_comment_add: Добавяне на коментар
316 label_comment_added: Добавен коментар
317 label_comment_delete: Изтриване на коментари
318 label_query: Потребителска справка
319 label_query_plural: Потребителски справки
320 label_query_new: Нова заявка
321 label_filter_add: Добави филтър
322 label_filter_plural: Филтри
323 label_equals: е
324 label_not_equals: не е
325 label_in_less_than: след по-малко от
326 label_in_more_than: след повече от
327 label_in: в следващите
328 label_today: днес
329 label_this_week: тази седмица
330 label_less_than_ago: преди по-малко от
331 label_more_than_ago: преди повече от
332 label_ago: преди
333 label_contains: съдържа
334 label_not_contains: не съдържа
335 label_day_plural: дни
336 label_repository: Хранилище
337 label_browse: Разглеждане
338 label_modification: %d промяна
339 label_modification_plural: %d промени
340 label_revision: Ревизия
341 label_revision_plural: Ревизии
342 label_added: добавено
343 label_modified: променено
344 label_deleted: изтрито
345 label_latest_revision: Последна ревизия
346 label_latest_revision_plural: Последни ревизии
347 label_view_revisions: Виж ревизиите
348 label_max_size: Максимална големина
349 label_on: 'от'
350 label_sort_highest: Премести най-горе
351 label_sort_higher: Премести по-горе
352 label_sort_lower: Премести по-долу
353 label_sort_lowest: Премести най-долу
354 label_roadmap: Пътна карта
355 label_roadmap_due_in: Излиза след %s
356 label_roadmap_overdue: %s закъснение
357 label_roadmap_no_issues: Няма задачи за тази версия
358 label_search: Търсене
359 label_result_plural: Pезултати
360 label_all_words: Всички думи
361 label_wiki: Wiki
362 label_wiki_edit: Wiki редакция
363 label_wiki_edit_plural: Wiki редакции
364 label_wiki_page: Wiki page
365 label_wiki_page_plural: Wiki pages
366 label_index_by_title: Индекс
367 label_index_by_date: Индекс по дата
368 label_current_version: Текуща версия
369 label_preview: Преглед
370 label_feed_plural: Feeds
371 label_changes_details: Подробни промени
372 label_issue_tracking: Тракинг
373 label_spent_time: Отделено време
374 label_f_hour: %.2f час
375 label_f_hour_plural: %.2f часа
376 label_time_tracking: Отделяне на време
377 label_change_plural: Промени
378 label_statistics: Статистики
379 label_commits_per_month: Ревизии по месеци
380 label_commits_per_author: Ревизии по автор
381 label_view_diff: Виж разликите
382 label_diff_inline: хоризонтално
383 label_diff_side_by_side: вертикално
384 label_options: Опции
385 label_copy_workflow_from: Копирай работния процес от
386 label_permissions_report: Справка за права
387 label_watched_issues: Наблюдавани задачи
388 label_related_issues: Свързани задачи
389 label_applied_status: Промени статуса на
390 label_loading: Зареждане...
391 label_relation_new: Нова релация
392 label_relation_delete: Изтриване на релация
393 label_relates_to: свързана със
394 label_duplicates: дублира
395 label_blocks: блокира
396 label_blocked_by: блокирана от
397 label_precedes: предшества
398 label_follows: изпълнява се след
399 label_end_to_start: end to start
400 label_end_to_end: end to end
401 label_start_to_start: start to start
402 label_start_to_end: start to end
403 label_stay_logged_in: Запомни ме
404 label_disabled: забранено
405 label_show_completed_versions: Показване на реализирани версии
406 label_me: аз
407 label_board: Форум
408 label_board_new: Нов форум
409 label_board_plural: Форуми
410 label_topic_plural: Теми
411 label_message_plural: Съобщения
412 label_message_last: Последно съобщение
413 label_message_new: Нова тема
414 label_reply_plural: Отговори
415 label_send_information: Изпращане на информацията до потребителя
416 label_year: Година
417 label_month: Месец
418 label_week: Седмица
419 label_date_from: От
420 label_date_to: До
421 label_language_based: В зависимост от езика
422 label_sort_by: Сортиране по %s
423 label_send_test_email: Изпращане на тестов e-mail
424 label_feeds_access_key_created_on: %s от създаването на RSS ключа
425 label_module_plural: Модули
426 label_added_time_by: Публикувана от %s преди %s
427 label_updated_time: Обновена преди %s
428 label_jump_to_a_project: Проект...
429
430 button_login: Вход
431 button_submit: Прикачване
432 button_save: Запис
433 button_check_all: Избор на всички
434 button_uncheck_all: Изчистване на всички
435 button_delete: Изтриване
436 button_create: Създаване
437 button_test: Тест
438 button_edit: Редакция
439 button_add: Добавяне
440 button_change: Промяна
441 button_apply: Приложи
442 button_clear: Изчисти
443 button_lock: Заключване
444 button_unlock: Отключване
445 button_download: Download
446 button_list: Списък
447 button_view: Преглед
448 button_move: Преместване
449 button_back: Назад
450 button_cancel: Отказ
451 button_activate: Активация
452 button_sort: Сортиране
453 button_log_time: Отделяне на време
454 button_rollback: Върни се към тази ревизия
455 button_watch: Наблюдавай
456 button_unwatch: Спри наблюдението
457 button_reply: Отговор
458 button_archive: Архивиране
459 button_unarchive: Разархивиране
460 button_reset: Генериране наново
461 button_rename: Преименуване
462
463 status_active: активен
464 status_registered: регистриран
465 status_locked: заключен
466
467 text_select_mail_notifications: Изберете събития за изпращане на e-mail.
468 text_regexp_info: пр. ^[A-Z0-9]+$
469 text_min_max_length_info: 0 - без ограничения
470 text_project_destroy_confirmation: Сигурни ли сте, че искате да изтриете проекта и данните в него?
471 text_workflow_edit: Изберете роля и тракер за да редактирате работния процес
472 text_are_you_sure: Сигурни ли сте?
473 text_journal_changed: промяна от %s на %s
474 text_journal_set_to: установено на %s
475 text_journal_deleted: изтрито
476 text_tip_task_begin_day: задача започваща този ден
477 text_tip_task_end_day: задача завършваща този ден
478 text_tip_task_begin_end_day: задача започваща и завършваща този ден
479 text_project_identifier_info: 'Позволени са малки букви (a-z), цифри и тирета.<br />Невъзможна промяна след запис.'
480 text_caracters_maximum: До %d символа.
481 text_length_between: От %d до %d символа.
482 text_tracker_no_workflow: Няма дефиниран работен процес за този тракер
483 text_unallowed_characters: Непозволени символи
484 text_comma_separated: Позволено е изброяване (с разделител запетая).
485 text_issues_ref_in_commit_messages: Отбелязване и приключване на задачи от ревизии
486 text_issue_added: Публикувана е нова задача с номер %s (от %s).
487 text_issue_updated: Задача %s е обновена (от %s).
488 text_wiki_destroy_confirmation: Сигурни ли сте, че искате да изтриете това Wiki и цялото му съдържание?
489 text_issue_category_destroy_question: Има задачи (%d) обвързани с тази категория. Какво ще изберете?
490 text_issue_category_destroy_assignments: Премахване на връзките с категорията
491 text_issue_category_reassign_to: Преобвързване с категория
492
493 default_role_manager: Мениджър
494 default_role_developper: Разработчик
495 default_role_reporter: Публикуващ
496 default_tracker_bug: Бъг
497 default_tracker_feature: Функционалност
498 default_tracker_support: Поддръжка
499 default_issue_status_new: Нова
500 default_issue_status_assigned: Възложена
501 default_issue_status_resolved: Приключена
502 default_issue_status_feedback: Обратна връзка
503 default_issue_status_closed: Затворена
504 default_issue_status_rejected: Отхвърлена
505 default_doc_category_user: Документация за потребителя
506 default_doc_category_tech: Техническа документация
507 default_priority_low: Нисък
508 default_priority_normal: Нормален
509 default_priority_high: Висок
510 default_priority_urgent: Спешен
511 default_priority_immediate: Веднага
512 default_activity_design: Дизайн
513 default_activity_development: Разработка
514
515 enumeration_issue_priorities: Приоритети на задачи
516 enumeration_doc_categories: Категории документи
517 enumeration_activities: Дейности (time tracking)
518 label_file_plural: Файлове
519 label_changeset_plural: Ревизии
520 field_column_names: Колони
521 label_default_columns: По подразбиране
522 setting_issue_list_default_columns: Показвани колони по подразбиране
523 setting_repositories_encodings: Кодови таблици
524 notice_no_issue_selected: "Няма избрани задачи."
525 label_bulk_edit_selected_issues: Редактиране на задачи
526 label_no_change_option: (Без промяна)
527 notice_failed_to_save_issues: "Неуспешен запис на %d задачи от %d избрани: %s."
528 label_theme: Тема
529 label_default: По подразбиране
530 label_search_titles_only: Само в заглавията
531 label_nobody: никой
532 button_change_password: Промяна на парола
533 text_user_mail_option: "За неизбраните проекти, ще получавате известия само за наблюдавани дейности или в които участвате (т.е. автор или назначени на мен)."
534 label_user_mail_option_selected: "За всички събития само в избраните проекти..."
535 label_user_mail_option_all: "За всяко събитие в проектите, в които участвам"
536 label_user_mail_option_none: "Само за наблюдавани или в които участвам (автор или назначени на мен)"
537 setting_emails_footer: Подтекст за e-mail
538 label_float: Дробно
539 button_copy: Копиране
540 mail_body_account_information_external: Можете да използвате вашия "%s" профил за вход.
541 mail_body_account_information: Информацията за профила ви
542 setting_protocol: Протокол
543 label_user_mail_no_self_notified: "Не искам известия за извършени от мен промени"
544 setting_time_format: Формат на часа
545 label_registration_activation_by_email: активиране на профила по email
546 mail_subject_account_activation_request: Заявка за активиране на профил в %s
547 mail_body_account_activation_request: 'Има новорегистриран потребител (%s), очакващ вашето одобрение:'
548 label_registration_automatic_activation: автоматично активиране
549 label_registration_manual_activation: ръчно активиране
550 notice_account_pending: "Профилът Ви е създаден и очаква одобрение от администратор."
551 field_time_zone: Часова зона
552 text_caracters_minimum: Минимум %d символа.
553 setting_bcc_recipients: Получатели на скрито копие (bcc)
554 button_annotate: Анотация
555 label_issues_by: Задачи по %s
556 field_searchable: С възможност за търсене
557 label_display_per_page: 'На страница по: %s'
558 setting_per_page_options: Опции за страниране
559 label_age: Възраст
560 notice_default_data_loaded: Примерната информацията е успешно заредена.
561 text_load_default_configuration: Зареждане на примерна информация
562 text_no_configuration_data: "Все още не са конфигурирани Роли, тракери, статуси на задачи и работен процес.\nСтрого се препоръчва зареждането на примерната информация. Веднъж заредена ще имате възможност да я редактирате."
563 error_can_t_load_default_data: "Грешка при зареждане на примерната информация: %s"
564 button_update: Обновяване
565 label_change_properties: Промяна на настройки
566 label_general: Основни
567 label_repository_plural: Хранилища
568 label_associated_revisions: Асоциирани ревизии
569 setting_user_format: Потребителски формат
570 text_status_changed_by_changeset: Приложено с ревизия %s.
571 label_more: Още
572 text_issues_destroy_confirmation: 'Сигурни ли сте, че искате да изтриете избраните задачи?'
573 label_scm: SCM (Система за контрол на кода)
574 text_select_project_modules: 'Изберете активните модули за този проект:'
575 label_issue_added: Добавена задача
576 label_issue_updated: Обновена задача
577 label_document_added: Добавен документ
578 label_message_posted: Добавено съобщение
579 label_file_added: Добавен файл
580 label_news_added: Добавена новина
581 project_module_boards: Форуми
582 project_module_issue_tracking: Тракинг
583 project_module_wiki: Wiki
584 project_module_files: Файлове
585 project_module_documents: Документи
586 project_module_repository: Хранилище
587 project_module_news: Новини
588 project_module_time_tracking: Отделяне на време
589 text_file_repository_writable: Възможност за писане в хранилището с файлове
590 text_default_administrator_account_changed: Сменен фабричния администраторски профил
591 text_rmagick_available: Наличен RMagick (по избор)
592 button_configure: Конфигуриране
593 label_plugins: Плъгини
594 label_ldap_authentication: LDAP оторизация
595 label_downloads_abbr: D/L
596 label_this_month: текущия месец
597 label_last_n_days: последните %d дни
598 label_all_time: всички
599 label_this_year: текущата година
600 label_date_range: Период
601 label_last_week: последната седмица
602 label_yesterday: вчера
603 label_last_month: последния месец
604 label_add_another_file: Добавяне на друг файл
605 label_optional_description: Незадължително описание
606 text_destroy_time_entries_question: %.02f часа са отделени на задачите, които искате да изтриете. Какво избирате?
607 error_issue_not_found_in_project: 'Задачата не е намерена или не принадлежи на този проект'
608 text_assign_time_entries_to_project: Прехвърляне на отделеното време към проект
609 text_destroy_time_entries: Изтриване на отделеното време
610 text_reassign_time_entries: 'Прехвърляне на отделеното време към задача:'
611 setting_activity_days_default: Брой дни показвани на таб Дейност
612 label_chronological_order: Хронологичен ред
613 field_comments_sorting: Сортиране на коментарите
614 label_reverse_chronological_order: Обратен хронологичен ред
615 label_preferences: Предпочитания
616 setting_display_subprojects_issues: Показване на подпроектите в проектите по подразбиране
617 label_overall_activity: Цялостна дейност
618 setting_default_projects_public: Новите проекти са публични по подразбиране
619 error_scm_annotate: "Обектът не съществува или не може да бъде анотиран."
620 label_planning: Планиране
621 text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.'
622 label_and_its_subprojects: %s and its subprojects
623 mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:"
624 mail_subject_reminder: "%d issue(s) due in the next days"
625 text_user_wrote: '%s wrote:'
626 label_duplicated_by: duplicated by
627 setting_enabled_scm: Enabled SCM
628 text_enumeration_category_reassign_to: 'Reassign them to this value:'
629 text_enumeration_destroy_question: '%d objects are assigned to this value.'
630 label_incoming_emails: Incoming emails
631 label_generate_key: Generate a key
632 setting_mail_handler_api_enabled: Enable WS for incoming emails
633 setting_mail_handler_api_key: API key
634 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
635 field_parent_title: Parent page
636 label_issue_watchers: Watchers
637 setting_commit_logs_encoding: Commit messages encoding
638 button_quote: Quote
639 setting_sequential_project_identifiers: Generate sequential project identifiers
640 notice_unable_delete_version: Unable to delete version
641 label_renamed: renamed
642 label_copied: copied
643 setting_plain_text_mail: plain text only (no HTML)
644 permission_view_files: View files
645 permission_edit_issues: Edit issues
646 permission_edit_own_time_entries: Edit own time logs
647 permission_manage_public_queries: Manage public queries
648 permission_add_issues: Add issues
649 permission_log_time: Log spent time
650 permission_view_changesets: View changesets
651 permission_view_time_entries: View spent time
652 permission_manage_versions: Manage versions
653 permission_manage_wiki: Manage wiki
654 permission_manage_categories: Manage issue categories
655 permission_protect_wiki_pages: Protect wiki pages
656 permission_comment_news: Comment news
657 permission_delete_messages: Delete messages
658 permission_select_project_modules: Select project modules
659 permission_manage_documents: Manage documents
660 permission_edit_wiki_pages: Edit wiki pages
661 permission_add_issue_watchers: Add watchers
662 permission_view_gantt: View gantt chart
663 permission_move_issues: Move issues
664 permission_manage_issue_relations: Manage issue relations
665 permission_delete_wiki_pages: Delete wiki pages
666 permission_manage_boards: Manage boards
667 permission_delete_wiki_pages_attachments: Delete attachments
668 permission_view_wiki_edits: View wiki history
669 permission_add_messages: Post messages
670 permission_view_messages: View messages
671 permission_manage_files: Manage files
672 permission_edit_issue_notes: Edit notes
673 permission_manage_news: Manage news
674 permission_view_calendar: View calendrier
675 permission_manage_members: Manage members
676 permission_edit_messages: Edit messages
677 permission_delete_issues: Delete issues
678 permission_view_issue_watchers: View watchers list
679 permission_manage_repository: Manage repository
680 permission_commit_access: Commit access
681 permission_browse_repository: Browse repository
682 permission_view_documents: View documents
683 permission_edit_project: Edit project
684 permission_add_issue_notes: Add notes
685 permission_save_queries: Save queries
686 permission_view_wiki_pages: View wiki
687 permission_rename_wiki_pages: Rename wiki pages
688 permission_edit_time_entries: Edit time logs
689 permission_edit_own_issue_notes: Edit own notes
690 setting_gravatar_enabled: Use Gravatar user icons
691 label_example: Example
692 text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
693 permission_edit_own_messages: Edit own messages
694 permission_delete_own_messages: Delete own messages
695 label_user_activity: "%s's activity"
696 label_updated_time_by: Updated by %s %s ago
697 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
698 setting_diff_max_lines_displayed: Max number of diff lines displayed
699 text_plugin_assets_writable: Plugin assets directory writable
700 warning_attachments_not_saved: "%d file(s) could not be saved."
701 button_create_and_continue: Create and continue
702 text_custom_field_possible_values_info: 'One line for each value'
703 label_display: Display
704 field_editable: Editable
705 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
706 field_identity_url: OpenID URL
707 setting_openid: Allow OpenID login and registration
708 label_login_with_open_id_option: or login with OpenID
709 field_watcher: Watcher
95 actionview_instancetag_blank_option: Изберете
96
97 general_text_No: 'Не'
98 general_text_Yes: 'Да'
99 general_text_no: 'не'
100 general_text_yes: 'да'
101 general_lang_name: 'Bulgarian'
102 general_csv_separator: ','
103 general_csv_decimal_separator: '.'
104 general_csv_encoding: UTF-8
105 general_pdf_encoding: UTF-8
106 general_first_day_of_week: '1'
107
108 notice_account_updated: Профилът е обновен успешно.
109 notice_account_invalid_creditentials: Невалиден потребител или парола.
110 notice_account_password_updated: Паролата е успешно променена.
111 notice_account_wrong_password: Грешна парола
112 notice_account_register_done: Профилът е създаден успешно.
113 notice_account_unknown_email: Непознат e-mail.
114 notice_can_t_change_password: Този профил е с външен метод за оторизация. Невъзможна смяна на паролата.
115 notice_account_lost_email_sent: Изпратен ви е e-mail с инструкции за избор на нова парола.
116 notice_account_activated: Профилът ви е активиран. Вече може да влезете в системата.
117 notice_successful_create: Успешно създаване.
118 notice_successful_update: Успешно обновяване.
119 notice_successful_delete: Успешно изтриване.
120 notice_successful_connection: Успешно свързване.
121 notice_file_not_found: Несъществуваща или преместена страница.
122 notice_locking_conflict: Друг потребител променя тези данни в момента.
123 notice_not_authorized: Нямате право на достъп до тази страница.
124 notice_email_sent: "Изпратен e-mail на {{value}}"
125 notice_email_error: "Грешка при изпращане на e-mail ({{value}})"
126 notice_feeds_access_key_reseted: Вашия ключ за RSS достъп беше променен.
127
128 error_scm_not_found: Несъществуващ обект в хранилището.
129 error_scm_command_failed: "Грешка при опит за комуникация с хранилище: {{value}}"
130
131 mail_subject_lost_password: "Вашата парола ({{value}})"
132 mail_body_lost_password: 'За да смените паролата си, използвайте следния линк:'
133 mail_subject_register: "Активация на профил ({{value}})"
134 mail_body_register: 'За да активирате профила си използвайте следния линк:'
135
136 gui_validation_error: 1 грешка
137 gui_validation_error_plural: "{{count}} грешки"
138
139 field_name: Име
140 field_description: Описание
141 field_summary: Групиран изглед
142 field_is_required: Задължително
143 field_firstname: Име
144 field_lastname: Фамилия
145 field_mail: Email
146 field_filename: Файл
147 field_filesize: Големина
148 field_downloads: Downloads
149 field_author: Автор
150 field_created_on: От дата
151 field_updated_on: Обновена
152 field_field_format: Тип
153 field_is_for_all: За всички проекти
154 field_possible_values: Възможни стойности
155 field_regexp: Регулярен израз
156 field_min_length: Мин. дължина
157 field_max_length: Макс. дължина
158 field_value: Стойност
159 field_category: Категория
160 field_title: Заглавие
161 field_project: Проект
162 field_issue: Задача
163 field_status: Статус
164 field_notes: Бележка
165 field_is_closed: Затворена задача
166 field_is_default: Статус по подразбиране
167 field_tracker: Тракер
168 field_subject: Относно
169 field_due_date: Крайна дата
170 field_assigned_to: Възложена на
171 field_priority: Приоритет
172 field_fixed_version: Планувана версия
173 field_user: Потребител
174 field_role: Роля
175 field_homepage: Начална страница
176 field_is_public: Публичен
177 field_parent: Подпроект на
178 field_is_in_chlog: Да се вижда ли в Изменения
179 field_is_in_roadmap: Да се вижда ли в Пътна карта
180 field_login: Потребител
181 field_mail_notification: Известия по пощата
182 field_admin: Администратор
183 field_last_login_on: Последно свързване
184 field_language: Език
185 field_effective_date: Дата
186 field_password: Парола
187 field_new_password: Нова парола
188 field_password_confirmation: Потвърждение
189 field_version: Версия
190 field_type: Тип
191 field_host: Хост
192 field_port: Порт
193 field_account: Профил
194 field_base_dn: Base DN
195 field_attr_login: Login attribute
196 field_attr_firstname: Firstname attribute
197 field_attr_lastname: Lastname attribute
198 field_attr_mail: Email attribute
199 field_onthefly: Динамично създаване на потребител
200 field_start_date: Начална дата
201 field_done_ratio: %% Прогрес
202 field_auth_source: Начин на оторизация
203 field_hide_mail: Скрий e-mail адреса ми
204 field_comments: Коментар
205 field_url: Адрес
206 field_start_page: Начална страница
207 field_subproject: Подпроект
208 field_hours: Часове
209 field_activity: Дейност
210 field_spent_on: Дата
211 field_identifier: Идентификатор
212 field_is_filter: Използва се за филтър
213 field_issue_to_id: Свързана задача
214 field_delay: Отместване
215 field_assignable: Възможно е възлагане на задачи за тази роля
216 field_redirect_existing_links: Пренасочване на съществуващи линкове
217 field_estimated_hours: Изчислено време
218 field_default_value: Стойност по подразбиране
219
220 setting_app_title: Заглавие
221 setting_app_subtitle: Описание
222 setting_welcome_text: Допълнителен текст
223 setting_default_language: Език по подразбиране
224 setting_login_required: Изискване за вход в системата
225 setting_self_registration: Регистрация от потребители
226 setting_attachment_max_size: Максимална големина на прикачен файл
227 setting_issues_export_limit: Лимит за експорт на задачи
228 setting_mail_from: E-mail адрес за емисии
229 setting_host_name: Хост
230 setting_text_formatting: Форматиране на текста
231 setting_wiki_compression: Wiki компресиране на историята
232 setting_feeds_limit: Лимит на Feeds
233 setting_autofetch_changesets: Автоматично обработване на ревизиите
234 setting_sys_api_enabled: Разрешаване на WS за управление
235 setting_commit_ref_keywords: Отбелязващи ключови думи
236 setting_commit_fix_keywords: Приключващи ключови думи
237 setting_autologin: Автоматичен вход
238 setting_date_format: Формат на датата
239 setting_cross_project_issue_relations: Релации на задачи между проекти
240
241 label_user: Потребител
242 label_user_plural: Потребители
243 label_user_new: Нов потребител
244 label_project: Проект
245 label_project_new: Нов проект
246 label_project_plural: Проекти
247 label_x_projects:
248 zero: no projects
249 one: 1 project
250 other: "{{count}} projects"
251 label_project_all: Всички проекти
252 label_project_latest: Последни проекти
253 label_issue: Задача
254 label_issue_new: Нова задача
255 label_issue_plural: Задачи
256 label_issue_view_all: Всички задачи
257 label_document: Документ
258 label_document_new: Нов документ
259 label_document_plural: Документи
260 label_role: Роля
261 label_role_plural: Роли
262 label_role_new: Нова роля
263 label_role_and_permissions: Роли и права
264 label_member: Член
265 label_member_new: Нов член
266 label_member_plural: Членове
267 label_tracker: Тракер
268 label_tracker_plural: Тракери
269 label_tracker_new: Нов тракер
270 label_workflow: Работен процес
271 label_issue_status: Статус на задача
272 label_issue_status_plural: Статуси на задачи
273 label_issue_status_new: Нов статус
274 label_issue_category: Категория задача
275 label_issue_category_plural: Категории задачи
276 label_issue_category_new: Нова категория
277 label_custom_field: Потребителско поле
278 label_custom_field_plural: Потребителски полета
279 label_custom_field_new: Ново потребителско поле
280 label_enumerations: Списъци
281 label_enumeration_new: Нова стойност
282 label_information: Информация
283 label_information_plural: Информация
284 label_please_login: Вход
285 label_register: Регистрация
286 label_password_lost: Забравена парола
287 label_home: Начало
288 label_my_page: Лична страница
289 label_my_account: Профил
290 label_my_projects: Проекти, в които участвам
291 label_administration: Администрация
292 label_login: Вход
293 label_logout: Изход
294 label_help: Помощ
295 label_reported_issues: Публикувани задачи
296 label_assigned_to_me_issues: Възложени на мен
297 label_last_login: Последно свързване
298 label_registered_on: Регистрация
299 label_activity: Дейност
300 label_new: Нов
301 label_logged_as: Логнат като
302 label_environment: Среда
303 label_authentication: Оторизация
304 label_auth_source: Начин на оторозация
305 label_auth_source_new: Нов начин на оторизация
306 label_auth_source_plural: Начини на оторизация
307 label_subproject_plural: Подпроекти
308 label_min_max_length: Мин. - Макс. дължина
309 label_list: Списък
310 label_date: Дата
311 label_integer: Целочислен
312 label_boolean: Чекбокс
313 label_string: Текст
314 label_text: Дълъг текст
315 label_attribute: Атрибут
316 label_attribute_plural: Атрибути
317 label_download: "{{count}} Download"
318 label_download_plural: "{{count}} Downloads"
319 label_no_data: Няма изходни данни
320 label_change_status: Промяна на статуса
321 label_history: История
322 label_attachment: Файл
323 label_attachment_new: Нов файл
324 label_attachment_delete: Изтриване
325 label_attachment_plural: Файлове
326 label_report: Справка
327 label_report_plural: Справки
328 label_news: Новини
329 label_news_new: Добави
330 label_news_plural: Новини
331 label_news_latest: Последни новини
332 label_news_view_all: Виж всички
333 label_change_log: Изменения
334 label_settings: Настройки
335 label_overview: Общ изглед
336 label_version: Версия
337 label_version_new: Нова версия
338 label_version_plural: Версии
339 label_confirmation: Одобрение
340 label_export_to: Експорт към
341 label_read: Read...
342 label_public_projects: Публични проекти
343 label_open_issues: отворена
344 label_open_issues_plural: отворени
345 label_closed_issues: затворена
346 label_closed_issues_plural: затворени
347 label_x_open_issues_abbr_on_total:
348 zero: 0 open / {{total}}
349 one: 1 open / {{total}}
350 other: "{{count}} open / {{total}}"
351 label_x_open_issues_abbr:
352 zero: 0 open
353 one: 1 open
354 other: "{{count}} open"
355 label_x_closed_issues_abbr:
356 zero: 0 closed
357 one: 1 closed
358 other: "{{count}} closed"
359 label_total: Общо
360 label_permissions: Права
361 label_current_status: Текущ статус
362 label_new_statuses_allowed: Позволени статуси
363 label_all: всички
364 label_none: никакви
365 label_next: Следващ
366 label_previous: Предишен
367 label_used_by: Използва се от
368 label_details: Детайли
369 label_add_note: Добавяне на бележка
370 label_per_page: На страница
371 label_calendar: Календар
372 label_months_from: месеца от
373 label_gantt: Gantt
374 label_internal: Вътрешен
375 label_last_changes: "последни {{count}} промени"
376 label_change_view_all: Виж всички промени
377 label_personalize_page: Персонализиране
378 label_comment: Коментар
379 label_comment_plural: Коментари
380 label_x_comments:
381 zero: no comments
382 one: 1 comment
383 other: "{{count}} comments"
384 label_comment_add: Добавяне на коментар
385 label_comment_added: Добавен коментар
386 label_comment_delete: Изтриване на коментари
387 label_query: Потребителска справка
388 label_query_plural: Потребителски справки
389 label_query_new: Нова заявка
390 label_filter_add: Добави филтър
391 label_filter_plural: Филтри
392 label_equals: е
393 label_not_equals: не е
394 label_in_less_than: след по-малко от
395 label_in_more_than: след повече от
396 label_in: в следващите
397 label_today: днес
398 label_this_week: тази седмица
399 label_less_than_ago: преди по-малко от
400 label_more_than_ago: преди повече от
401 label_ago: преди
402 label_contains: съдържа
403 label_not_contains: не съдържа
404 label_day_plural: дни
405 label_repository: Хранилище
406 label_browse: Разглеждане
407 label_modification: "{{count}} промяна"
408 label_modification_plural: "{{count}} промени"
409 label_revision: Ревизия
410 label_revision_plural: Ревизии
411 label_added: добавено
412 label_modified: променено
413 label_deleted: изтрито
414 label_latest_revision: Последна ревизия
415 label_latest_revision_plural: Последни ревизии
416 label_view_revisions: Виж ревизиите
417 label_max_size: Максимална големина
418 label_sort_highest: Премести най-горе
419 label_sort_higher: Премести по-горе
420 label_sort_lower: Премести по-долу
421 label_sort_lowest: Премести най-долу
422 label_roadmap: Пътна карта
423 label_roadmap_due_in: "Излиза след {{value}}"
424 label_roadmap_overdue: "{{value}} закъснение"
425 label_roadmap_no_issues: Няма задачи за тази версия
426 label_search: Търсене
427 label_result_plural: Pезултати
428 label_all_words: Всички думи
429 label_wiki: Wiki
430 label_wiki_edit: Wiki редакция
431 label_wiki_edit_plural: Wiki редакции
432 label_wiki_page: Wiki page
433 label_wiki_page_plural: Wiki pages
434 label_index_by_title: Индекс
435 label_index_by_date: Индекс по дата
436 label_current_version: Текуща версия
437 label_preview: Преглед
438 label_feed_plural: Feeds
439 label_changes_details: Подробни промени
440 label_issue_tracking: Тракинг
441 label_spent_time: Отделено време
442 label_f_hour: "{{value}} час"
443 label_f_hour_plural: "{{value}} часа"
444 label_time_tracking: Отделяне на време
445 label_change_plural: Промени
446 label_statistics: Статистики
447 label_commits_per_month: Ревизии по месеци
448 label_commits_per_author: Ревизии по автор
449 label_view_diff: Виж разликите
450 label_diff_inline: хоризонтално
451 label_diff_side_by_side: вертикално
452 label_options: Опции
453 label_copy_workflow_from: Копирай работния процес от
454 label_permissions_report: Справка за права
455 label_watched_issues: Наблюдавани задачи
456 label_related_issues: Свързани задачи
457 label_applied_status: Промени статуса на
458 label_loading: Зареждане...
459 label_relation_new: Нова релация
460 label_relation_delete: Изтриване на релация
461 label_relates_to: свързана със
462 label_duplicates: дублира
463 label_blocks: блокира
464 label_blocked_by: блокирана от
465 label_precedes: предшества
466 label_follows: изпълнява се след
467 label_end_to_start: end to start
468 label_end_to_end: end to end
469 label_start_to_start: start to start
470 label_start_to_end: start to end
471 label_stay_logged_in: Запомни ме
472 label_disabled: забранено
473 label_show_completed_versions: Показване на реализирани версии
474 label_me: аз
475 label_board: Форум
476 label_board_new: Нов форум
477 label_board_plural: Форуми
478 label_topic_plural: Теми
479 label_message_plural: Съобщения
480 label_message_last: Последно съобщение
481 label_message_new: Нова тема
482 label_reply_plural: Отговори
483 label_send_information: Изпращане на информацията до потребителя
484 label_year: Година
485 label_month: Месец
486 label_week: Седмица
487 label_date_from: От
488 label_date_to: До
489 label_language_based: В зависимост от езика
490 label_sort_by: "Сортиране по {{value}}"
491 label_send_test_email: Изпращане на тестов e-mail
492 label_feeds_access_key_created_on: "{{value}} от създаването на RSS ключа"
493 label_module_plural: Модули
494 label_added_time_by: "Публикувана от {{author}} преди {{age}}"
495 label_updated_time: "Обновена преди {{value}}"
496 label_jump_to_a_project: Проект...
497
498 button_login: Вход
499 button_submit: Прикачване
500 button_save: Запис
501 button_check_all: Избор на всички
502 button_uncheck_all: Изчистване на всички
503 button_delete: Изтриване
504 button_create: Създаване
505 button_test: Тест
506 button_edit: Редакция
507 button_add: Добавяне
508 button_change: Промяна
509 button_apply: Приложи
510 button_clear: Изчисти
511 button_lock: Заключване
512 button_unlock: Отключване
513 button_download: Download
514 button_list: Списък
515 button_view: Преглед
516 button_move: Преместване
517 button_back: Назад
518 button_cancel: Отказ
519 button_activate: Активация
520 button_sort: Сортиране
521 button_log_time: Отделяне на време
522 button_rollback: Върни се към тази ревизия
523 button_watch: Наблюдавай
524 button_unwatch: Спри наблюдението
525 button_reply: Отговор
526 button_archive: Архивиране
527 button_unarchive: Разархивиране
528 button_reset: Генериране наново
529 button_rename: Преименуване
530
531 status_active: активен
532 status_registered: регистриран
533 status_locked: заключен
534
535 text_select_mail_notifications: Изберете събития за изпращане на e-mail.
536 text_regexp_info: пр. ^[A-Z0-9]+$
537 text_min_max_length_info: 0 - без ограничения
538 text_project_destroy_confirmation: Сигурни ли сте, че искате да изтриете проекта и данните в него?
539 text_workflow_edit: Изберете роля и тракер за да редактирате работния процес
540 text_are_you_sure: Сигурни ли сте?
541 text_journal_changed: "промяна от {{old}} на {{new}}"
542 text_journal_set_to: "установено на {{value}}"
543 text_journal_deleted: изтрито
544 text_tip_task_begin_day: задача започваща този ден
545 text_tip_task_end_day: задача завършваща този ден
546 text_tip_task_begin_end_day: задача започваща и завършваща този ден
547 text_project_identifier_info: 'Позволени са малки букви (a-z), цифри и тирета.<br />Невъзможна промяна след запис.'
548 text_caracters_maximum: "До {{count}} символа."
549 text_length_between: "От {{min}} до {{max}} символа."
550 text_tracker_no_workflow: Няма дефиниран работен процес за този тракер
551 text_unallowed_characters: Непозволени символи
552 text_comma_separated: Позволено е изброяване (с разделител запетая).
553 text_issues_ref_in_commit_messages: Отбелязване и приключване на задачи от ревизии
554 text_issue_added: "Публикувана е нова задача с номер {{id}} (от {{author}})."
555 text_issue_updated: "Задача {{id}} е обновена (от {{author}})."
556 text_wiki_destroy_confirmation: Сигурни ли сте, че искате да изтриете това Wiki и цялото му съдържание?
557 text_issue_category_destroy_question: "Има задачи ({{count}}) обвързани с тази категория. Какво ще изберете?"
558 text_issue_category_destroy_assignments: Премахване на връзките с категорията
559 text_issue_category_reassign_to: Преобвързване с категория
560
561 default_role_manager: Мениджър
562 default_role_developper: Разработчик
563 default_role_reporter: Публикуващ
564 default_tracker_bug: Бъг
565 default_tracker_feature: Функционалност
566 default_tracker_support: Поддръжка
567 default_issue_status_new: Нова
568 default_issue_status_assigned: Възложена
569 default_issue_status_resolved: Приключена
570 default_issue_status_feedback: Обратна връзка
571 default_issue_status_closed: Затворена
572 default_issue_status_rejected: Отхвърлена
573 default_doc_category_user: Документация за потребителя
574 default_doc_category_tech: Техническа документация
575 default_priority_low: Нисък
576 default_priority_normal: Нормален
577 default_priority_high: Висок
578 default_priority_urgent: Спешен
579 default_priority_immediate: Веднага
580 default_activity_design: Дизайн
581 default_activity_development: Разработка
582
583 enumeration_issue_priorities: Приоритети на задачи
584 enumeration_doc_categories: Категории документи
585 enumeration_activities: Дейности (time tracking)
586 label_file_plural: Файлове
587 label_changeset_plural: Ревизии
588 field_column_names: Колони
589 label_default_columns: По подразбиране
590 setting_issue_list_default_columns: Показвани колони по подразбиране
591 setting_repositories_encodings: Кодови таблици
592 notice_no_issue_selected: "Няма избрани задачи."
593 label_bulk_edit_selected_issues: Редактиране на задачи
594 label_no_change_option: (Без промяна)
595 notice_failed_to_save_issues: "Неуспешен запис на {{count}} задачи от {{total}} избрани: {{ids}}."
596 label_theme: Тема
597 label_default: По подразбиране
598 label_search_titles_only: Само в заглавията
599 label_nobody: никой
600 button_change_password: Промяна на парола
601 text_user_mail_option: "За неизбраните проекти, ще получавате известия само за наблюдавани дейности или в които участвате (т.е. автор или назначени на мен)."
602 label_user_mail_option_selected: "За всички събития само в избраните проекти..."
603 label_user_mail_option_all: "За всяко събитие в проектите, в които участвам"
604 label_user_mail_option_none: "Само за наблюдавани или в които участвам (автор или назначени на мен)"
605 setting_emails_footer: Подтекст за e-mail
606 label_float: Дробно
607 button_copy: Копиране
608 mail_body_account_information_external: "Можете да използвате вашия {{value}} профил за вход."
609 mail_body_account_information: Информацията за профила ви
610 setting_protocol: Протокол
611 label_user_mail_no_self_notified: "Не искам известия за извършени от мен промени"
612 setting_time_format: Формат на часа
613 label_registration_activation_by_email: активиране на профила по email
614 mail_subject_account_activation_request: "Заявка за активиране на профил в {{value}}"
615 mail_body_account_activation_request: "Има новорегистриран потребител ({{value}}), очакващ вашето одобрение:'"
616 label_registration_automatic_activation: автоматично активиране
617 label_registration_manual_activation: ръчно активиране
618 notice_account_pending: "Профилът Ви е създаден и очаква одобрение от администратор."
619 field_time_zone: Часова зона
620 text_caracters_minimum: "Минимум {{count}} символа."
621 setting_bcc_recipients: Получатели на скрито копие (bcc)
622 button_annotate: Анотация
623 label_issues_by: "Задачи по {{value}}"
624 field_searchable: С възможност за търсене
625 label_display_per_page: "На страница по: {{value}}'"
626 setting_per_page_options: Опции за страниране
627 label_age: Възраст
628 notice_default_data_loaded: Примерната информацията е успешно заредена.
629 text_load_default_configuration: Зареждане на примерна информация
630 text_no_configuration_data: "Все още не са конфигурирани Роли, тракери, статуси на задачи и работен процес.\nСтрого се препоръчва зареждането на примерната информация. Веднъж заредена ще имате възможност да я редактирате."
631 error_can_t_load_default_data: "Грешка при зареждане на примерната информация: {{value}}"
632 button_update: Обновяване
633 label_change_properties: Промяна на настройки
634 label_general: Основни
635 label_repository_plural: Хранилища
636 label_associated_revisions: Асоциирани ревизии
637 setting_user_format: Потребителски формат
638 text_status_changed_by_changeset: "Приложено с ревизия {{value}}."
639 label_more: Още
640 text_issues_destroy_confirmation: 'Сигурни ли сте, че искате да изтриете избраните задачи?'
641 label_scm: SCM (Система за контрол на кода)
642 text_select_project_modules: 'Изберете активните модули за този проект:'
643 label_issue_added: Добавена задача
644 label_issue_updated: Обновена задача
645 label_document_added: Добавен документ
646 label_message_posted: Добавено съобщение
647 label_file_added: Добавен файл
648 label_news_added: Добавена новина
649 project_module_boards: Форуми
650 project_module_issue_tracking: Тракинг
651 project_module_wiki: Wiki
652 project_module_files: Файлове
653 project_module_documents: Документи
654 project_module_repository: Хранилище
655 project_module_news: Новини
656 project_module_time_tracking: Отделяне на време
657 text_file_repository_writable: Възможност за писане в хранилището с файлове
658 text_default_administrator_account_changed: Сменен фабричния администраторски профил
659 text_rmagick_available: Наличен RMagick (по избор)
660 button_configure: Конфигуриране
661 label_plugins: Плъгини
662 label_ldap_authentication: LDAP оторизация
663 label_downloads_abbr: D/L
664 label_this_month: текущия месец
665 label_last_n_days: "последните {{count}} дни"
666 label_all_time: всички
667 label_this_year: текущата година
668 label_date_range: Период
669 label_last_week: последната седмица
670 label_yesterday: вчера
671 label_last_month: последния месец
672 label_add_another_file: Добавяне на друг файл
673 label_optional_description: Незадължително описание
674 text_destroy_time_entries_question: %.02f часа са отделени на задачите, които искате да изтриете. Какво избирате?
675 error_issue_not_found_in_project: 'Задачата не е намерена или не принадлежи на този проект'
676 text_assign_time_entries_to_project: Прехвърляне на отделеното време към проект
677 text_destroy_time_entries: Изтриване на отделеното време
678 text_reassign_time_entries: 'Прехвърляне на отделеното време към задача:'
679 setting_activity_days_default: Брой дни показвани на таб Дейност
680 label_chronological_order: Хронологичен ред
681 field_comments_sorting: Сортиране на коментарите
682 label_reverse_chronological_order: Обратен хронологичен ред
683 label_preferences: Предпочитания
684 setting_display_subprojects_issues: Показване на подпроектите в проектите по подразбиране
685 label_overall_activity: Цялостна дейност
686 setting_default_projects_public: Новите проекти са публични по подразбиране
687 error_scm_annotate: "Обектът не съществува или не може да бъде анотиран."
688 label_planning: Планиране
689 text_subprojects_destroy_warning: "Its subproject(s): {{value}} will be also deleted.'"
690 label_and_its_subprojects: "{{value}} and its subprojects"
691 mail_body_reminder: "{{count}} issue(s) that are assigned to you are due in the next {{days}} days:"
692 mail_subject_reminder: "{{count}} issue(s) due in the next days"
693 text_user_wrote: "{{value}} wrote:'"
694 label_duplicated_by: duplicated by
695 setting_enabled_scm: Enabled SCM
696 text_enumeration_category_reassign_to: 'Reassign them to this value:'
697 text_enumeration_destroy_question: "{{count}} objects are assigned to this value.'"
698 label_incoming_emails: Incoming emails
699 label_generate_key: Generate a key
700 setting_mail_handler_api_enabled: Enable WS for incoming emails
701 setting_mail_handler_api_key: API key
702 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
703 field_parent_title: Parent page
704 label_issue_watchers: Watchers
705 setting_commit_logs_encoding: Commit messages encoding
706 button_quote: Quote
707 setting_sequential_project_identifiers: Generate sequential project identifiers
708 notice_unable_delete_version: Unable to delete version
709 label_renamed: renamed
710 label_copied: copied
711 setting_plain_text_mail: plain text only (no HTML)
712 permission_view_files: View files
713 permission_edit_issues: Edit issues
714 permission_edit_own_time_entries: Edit own time logs
715 permission_manage_public_queries: Manage public queries
716 permission_add_issues: Add issues
717 permission_log_time: Log spent time
718 permission_view_changesets: View changesets
719 permission_view_time_entries: View spent time
720 permission_manage_versions: Manage versions
721 permission_manage_wiki: Manage wiki
722 permission_manage_categories: Manage issue categories
723 permission_protect_wiki_pages: Protect wiki pages
724 permission_comment_news: Comment news
725 permission_delete_messages: Delete messages
726 permission_select_project_modules: Select project modules
727 permission_manage_documents: Manage documents
728 permission_edit_wiki_pages: Edit wiki pages
729 permission_add_issue_watchers: Add watchers
730 permission_view_gantt: View gantt chart
731 permission_move_issues: Move issues
732 permission_manage_issue_relations: Manage issue relations
733 permission_delete_wiki_pages: Delete wiki pages
734 permission_manage_boards: Manage boards
735 permission_delete_wiki_pages_attachments: Delete attachments
736 permission_view_wiki_edits: View wiki history
737 permission_add_messages: Post messages
738 permission_view_messages: View messages
739 permission_manage_files: Manage files
740 permission_edit_issue_notes: Edit notes
741 permission_manage_news: Manage news
742 permission_view_calendar: View calendrier
743 permission_manage_members: Manage members
744 permission_edit_messages: Edit messages
745 permission_delete_issues: Delete issues
746 permission_view_issue_watchers: View watchers list
747 permission_manage_repository: Manage repository
748 permission_commit_access: Commit access
749 permission_browse_repository: Browse repository
750 permission_view_documents: View documents
751 permission_edit_project: Edit project
752 permission_add_issue_notes: Add notes
753 permission_save_queries: Save queries
754 permission_view_wiki_pages: View wiki
755 permission_rename_wiki_pages: Rename wiki pages
756 permission_edit_time_entries: Edit time logs
757 permission_edit_own_issue_notes: Edit own notes
758 setting_gravatar_enabled: Use Gravatar user icons
759 label_example: Example
760 text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
761 permission_edit_own_messages: Edit own messages
762 permission_delete_own_messages: Delete own messages
763 label_user_activity: "{{value}}'s activity"
764 label_updated_time_by: "Updated by {{author}} {{age}} ago"
765 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
766 setting_diff_max_lines_displayed: Max number of diff lines displayed
767 text_plugin_assets_writable: Plugin assets directory writable
768 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
769 button_create_and_continue: Create and continue
770 text_custom_field_possible_values_info: 'One line for each value'
771 label_display: Display
772 field_editable: Editable
773 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
This diff has been collapsed as it changes many lines, (1480 lines changed) Show them Hide them
@@ -1,710 +1,774
1 _gloc_rule_default: '|n| n==1 ? "" : "_plural" '
1 ca:
2 date:
3 formats:
4 # Use the strftime parameters for formats.
5 # When no format has been given, it uses default.
6 # You can provide other formats here if you like!
7 default: "%Y-%m-%d"
8 short: "%b %d"
9 long: "%B %d, %Y"
10
11 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
12 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
13
14 # Don't forget the nil at the beginning; there's no such thing as a 0th month
15 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
16 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
17 # Used in date_select and datime_select.
18 order: [ :year, :month, :day ]
2 19
3 actionview_datehelper_select_day_prefix:
4 actionview_datehelper_select_month_names: Gener,Febrer,Març,Abril,Maig,Juny,Juliol,Agost,Setembre,Octubre,Novembre,Desembre
5 actionview_datehelper_select_month_names_abbr: Gen,Feb,Mar,Abr,Mai,Jun,Jul,Ago,Set,Oct,Nov,Dec
6 actionview_datehelper_select_month_prefix:
7 actionview_datehelper_select_year_prefix:
8 actionview_datehelper_time_in_words_day: 1 dia
9 actionview_datehelper_time_in_words_day_plural: %d dies
10 actionview_datehelper_time_in_words_hour_about: aproximadament una hora
11 actionview_datehelper_time_in_words_hour_about_plural: aproximadament %d hores
12 actionview_datehelper_time_in_words_hour_about_single: aproximadament una hora
13 actionview_datehelper_time_in_words_minute: 1 minut
14 actionview_datehelper_time_in_words_minute_half: mig minut
15 actionview_datehelper_time_in_words_minute_less_than: "menys d'un minut"
16 actionview_datehelper_time_in_words_minute_plural: %d minuts
17 actionview_datehelper_time_in_words_minute_single: 1 minut
18 actionview_datehelper_time_in_words_second_less_than: "menys d'un segon"
19 actionview_datehelper_time_in_words_second_less_than_plural: menys de %d segons
20 actionview_instancetag_blank_option: Seleccioneu
20 time:
21 formats:
22 default: "%a, %d %b %Y %H:%M:%S %z"
23 short: "%d %b %H:%M"
24 long: "%B %d, %Y %H:%M"
25 am: "am"
26 pm: "pm"
27
28 datetime:
29 distance_in_words:
30 half_a_minute: "half a minute"
31 less_than_x_seconds:
32 one: "less than 1 second"
33 other: "less than {{count}} seconds"
34 x_seconds:
35 one: "1 second"
36 other: "{{count}} seconds"
37 less_than_x_minutes:
38 one: "less than a minute"
39 other: "less than {{count}} minutes"
40 x_minutes:
41 one: "1 minute"
42 other: "{{count}} minutes"
43 about_x_hours:
44 one: "about 1 hour"
45 other: "about {{count}} hours"
46 x_days:
47 one: "1 day"
48 other: "{{count}} days"
49 about_x_months:
50 one: "about 1 month"
51 other: "about {{count}} months"
52 x_months:
53 one: "1 month"
54 other: "{{count}} months"
55 about_x_years:
56 one: "about 1 year"
57 other: "about {{count}} years"
58 over_x_years:
59 one: "over 1 year"
60 other: "over {{count}} years"
61
62 # Used in array.to_sentence.
63 support:
64 array:
65 sentence_connector: "and"
66 skip_last_comma: false
67
68 activerecord:
69 errors:
70 messages:
71 inclusion: "no està inclòs a la llista"
72 exclusion: "està reservat"
73 invalid: "no és vàlid"
74 confirmation: "la confirmació no coincideix"
75 accepted: "s'ha d'acceptar"
76 empty: "no pot estar buit"
77 blank: "no pot estar en blanc"
78 too_long: "és massa llarg"
79 too_short: "és massa curt"
80 wrong_length: "la longitud és incorrecta"
81 taken: "ja s'està utilitzant"
82 not_a_number: "no és un número"
83 not_a_date: "no és una data vàlida"
84 greater_than: "must be greater than {{count}}"
85 greater_than_or_equal_to: "must be greater than or equal to {{count}}"
86 equal_to: "must be equal to {{count}}"
87 less_than: "must be less than {{count}}"
88 less_than_or_equal_to: "must be less than or equal to {{count}}"
89 odd: "must be odd"
90 even: "must be even"
91 greater_than_start_date: "ha de ser superior que la data inicial"
92 not_same_project: "no pertany al mateix projecte"
93 circular_dependency: "Aquesta relació crearia una dependència circular"
21 94
22 activerecord_error_inclusion: no està inclòs a la llista
23 activerecord_error_exclusion: està reservat
24 activerecord_error_invalid: no és vàlid
25 activerecord_error_confirmation: la confirmació no coincideix
26 activerecord_error_accepted: "s'ha d'acceptar"
27 activerecord_error_empty: no pot estar buit
28 activerecord_error_blank: no pot estar en blanc
29 activerecord_error_too_long: és massa llarg
30 activerecord_error_too_short: és massa curt
31 activerecord_error_wrong_length: la longitud és incorrecta
32 activerecord_error_taken: "ja s'està utilitzant"
33 activerecord_error_not_a_number: no és un número
34 activerecord_error_not_a_date: no és una data vàlida
35 activerecord_error_greater_than_start_date: ha de ser superior que la data inicial
36 activerecord_error_not_same_project: no pertany al mateix projecte
37 activerecord_error_circular_dependency: Aquesta relació crearia una dependència circular
38
39 general_fmt_age: %d any
40 general_fmt_age_plural: %d anys
41 general_fmt_date: %%d/%%m/%%Y
42 general_fmt_datetime: %%d/%%m/%%Y %%H:%%M
43 general_fmt_datetime_short: %%d/%%m %%H:%%M
44 general_fmt_time: %%H:%%M
45 general_text_No: 'No'
46 general_text_Yes: 'Si'
47 general_text_no: 'no'
48 general_text_yes: 'si'
49 general_lang_name: 'Català'
50 general_csv_separator: ';'
51 general_csv_decimal_separator: ','
52 general_csv_encoding: ISO-8859-15
53 general_pdf_encoding: ISO-8859-15
54 general_day_names: Dilluns,Dimarts,Dimecres,Dijous,Divendres,Dissabte,Diumenge
55 general_first_day_of_week: '1'
56
57 notice_account_updated: "El compte s'ha actualitzat correctament."
58 notice_account_invalid_creditentials: Usuari o contrasenya invàlid
59 notice_account_password_updated: "La contrasenya s'ha modificat correctament."
60 notice_account_wrong_password: Contrasenya incorrecta
61 notice_account_register_done: "El compte s'ha creat correctament. Per a activar el compte, feu clic en l'enllaç que us han enviat per correu electrònic."
62 notice_account_unknown_email: Usuari desconegut.
63 notice_can_t_change_password: "Aquest compte utilitza una font d'autenticació externa. No és possible canviar la contrasenya."
64 notice_account_lost_email_sent: "S'ha enviat un correu electrònic amb instruccions per a seleccionar una contrasenya nova."
65 notice_account_activated: "El compte s'ha activat. Ara podeu entrar."
66 notice_successful_create: "S'ha creat correctament."
67 notice_successful_update: "S'ha modificat correctament."
68 notice_successful_delete: "S'ha suprimit correctament."
69 notice_successful_connection: "S'ha connectat correctament."
70 notice_file_not_found: "La pàgina a la que intenteu accedir no existeix o s'ha suprimit."
71 notice_locking_conflict: Un altre usuari ha actualitzat les dades.
72 notice_not_authorized: No teniu permís per a accedir a aquesta pàgina.
73 notice_email_sent: "S'ha enviat un correu electrònic a %s"
74 notice_email_error: "S'ha produït un error en enviar el correu (%s)"
75 notice_feeds_access_key_reseted: "S'ha reiniciat la clau d'accés del RSS."
76 notice_failed_to_save_issues: "No s'han pogut desar %s assumptes de %d seleccionats: %s."
77 notice_no_issue_selected: "No s'ha seleccionat cap assumpte. Activeu els assumptes que voleu editar."
78 notice_account_pending: "S'ha creat el compte i ara està pendent de l'aprovació de l'administrador."
79 notice_default_data_loaded: "S'ha carregat correctament la configuració predeterminada."
80 notice_unable_delete_version: "No s'ha pogut suprimir la versió."
81
82 error_can_t_load_default_data: "No s'ha pogut carregar la configuració predeterminada: %s"
83 error_scm_not_found: "No s'ha trobat l'entrada o la revisió en el dipòsit."
84 error_scm_command_failed: "S'ha produït un error en intentar accedir al dipòsit: %s"
85 error_scm_annotate: "L'entrada no existeix o no s'ha pogut anotar."
86 error_issue_not_found_in_project: "No s'ha trobat l'assumpte o no pertany a aquest projecte"
87
88 mail_subject_lost_password: Contrasenya de %s
89 mail_body_lost_password: "Per a canviar la contrasenya, feu clic en l'enllaç següent:"
90 mail_subject_register: Activació del compte de %s
91 mail_body_register: "Per a activar el compte, feu clic en l'enllaç següent:"
92 mail_body_account_information_external: Podeu utilitzar el compte «%s» per a entrar.
93 mail_body_account_information: Informació del compte
94 mail_subject_account_activation_request: "Sol·licitud d'activació del compte de %s"
95 mail_body_account_activation_request: "S'ha registrat un usuari nou (%s). El seu compte està pendent d'aprovació:"
96 mail_subject_reminder: "%d assumptes venceran els següents %d dies"
97 mail_body_reminder: "%d assumptes que teniu assignades venceran els següents %d dies:"
98
99 gui_validation_error: 1 error
100 gui_validation_error_plural: %d errors
101
102 field_name: Nom
103 field_description: Descripció
104 field_summary: Resum
105 field_is_required: Necessari
106 field_firstname: Nom
107 field_lastname: Cognom
108 field_mail: Correu electrònic
109 field_filename: Fitxer
110 field_filesize: Mida
111 field_downloads: Baixades
112 field_author: Autor
113 field_created_on: Creat
114 field_updated_on: Actualitzat
115 field_field_format: Format
116 field_is_for_all: Per a tots els projectes
117 field_possible_values: Valores possibles
118 field_regexp: Expressió regular
119 field_min_length: Longitud mínima
120 field_max_length: Longitud màxima
121 field_value: Valor
122 field_category: Categoria
123 field_title: Títol
124 field_project: Projecte
125 field_issue: Assumpte
126 field_status: Estat
127 field_notes: Notes
128 field_is_closed: Assumpte tancat
129 field_is_default: Estat predeterminat
130 field_tracker: Seguidor
131 field_subject: Tema
132 field_due_date: Data de venciment
133 field_assigned_to: Assignat a
134 field_priority: Prioritat
135 field_fixed_version: Versió objectiu
136 field_user: Usuari
137 field_role: Rol
138 field_homepage: Pàgina web
139 field_is_public: Públic
140 field_parent: Subprojecte de
141 field_is_in_chlog: Assumptes mostrats en el registre de canvis
142 field_is_in_roadmap: Assumptes mostrats en la planificació
143 field_login: Entrada
144 field_mail_notification: Notificacions per correu electrònic
145 field_admin: Administrador
146 field_last_login_on: Última connexió
147 field_language: Idioma
148 field_effective_date: Data
149 field_password: Contrasenya
150 field_new_password: Contrasenya nova
151 field_password_confirmation: Confirmació
152 field_version: Versió
153 field_type: Tipus
154 field_host: Ordinador
155 field_port: Port
156 field_account: Compte
157 field_base_dn: Base DN
158 field_attr_login: "Atribut d'entrada"
159 field_attr_firstname: Atribut del nom
160 field_attr_lastname: Atribut del cognom
161 field_attr_mail: Atribut del correu electrònic
162 field_onthefly: "Creació de l'usuari «al vol»"
163 field_start_date: Inici
164 field_done_ratio: %% realitzat
165 field_auth_source: "Mode d'autenticació"
166 field_hide_mail: "Oculta l'adreça de correu electrònic"
167 field_comments: Comentari
168 field_url: URL
169 field_start_page: Pàgina inicial
170 field_subproject: Subprojecte
171 field_hours: Hores
172 field_activity: Activitat
173 field_spent_on: Data
174 field_identifier: Identificador
175 field_is_filter: "S'ha utilitzat com a filtre"
176 field_issue_to_id: Assumpte relacionat
177 field_delay: Retard
178 field_assignable: Es poden assignar assumptes a aquest rol
179 field_redirect_existing_links: Redirigeix els enllaços existents
180 field_estimated_hours: Temps previst
181 field_column_names: Columnes
182 field_time_zone: Zona horària
183 field_searchable: Es pot cercar
184 field_default_value: Valor predeterminat
185 field_comments_sorting: Mostra els comentaris
186 field_parent_title: Pàgina pare
187
188 setting_app_title: "Títol de l'aplicació"
189 setting_app_subtitle: "Subtítol de l'aplicació"
190 setting_welcome_text: Text de benvinguda
191 setting_default_language: Idioma predeterminat
192 setting_login_required: Es necessita autenticació
193 setting_self_registration: Registre automàtic
194 setting_attachment_max_size: Mida màxima dels adjunts
195 setting_issues_export_limit: "Límit d'exportació d'assumptes"
196 setting_mail_from: "Adreça de correu electrònic d'emissió"
197 setting_bcc_recipients: Vincula els destinataris de les còpies amb carbó (bcc)
198 setting_host_name: "Nom de l'ordinador"
199 setting_text_formatting: Format del text
200 setting_wiki_compression: "Comprimeix l'historial del wiki"
201 setting_feeds_limit: Límit de contingut del canal
202 setting_default_projects_public: Els projectes nous són públics per defecte
203 setting_autofetch_changesets: Omple automàticament les publicacions
204 setting_sys_api_enabled: Habilita el WS per a la gestió del dipòsit
205 setting_commit_ref_keywords: Paraules claus per a la referència
206 setting_commit_fix_keywords: Paraules claus per a la correcció
207 setting_autologin: Entrada automàtica
208 setting_date_format: Format de la data
209 setting_time_format: Format de hora
210 setting_cross_project_issue_relations: "Permet les relacions d'assumptes entre projectes"
211 setting_issue_list_default_columns: "Columnes mostrades per defecte en la llista d'assumptes"
212 setting_repositories_encodings: Codificacions del dipòsit
213 setting_commit_logs_encoding: Codificació dels missatges publicats
214 setting_emails_footer: Peu dels correus electrònics
215 setting_protocol: Protocol
216 setting_per_page_options: Opcions dels objectes per pàgina
217 setting_user_format: "Format de com mostrar l'usuari"
218 setting_activity_days_default: "Dies a mostrar l'activitat del projecte"
219 setting_display_subprojects_issues: "Mostra els assumptes d'un subprojecte en el projecte pare per defecte"
220 setting_enabled_scm: "Habilita l'SCM"
221 setting_mail_handler_api_enabled: "Habilita el WS per correus electrònics d'entrada"
222 setting_mail_handler_api_key: Clau API
223 setting_sequential_project_identifiers: Genera identificadors de projecte seqüencials
224
225 project_module_issue_tracking: "Seguidor d'assumptes"
226 project_module_time_tracking: Seguidor de temps
227 project_module_news: Noticies
228 project_module_documents: Documents
229 project_module_files: Fitxers
230 project_module_wiki: Wiki
231 project_module_repository: Dipòsit
232 project_module_boards: Taulers
233
234 label_user: Usuari
235 label_user_plural: Usuaris
236 label_user_new: Usuari nou
237 label_project: Projecte
238 label_project_new: Projecte nou
239 label_project_plural: Projectes
240 label_project_all: Tots els projectes
241 label_project_latest: Els últims projectes
242 label_issue: Assumpte
243 label_issue_new: Assumpte nou
244 label_issue_plural: Assumptes
245 label_issue_view_all: Visualitza tots els assumptes
246 label_issues_by: Assumptes per %s
247 label_issue_added: Assumpte afegit
248 label_issue_updated: Assumpte actualitzat
249 label_document: Document
250 label_document_new: Document nou
251 label_document_plural: Documents
252 label_document_added: Document afegit
253 label_role: Rol
254 label_role_plural: Rols
255 label_role_new: Rol nou
256 label_role_and_permissions: Rols i permisos
257 label_member: Membre
258 label_member_new: Membre nou
259 label_member_plural: Membres
260 label_tracker: Seguidor
261 label_tracker_plural: Seguidors
262 label_tracker_new: Seguidor nou
263 label_workflow: Flux de treball
264 label_issue_status: "Estat de l'assumpte"
265 label_issue_status_plural: "Estats de l'assumpte"
266 label_issue_status_new: Estat nou
267 label_issue_category: "Categoria de l'assumpte"
268 label_issue_category_plural: "Categories de l'assumpte"
269 label_issue_category_new: Categoria nova
270 label_custom_field: Camp personalitzat
271 label_custom_field_plural: Camps personalitzats
272 label_custom_field_new: Camp personalitzat nou
273 label_enumerations: Enumeracions
274 label_enumeration_new: Valor nou
275 label_information: Informació
276 label_information_plural: Informació
277 label_please_login: Entreu
278 label_register: Registre
279 label_password_lost: Contrasenya perduda
280 label_home: Inici
281 label_my_page: La meva pàgina
282 label_my_account: El meu compte
283 label_my_projects: Els meus projectes
284 label_administration: Administració
285 label_login: Entra
286 label_logout: Surt
287 label_help: Ajuda
288 label_reported_issues: Assumptes informats
289 label_assigned_to_me_issues: Assumptes assignats a mi
290 label_last_login: Última connexió
291 label_last_updates: Última actualització
292 label_last_updates_plural: %d última actualització
293 label_registered_on: Informat el
294 label_activity: Activitat
295 label_overall_activity: Activitat global
296 label_new: Nou
297 label_logged_as: Heu entrat com a
298 label_environment: Entorn
299 label_authentication: Autenticació
300 label_auth_source: "Mode d'autenticació"
301 label_auth_source_new: "Mode d'autenticació nou"
302 label_auth_source_plural: "Modes d'autenticació"
303 label_subproject_plural: Subprojectes
304 label_and_its_subprojects: %s i els seus subprojectes
305 label_min_max_length: Longitud mín - max
306 label_list: Llist
307 label_date: Data
308 label_integer: Enter
309 label_float: Flotant
310 label_boolean: Booleà
311 label_string: Text
312 label_text: Text llarg
313 label_attribute: Atribut
314 label_attribute_plural: Atributs
315 label_download: %d baixada
316 label_download_plural: %d baixades
317 label_no_data: Sense dades a mostrar
318 label_change_status: "Canvia l'estat"
319 label_history: Historial
320 label_attachment: Fitxer
321 label_attachment_new: Fitxer nou
322 label_attachment_delete: Suprimeix el fitxer
323 label_attachment_plural: Fitxers
324 label_file_added: Fitxer afegit
325 label_report: Informe
326 label_report_plural: Informes
327 label_news: Noticies
328 label_news_new: Afegeix noticies
329 label_news_plural: Noticies
330 label_news_latest: Últimes noticies
331 label_news_view_all: Visualitza totes les noticies
332 label_news_added: Noticies afegides
333 label_change_log: Registre de canvis
334 label_settings: Paràmetres
335 label_overview: Resum
336 label_version: Versió
337 label_version_new: Versió nova
338 label_version_plural: Versions
339 label_confirmation: Confirmació
340 label_export_to: 'També disponible a:'
341 label_read: Llegeix...
342 label_public_projects: Projectes públics
343 label_open_issues: obert
344 label_open_issues_plural: oberts
345 label_closed_issues: tancat
346 label_closed_issues_plural: tancats
347 label_total: Total
348 label_permissions: Permisos
349 label_current_status: Estat actual
350 label_new_statuses_allowed: Nous estats autoritzats
351 label_all: tots
352 label_none: cap
353 label_nobody: ningú
354 label_next: Següent
355 label_previous: Anterior
356 label_used_by: Utilitzat per
357 label_details: Detalls
358 label_add_note: Afegeix una nota
359 label_per_page: Per pàgina
360 label_calendar: Calendari
361 label_months_from: mesos des de
362 label_gantt: Gantt
363 label_internal: Intern
364 label_last_changes: últims %d canvis
365 label_change_view_all: Visualitza tots els canvis
366 label_personalize_page: Personalitza aquesta pàgina
367 label_comment: Comentari
368 label_comment_plural: Comentaris
369 label_comment_add: Afegeix un comentari
370 label_comment_added: Comentari afegit
371 label_comment_delete: Suprimeix comentaris
372 label_query: Consulta personalitzada
373 label_query_plural: Consultes personalitzades
374 label_query_new: Consulta nova
375 label_filter_add: Afegeix un filtre
376 label_filter_plural: Filtres
377 label_equals: és
378 label_not_equals: no és
379 label_in_less_than: en menys de
380 label_in_more_than: en més de
381 label_in: en
382 label_today: avui
383 label_all_time: tot el temps
384 label_yesterday: ahir
385 label_this_week: aquesta setmana
386 label_last_week: "l'última setmana"
387 label_last_n_days: els últims %d dies
388 label_this_month: aquest més
389 label_last_month: "l'últim més"
390 label_this_year: aquest any
391 label_date_range: Abast de les dates
392 label_less_than_ago: fa menys de
393 label_more_than_ago: fa més de
394 label_ago: fa
395 label_contains: conté
396 label_not_contains: no conté
397 label_day_plural: dies
398 label_repository: Dipòsit
399 label_repository_plural: Dipòsits
400 label_browse: Navega
401 label_modification: %d canvi
402 label_modification_plural: %d canvis
403 label_revision: Revisió
404 label_revision_plural: Revisions
405 label_associated_revisions: Revisions associades
406 label_added: afegit
407 label_modified: modificat
408 label_renamed: reanomenat
409 label_copied: copiat
410 label_deleted: suprimit
411 label_latest_revision: Última revisió
412 label_latest_revision_plural: Últimes revisions
413 label_view_revisions: Visualitza les revisions
414 label_max_size: Mida màxima
415 label_on: 'de'
416 label_sort_highest: Mou a la part superior
417 label_sort_higher: Mou cap amunt
418 label_sort_lower: Mou cap avall
419 label_sort_lowest: Mou a la part inferior
420 label_roadmap: Planificació
421 label_roadmap_due_in: Venç en %s
422 label_roadmap_overdue: %s tard
423 label_roadmap_no_issues: No hi ha assumptes per a aquesta versió
424 label_search: Cerca
425 label_result_plural: Resultats
426 label_all_words: Totes les paraules
427 label_wiki: Wiki
428 label_wiki_edit: Edició wiki
429 label_wiki_edit_plural: Edicions wiki
430 label_wiki_page: Pàgina wiki
431 label_wiki_page_plural: Pàgines wiki
432 label_index_by_title: Índex per títol
433 label_index_by_date: Índex per data
434 label_current_version: Versió actual
435 label_preview: Previsualització
436 label_feed_plural: Canals
437 label_changes_details: Detalls de tots els canvis
438 label_issue_tracking: "Seguiment d'assumptes"
439 label_spent_time: Temps invertit
440 label_f_hour: %.2f hora
441 label_f_hour_plural: %.2f hores
442 label_time_tracking: Temps de seguiment
443 label_change_plural: Canvis
444 label_statistics: Estadístiques
445 label_commits_per_month: Publicacions per mes
446 label_commits_per_author: Publicacions per autor
447 label_view_diff: Visualitza les diferències
448 label_diff_inline: en línia
449 label_diff_side_by_side: costat per costat
450 label_options: Opcions
451 label_copy_workflow_from: Copia el flux de treball des de
452 label_permissions_report: Informe de permisos
453 label_watched_issues: Assumptes vigilats
454 label_related_issues: Assumptes relacionats
455 label_applied_status: Estat aplicat
456 label_loading: "S'està carregant..."
457 label_relation_new: Relació nova
458 label_relation_delete: Suprimeix la relació
459 label_relates_to: relacionat amb
460 label_duplicates: duplicats
461 label_duplicated_by: duplicat per
462 label_blocks: bloqueja
463 label_blocked_by: bloquejats per
464 label_precedes: anterior a
465 label_follows: posterior a
466 label_end_to_start: final al començament
467 label_end_to_end: final al final
468 label_start_to_start: començament al començament
469 label_start_to_end: començament al final
470 label_stay_logged_in: "Manté l'entrada"
471 label_disabled: inhabilitat
472 label_show_completed_versions: Mostra les versions completes
473 label_me: jo mateix
474 label_board: Fòrum
475 label_board_new: Fòrum nou
476 label_board_plural: Fòrums
477 label_topic_plural: Temes
478 label_message_plural: Missatges
479 label_message_last: Últim missatge
480 label_message_new: Missatge nou
481 label_message_posted: Missatge afegit
482 label_reply_plural: Respostes
483 label_send_information: "Envia la informació del compte a l'usuari"
484 label_year: Any
485 label_month: Mes
486 label_week: Setmana
487 label_date_from: Des de
488 label_date_to: A
489 label_language_based: "Basat en l'idioma de l'usuari"
490 label_sort_by: Ordena per %s
491 label_send_test_email: Envia un correu electrònic de prova
492 label_feeds_access_key_created_on: "Clau d'accés del RSS creada fa %s"
493 label_module_plural: Mòduls
494 label_added_time_by: Afegit per %s fa %s
495 label_updated_time: Actualitzat fa %s
496 label_jump_to_a_project: Salta al projecte...
497 label_file_plural: Fitxers
498 label_changeset_plural: Conjunt de canvis
499 label_default_columns: Columnes predeterminades
500 label_no_change_option: (sense canvis)
501 label_bulk_edit_selected_issues: Edita en bloc els assumptes seleccionats
502 label_theme: Tema
503 label_default: Predeterminat
504 label_search_titles_only: Cerca només en els títols
505 label_user_mail_option_all: "Per qualsevol esdeveniment en tots els meus projectes"
506 label_user_mail_option_selected: "Per qualsevol esdeveniment en els projectes seleccionats..."
507 label_user_mail_option_none: "Només per les coses que vigilo o hi estic implicat"
508 label_user_mail_no_self_notified: "No vull ser notificat pels canvis que faig jo mateix"
509 label_registration_activation_by_email: activació del compte per correu electrònic
510 label_registration_manual_activation: activació del compte manual
511 label_registration_automatic_activation: activació del compte automàtica
512 label_display_per_page: 'Per pàgina: %s'
513 label_age: Edat
514 label_change_properties: Canvia les propietats
515 label_general: General
516 label_more: Més
517 label_scm: SCM
518 label_plugins: Connectors
519 label_ldap_authentication: Autenticació LDAP
520 label_downloads_abbr: Baixades
521 label_optional_description: Descripció opcional
522 label_add_another_file: Afegeix un altre fitxer
523 label_preferences: Preferències
524 label_chronological_order: En ordre cronològic
525 label_reverse_chronological_order: En ordre cronològic invers
526 label_planning: Planificació
527 label_incoming_emails: "Correu electrònics d'entrada"
528 label_generate_key: Genera una clau
529 label_issue_watchers: Vigilants
530
531 button_login: Entra
532 button_submit: Tramet
533 button_save: Desa
534 button_check_all: Activa-ho tot
535 button_uncheck_all: Desactiva-ho tot
536 button_delete: Suprimeix
537 button_create: Crea
538 button_test: Test
539 button_edit: Edit
540 button_add: Afegeix
541 button_change: Canvia
542 button_apply: Aplica
543 button_clear: Neteja
544 button_lock: Bloca
545 button_unlock: Desbloca
546 button_download: Baixa
547 button_list: Llista
548 button_view: Visualitza
549 button_move: Mou
550 button_back: Enrere
551 button_cancel: Cancel·la
552 button_activate: Activa
553 button_sort: Ordena
554 button_log_time: "Hora d'entrada"
555 button_rollback: Torna a aquesta versió
556 button_watch: Vigila
557 button_unwatch: No vigilis
558 button_reply: Resposta
559 button_archive: Arxiva
560 button_unarchive: Desarxiva
561 button_reset: Reinicia
562 button_rename: Reanomena
563 button_change_password: Canvia la contrasenya
564 button_copy: Copia
565 button_annotate: Anota
566 button_update: Actualitza
567 button_configure: Configura
568 button_quote: Cita
569
570 status_active: actiu
571 status_registered: informat
572 status_locked: bloquejat
573
574 text_select_mail_notifications: "Seleccioneu les accions per les quals s'hauria d'enviar una notificació per correu electrònic."
575 text_regexp_info: ex. ^[A-Z0-9]+$
576 text_min_max_length_info: 0 significa sense restricció
577 text_project_destroy_confirmation: Segur que voleu suprimir aquest projecte i les dades relacionades?
578 text_subprojects_destroy_warning: "També seran suprimits els seus subprojectes: %s."
579 text_workflow_edit: Seleccioneu un rol i un seguidor per a editar el flux de treball
580 text_are_you_sure: Segur?
581 text_journal_changed: canviat des de %s a %s
582 text_journal_set_to: establert a %s
583 text_journal_deleted: suprimit
584 text_tip_task_begin_day: "tasca que s'inicia aquest dia"
585 text_tip_task_end_day: tasca que finalitza aquest dia
586 text_tip_task_begin_end_day: "tasca que s'inicia i finalitza aquest dia"
587 text_project_identifier_info: "Es permeten lletres en minúscules (a-z), números i guions.<br />Un cop desat, l'identificador no es pot modificar."
588 text_caracters_maximum: %d caràcters com a màxim.
589 text_caracters_minimum: Com a mínim ha de tenir %d caràcters.
590 text_length_between: Longitud entre %d i %d caràcters.
591 text_tracker_no_workflow: "No s'ha definit cap flux de treball per a aquest seguidor"
592 text_unallowed_characters: Caràcters no permesos
593 text_comma_separated: Es permeten valors múltiples (separats per una coma).
594 text_issues_ref_in_commit_messages: Referència i soluciona els assumptes en els missatges publicats
595 text_issue_added: "L'assumpte %s ha sigut informat per %s."
596 text_issue_updated: "L'assumpte %s ha sigut actualitzat per %s."
597 text_wiki_destroy_confirmation: Segur que voleu suprimir aquest wiki i tots els seus continguts?
598 text_issue_category_destroy_question: Alguns assumptes (%d) estan assignats a aquesta categoria. Què voleu fer?
599 text_issue_category_destroy_assignments: Suprimeix les assignacions de la categoria
600 text_issue_category_reassign_to: Torna a assignar els assumptes a aquesta categoria
601 text_user_mail_option: "Per als projectes no seleccionats, només rebreu notificacions sobre les coses que vigileu o que hi esteu implicat (ex. assumptes que en sou l'autor o hi esteu assignat)."
602 text_no_configuration_data: "Encara no s'han configurat els rols, seguidors, estats de l'assumpte i flux de treball.\nÉs altament recomanable que carregueu la configuració predeterminada. Podreu modificar-la un cop carregada."
603 text_load_default_configuration: Carrega la configuració predeterminada
604 text_status_changed_by_changeset: Aplicat en el conjunt de canvis %s.
605 text_issues_destroy_confirmation: "Segur que voleu suprimir els assumptes seleccionats?"
606 text_select_project_modules: "Seleccioneu els mòduls a habilitar per a aquest projecte:"
607 text_default_administrator_account_changed: "S'ha canviat el compte d'administrador predeterminat"
608 text_file_repository_writable: Es pot escriure en el dipòsit de fitxers
609 text_rmagick_available: RMagick disponible (opcional)
610 text_destroy_time_entries_question: "S'han informat %.02f hores en els assumptes que aneu a suprimir. Què voleu fer?"
611 text_destroy_time_entries: Suprimeix les hores informades
612 text_assign_time_entries_to_project: Assigna les hores informades al projecte
613 text_reassign_time_entries: 'Torna a assignar les hores informades a aquest assumpte:'
614 text_user_wrote: '%s va escriure:'
615 text_enumeration_destroy_question: '%d objectes estan assignats a aquest valor.'
616 text_enumeration_category_reassign_to: 'Torna a assignar-los a aquest valor:'
617 text_email_delivery_not_configured: "El lliurament per correu electrònic no està configurat i les notificacions estan inhabilitades.\nConfigureu el servidor SMTP a config/email.yml i reinicieu l'aplicació per habilitar-lo."
618
619 default_role_manager: Gestor
620 default_role_developper: Desenvolupador
621 default_role_reporter: Informador
622 default_tracker_bug: Error
623 default_tracker_feature: Característica
624 default_tracker_support: Suport
625 default_issue_status_new: Nou
626 default_issue_status_assigned: Assignat
627 default_issue_status_resolved: Resolt
628 default_issue_status_feedback: Comentaris
629 default_issue_status_closed: Tancat
630 default_issue_status_rejected: Rebutjat
631 default_doc_category_user: "Documentació d'usuari"
632 default_doc_category_tech: Documentació tècnica
633 default_priority_low: Baixa
634 default_priority_normal: Normal
635 default_priority_high: Alta
636 default_priority_urgent: Urgent
637 default_priority_immediate: Immediata
638 default_activity_design: Disseny
639 default_activity_development: Desenvolupament
640
641 enumeration_issue_priorities: Prioritat dels assumptes
642 enumeration_doc_categories: Categories del document
643 enumeration_activities: Activitats (seguidor de temps)
644 setting_plain_text_mail: plain text only (no HTML)
645 permission_view_files: View files
646 permission_edit_issues: Edit issues
647 permission_edit_own_time_entries: Edit own time logs
648 permission_manage_public_queries: Manage public queries
649 permission_add_issues: Add issues
650 permission_log_time: Log spent time
651 permission_view_changesets: View changesets
652 permission_view_time_entries: View spent time
653 permission_manage_versions: Manage versions
654 permission_manage_wiki: Manage wiki
655 permission_manage_categories: Manage issue categories
656 permission_protect_wiki_pages: Protect wiki pages
657 permission_comment_news: Comment news
658 permission_delete_messages: Delete messages
659 permission_select_project_modules: Select project modules
660 permission_manage_documents: Manage documents
661 permission_edit_wiki_pages: Edit wiki pages
662 permission_add_issue_watchers: Add watchers
663 permission_view_gantt: View gantt chart
664 permission_move_issues: Move issues
665 permission_manage_issue_relations: Manage issue relations
666 permission_delete_wiki_pages: Delete wiki pages
667 permission_manage_boards: Manage boards
668 permission_delete_wiki_pages_attachments: Delete attachments
669 permission_view_wiki_edits: View wiki history
670 permission_add_messages: Post messages
671 permission_view_messages: View messages
672 permission_manage_files: Manage files
673 permission_edit_issue_notes: Edit notes
674 permission_manage_news: Manage news
675 permission_view_calendar: View calendrier
676 permission_manage_members: Manage members
677 permission_edit_messages: Edit messages
678 permission_delete_issues: Delete issues
679 permission_view_issue_watchers: View watchers list
680 permission_manage_repository: Manage repository
681 permission_commit_access: Commit access
682 permission_browse_repository: Browse repository
683 permission_view_documents: View documents
684 permission_edit_project: Edit project
685 permission_add_issue_notes: Add notes
686 permission_save_queries: Save queries
687 permission_view_wiki_pages: View wiki
688 permission_rename_wiki_pages: Rename wiki pages
689 permission_edit_time_entries: Edit time logs
690 permission_edit_own_issue_notes: Edit own notes
691 setting_gravatar_enabled: Use Gravatar user icons
692 label_example: Example
693 text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
694 permission_edit_own_messages: Edit own messages
695 permission_delete_own_messages: Delete own messages
696 label_user_activity: "%s's activity"
697 label_updated_time_by: Updated by %s %s ago
698 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
699 setting_diff_max_lines_displayed: Max number of diff lines displayed
700 text_plugin_assets_writable: Plugin assets directory writable
701 warning_attachments_not_saved: "%d file(s) could not be saved."
702 button_create_and_continue: Create and continue
703 text_custom_field_possible_values_info: 'One line for each value'
704 label_display: Display
705 field_editable: Editable
706 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
707 field_identity_url: OpenID URL
708 setting_openid: Allow OpenID login and registration
709 label_login_with_open_id_option: or login with OpenID
710 field_watcher: Watcher
95 actionview_instancetag_blank_option: Seleccioneu
96
97 general_text_No: 'No'
98 general_text_Yes: 'Si'
99 general_text_no: 'no'
100 general_text_yes: 'si'
101 general_lang_name: 'Català'
102 general_csv_separator: ';'
103 general_csv_decimal_separator: ','
104 general_csv_encoding: ISO-8859-15
105 general_pdf_encoding: ISO-8859-15
106 general_first_day_of_week: '1'
107
108 notice_account_updated: "El compte s'ha actualitzat correctament."
109 notice_account_invalid_creditentials: Usuari o contrasenya invàlid
110 notice_account_password_updated: "La contrasenya s'ha modificat correctament."
111 notice_account_wrong_password: Contrasenya incorrecta
112 notice_account_register_done: "El compte s'ha creat correctament. Per a activar el compte, feu clic en l'enllaç que us han enviat per correu electrònic."
113 notice_account_unknown_email: Usuari desconegut.
114 notice_can_t_change_password: "Aquest compte utilitza una font d'autenticació externa. No és possible canviar la contrasenya."
115 notice_account_lost_email_sent: "S'ha enviat un correu electrònic amb instruccions per a seleccionar una contrasenya nova."
116 notice_account_activated: "El compte s'ha activat. Ara podeu entrar."
117 notice_successful_create: "S'ha creat correctament."
118 notice_successful_update: "S'ha modificat correctament."
119 notice_successful_delete: "S'ha suprimit correctament."
120 notice_successful_connection: "S'ha connectat correctament."
121 notice_file_not_found: "La pàgina a la que intenteu accedir no existeix o s'ha suprimit."
122 notice_locking_conflict: Un altre usuari ha actualitzat les dades.
123 notice_not_authorized: No teniu permís per a accedir a aquesta pàgina.
124 notice_email_sent: "S'ha enviat un correu electrònic a {{value}}"
125 notice_email_error: "S'ha produït un error en enviar el correu ({{value}})"
126 notice_feeds_access_key_reseted: "S'ha reiniciat la clau d'accés del RSS."
127 notice_failed_to_save_issues: "No s'han pogut desar %s assumptes de {{count}} seleccionats: {{value}}."
128 notice_no_issue_selected: "No s'ha seleccionat cap assumpte. Activeu els assumptes que voleu editar."
129 notice_account_pending: "S'ha creat el compte i ara està pendent de l'aprovació de l'administrador."
130 notice_default_data_loaded: "S'ha carregat correctament la configuració predeterminada."
131 notice_unable_delete_version: "No s'ha pogut suprimir la versió."
132
133 error_can_t_load_default_data: "No s'ha pogut carregar la configuració predeterminada: {{value}} "
134 error_scm_not_found: "No s'ha trobat l'entrada o la revisió en el dipòsit."
135 error_scm_command_failed: "S'ha produït un error en intentar accedir al dipòsit: {{value}}"
136 error_scm_annotate: "L'entrada no existeix o no s'ha pogut anotar."
137 error_issue_not_found_in_project: "No s'ha trobat l'assumpte o no pertany a aquest projecte"
138
139 mail_subject_lost_password: "Contrasenya de {{value}}"
140 mail_body_lost_password: "Per a canviar la contrasenya, feu clic en l'enllaç següent:"
141 mail_subject_register: "Activació del compte de {{value}}"
142 mail_body_register: "Per a activar el compte, feu clic en l'enllaç següent:"
143 mail_body_account_information_external: "Podeu utilitzar el compte «{{value}}» per a entrar."
144 mail_body_account_information: Informació del compte
145 mail_subject_account_activation_request: "Sol·licitud d'activació del compte de {{value}}"
146 mail_body_account_activation_request: "S'ha registrat un usuari nou ({{value}}). El seu compte està pendent d'aprovació:"
147 mail_subject_reminder: "%d assumptes venceran els següents {{count}} dies"
148 mail_body_reminder: "{{count}} assumptes que teniu assignades venceran els següents {{days}} dies:"
149
150 gui_validation_error: 1 error
151 gui_validation_error_plural: "{{count}} errors"
152
153 field_name: Nom
154 field_description: Descripció
155 field_summary: Resum
156 field_is_required: Necessari
157 field_firstname: Nom
158 field_lastname: Cognom
159 field_mail: Correu electrònic
160 field_filename: Fitxer
161 field_filesize: Mida
162 field_downloads: Baixades
163 field_author: Autor
164 field_created_on: Creat
165 field_updated_on: Actualitzat
166 field_field_format: Format
167 field_is_for_all: Per a tots els projectes
168 field_possible_values: Valores possibles
169 field_regexp: Expressió regular
170 field_min_length: Longitud mínima
171 field_max_length: Longitud màxima
172 field_value: Valor
173 field_category: Categoria
174 field_title: Títol
175 field_project: Projecte
176 field_issue: Assumpte
177 field_status: Estat
178 field_notes: Notes
179 field_is_closed: Assumpte tancat
180 field_is_default: Estat predeterminat
181 field_tracker: Seguidor
182 field_subject: Tema
183 field_due_date: Data de venciment
184 field_assigned_to: Assignat a
185 field_priority: Prioritat
186 field_fixed_version: Versió objectiu
187 field_user: Usuari
188 field_role: Rol
189 field_homepage: Pàgina web
190 field_is_public: Públic
191 field_parent: Subprojecte de
192 field_is_in_chlog: Assumptes mostrats en el registre de canvis
193 field_is_in_roadmap: Assumptes mostrats en la planificació
194 field_login: Entrada
195 field_mail_notification: Notificacions per correu electrònic
196 field_admin: Administrador
197 field_last_login_on: Última connexió
198 field_language: Idioma
199 field_effective_date: Data
200 field_password: Contrasenya
201 field_new_password: Contrasenya nova
202 field_password_confirmation: Confirmació
203 field_version: Versió
204 field_type: Tipus
205 field_host: Ordinador
206 field_port: Port
207 field_account: Compte
208 field_base_dn: Base DN
209 field_attr_login: "Atribut d'entrada"
210 field_attr_firstname: Atribut del nom
211 field_attr_lastname: Atribut del cognom
212 field_attr_mail: Atribut del correu electrònic
213 field_onthefly: "Creació de l'usuari «al vol»"
214 field_start_date: Inici
215 field_done_ratio: %% realitzat
216 field_auth_source: "Mode d'autenticació"
217 field_hide_mail: "Oculta l'adreça de correu electrònic"
218 field_comments: Comentari
219 field_url: URL
220 field_start_page: Pàgina inicial
221 field_subproject: Subprojecte
222 field_hours: Hores
223 field_activity: Activitat
224 field_spent_on: Data
225 field_identifier: Identificador
226 field_is_filter: "S'ha utilitzat com a filtre"
227 field_issue_to_id: Assumpte relacionat
228 field_delay: Retard
229 field_assignable: Es poden assignar assumptes a aquest rol
230 field_redirect_existing_links: Redirigeix els enllaços existents
231 field_estimated_hours: Temps previst
232 field_column_names: Columnes
233 field_time_zone: Zona horària
234 field_searchable: Es pot cercar
235 field_default_value: Valor predeterminat
236 field_comments_sorting: Mostra els comentaris
237 field_parent_title: Pàgina pare
238
239 setting_app_title: "Títol de l'aplicació"
240 setting_app_subtitle: "Subtítol de l'aplicació"
241 setting_welcome_text: Text de benvinguda
242 setting_default_language: Idioma predeterminat
243 setting_login_required: Es necessita autenticació
244 setting_self_registration: Registre automàtic
245 setting_attachment_max_size: Mida màxima dels adjunts
246 setting_issues_export_limit: "Límit d'exportació d'assumptes"
247 setting_mail_from: "Adreça de correu electrònic d'emissió"
248 setting_bcc_recipients: Vincula els destinataris de les còpies amb carbó (bcc)
249 setting_host_name: "Nom de l'ordinador"
250 setting_text_formatting: Format del text
251 setting_wiki_compression: "Comprimeix l'historial del wiki"
252 setting_feeds_limit: Límit de contingut del canal
253 setting_default_projects_public: Els projectes nous són públics per defecte
254 setting_autofetch_changesets: Omple automàticament les publicacions
255 setting_sys_api_enabled: Habilita el WS per a la gestió del dipòsit
256 setting_commit_ref_keywords: Paraules claus per a la referència
257 setting_commit_fix_keywords: Paraules claus per a la correcció
258 setting_autologin: Entrada automàtica
259 setting_date_format: Format de la data
260 setting_time_format: Format de hora
261 setting_cross_project_issue_relations: "Permet les relacions d'assumptes entre projectes"
262 setting_issue_list_default_columns: "Columnes mostrades per defecte en la llista d'assumptes"
263 setting_repositories_encodings: Codificacions del dipòsit
264 setting_commit_logs_encoding: Codificació dels missatges publicats
265 setting_emails_footer: Peu dels correus electrònics
266 setting_protocol: Protocol
267 setting_per_page_options: Opcions dels objectes per pàgina
268 setting_user_format: "Format de com mostrar l'usuari"
269 setting_activity_days_default: "Dies a mostrar l'activitat del projecte"
270 setting_display_subprojects_issues: "Mostra els assumptes d'un subprojecte en el projecte pare per defecte"
271 setting_enabled_scm: "Habilita l'SCM"
272 setting_mail_handler_api_enabled: "Habilita el WS per correus electrònics d'entrada"
273 setting_mail_handler_api_key: Clau API
274 setting_sequential_project_identifiers: Genera identificadors de projecte seqüencials
275
276 project_module_issue_tracking: "Seguidor d'assumptes"
277 project_module_time_tracking: Seguidor de temps
278 project_module_news: Noticies
279 project_module_documents: Documents
280 project_module_files: Fitxers
281 project_module_wiki: Wiki
282 project_module_repository: Dipòsit
283 project_module_boards: Taulers
284
285 label_user: Usuari
286 label_user_plural: Usuaris
287 label_user_new: Usuari nou
288 label_project: Projecte
289 label_project_new: Projecte nou
290 label_project_plural: Projectes
291 label_x_projects:
292 zero: no projects
293 one: 1 project
294 other: "{{count}} projects"
295 label_project_all: Tots els projectes
296 label_project_latest: Els últims projectes
297 label_issue: Assumpte
298 label_issue_new: Assumpte nou
299 label_issue_plural: Assumptes
300 label_issue_view_all: Visualitza tots els assumptes
301 label_issues_by: "Assumptes per {{value}}"
302 label_issue_added: Assumpte afegit
303 label_issue_updated: Assumpte actualitzat
304 label_document: Document
305 label_document_new: Document nou
306 label_document_plural: Documents
307 label_document_added: Document afegit
308 label_role: Rol
309 label_role_plural: Rols
310 label_role_new: Rol nou
311 label_role_and_permissions: Rols i permisos
312 label_member: Membre
313 label_member_new: Membre nou
314 label_member_plural: Membres
315 label_tracker: Seguidor
316 label_tracker_plural: Seguidors
317 label_tracker_new: Seguidor nou
318 label_workflow: Flux de treball
319 label_issue_status: "Estat de l'assumpte"
320 label_issue_status_plural: "Estats de l'assumpte"
321 label_issue_status_new: Estat nou
322 label_issue_category: "Categoria de l'assumpte"
323 label_issue_category_plural: "Categories de l'assumpte"
324 label_issue_category_new: Categoria nova
325 label_custom_field: Camp personalitzat
326 label_custom_field_plural: Camps personalitzats
327 label_custom_field_new: Camp personalitzat nou
328 label_enumerations: Enumeracions
329 label_enumeration_new: Valor nou
330 label_information: Informació
331 label_information_plural: Informació
332 label_please_login: Entreu
333 label_register: Registre
334 label_password_lost: Contrasenya perduda
335 label_home: Inici
336 label_my_page: La meva pàgina
337 label_my_account: El meu compte
338 label_my_projects: Els meus projectes
339 label_administration: Administració
340 label_login: Entra
341 label_logout: Surt
342 label_help: Ajuda
343 label_reported_issues: Assumptes informats
344 label_assigned_to_me_issues: Assumptes assignats a mi
345 label_last_login: Última connexió
346 label_registered_on: Informat el
347 label_activity: Activitat
348 label_overall_activity: Activitat global
349 label_new: Nou
350 label_logged_as: Heu entrat com a
351 label_environment: Entorn
352 label_authentication: Autenticació
353 label_auth_source: "Mode d'autenticació"
354 label_auth_source_new: "Mode d'autenticació nou"
355 label_auth_source_plural: "Modes d'autenticació"
356 label_subproject_plural: Subprojectes
357 label_and_its_subprojects: "{{value}} i els seus subprojectes"
358 label_min_max_length: Longitud mín - max
359 label_list: Llist
360 label_date: Data
361 label_integer: Enter
362 label_float: Flotant
363 label_boolean: Booleà
364 label_string: Text
365 label_text: Text llarg
366 label_attribute: Atribut
367 label_attribute_plural: Atributs
368 label_download: "{{count}} baixada"
369 label_download_plural: "{{count}} baixades"
370 label_no_data: Sense dades a mostrar
371 label_change_status: "Canvia l'estat"
372 label_history: Historial
373 label_attachment: Fitxer
374 label_attachment_new: Fitxer nou
375 label_attachment_delete: Suprimeix el fitxer
376 label_attachment_plural: Fitxers
377 label_file_added: Fitxer afegit
378 label_report: Informe
379 label_report_plural: Informes
380 label_news: Noticies
381 label_news_new: Afegeix noticies
382 label_news_plural: Noticies
383 label_news_latest: Últimes noticies
384 label_news_view_all: Visualitza totes les noticies
385 label_news_added: Noticies afegides
386 label_change_log: Registre de canvis
387 label_settings: Paràmetres
388 label_overview: Resum
389 label_version: Versió
390 label_version_new: Versió nova
391 label_version_plural: Versions
392 label_confirmation: Confirmació
393 label_export_to: 'També disponible a:'
394 label_read: Llegeix...
395 label_public_projects: Projectes públics
396 label_open_issues: obert
397 label_open_issues_plural: oberts
398 label_closed_issues: tancat
399 label_closed_issues_plural: tancats
400 label_x_open_issues_abbr_on_total:
401 zero: 0 open / {{total}}
402 one: 1 open / {{total}}
403 other: "{{count}} open / {{total}}"
404 label_x_open_issues_abbr:
405 zero: 0 open
406 one: 1 open
407 other: "{{count}} open"
408 label_x_closed_issues_abbr:
409 zero: 0 closed
410 one: 1 closed
411 other: "{{count}} closed"
412 label_total: Total
413 label_permissions: Permisos
414 label_current_status: Estat actual
415 label_new_statuses_allowed: Nous estats autoritzats
416 label_all: tots
417 label_none: cap
418 label_nobody: ningú
419 label_next: Següent
420 label_previous: Anterior
421 label_used_by: Utilitzat per
422 label_details: Detalls
423 label_add_note: Afegeix una nota
424 label_per_page: Per pàgina
425 label_calendar: Calendari
426 label_months_from: mesos des de
427 label_gantt: Gantt
428 label_internal: Intern
429 label_last_changes: "últims {{count}} canvis"
430 label_change_view_all: Visualitza tots els canvis
431 label_personalize_page: Personalitza aquesta pàgina
432 label_comment: Comentari
433 label_comment_plural: Comentaris
434 label_x_comments:
435 zero: no comments
436 one: 1 comment
437 other: "{{count}} comments"
438 label_comment_add: Afegeix un comentari
439 label_comment_added: Comentari afegit
440 label_comment_delete: Suprimeix comentaris
441 label_query: Consulta personalitzada
442 label_query_plural: Consultes personalitzades
443 label_query_new: Consulta nova
444 label_filter_add: Afegeix un filtre
445 label_filter_plural: Filtres
446 label_equals: és
447 label_not_equals: no és
448 label_in_less_than: en menys de
449 label_in_more_than: en més de
450 label_in: en
451 label_today: avui
452 label_all_time: tot el temps
453 label_yesterday: ahir
454 label_this_week: aquesta setmana
455 label_last_week: "l'última setmana"
456 label_last_n_days: "els últims {{count}} dies"
457 label_this_month: aquest més
458 label_last_month: "l'últim més"
459 label_this_year: aquest any
460 label_date_range: Abast de les dates
461 label_less_than_ago: fa menys de
462 label_more_than_ago: fa més de
463 label_ago: fa
464 label_contains: conté
465 label_not_contains: no conté
466 label_day_plural: dies
467 label_repository: Dipòsit
468 label_repository_plural: Dipòsits
469 label_browse: Navega
470 label_modification: "{{count}} canvi"
471 label_modification_plural: "{{count}} canvis"
472 label_revision: Revisió
473 label_revision_plural: Revisions
474 label_associated_revisions: Revisions associades
475 label_added: afegit
476 label_modified: modificat
477 label_renamed: reanomenat
478 label_copied: copiat
479 label_deleted: suprimit
480 label_latest_revision: Última revisió
481 label_latest_revision_plural: Últimes revisions
482 label_view_revisions: Visualitza les revisions
483 label_max_size: Mida màxima
484 label_sort_highest: Mou a la part superior
485 label_sort_higher: Mou cap amunt
486 label_sort_lower: Mou cap avall
487 label_sort_lowest: Mou a la part inferior
488 label_roadmap: Planificació
489 label_roadmap_due_in: "Venç en {{value}}"
490 label_roadmap_overdue: "{{value}} tard"
491 label_roadmap_no_issues: No hi ha assumptes per a aquesta versió
492 label_search: Cerca
493 label_result_plural: Resultats
494 label_all_words: Totes les paraules
495 label_wiki: Wiki
496 label_wiki_edit: Edició wiki
497 label_wiki_edit_plural: Edicions wiki
498 label_wiki_page: Pàgina wiki
499 label_wiki_page_plural: Pàgines wiki
500 label_index_by_title: Índex per títol
501 label_index_by_date: Índex per data
502 label_current_version: Versió actual
503 label_preview: Previsualització
504 label_feed_plural: Canals
505 label_changes_details: Detalls de tots els canvis
506 label_issue_tracking: "Seguiment d'assumptes"
507 label_spent_time: Temps invertit
508 label_f_hour: "{{value}} hora"
509 label_f_hour_plural: "{{value}} hores"
510 label_time_tracking: Temps de seguiment
511 label_change_plural: Canvis
512 label_statistics: Estadístiques
513 label_commits_per_month: Publicacions per mes
514 label_commits_per_author: Publicacions per autor
515 label_view_diff: Visualitza les diferències
516 label_diff_inline: en línia
517 label_diff_side_by_side: costat per costat
518 label_options: Opcions
519 label_copy_workflow_from: Copia el flux de treball des de
520 label_permissions_report: Informe de permisos
521 label_watched_issues: Assumptes vigilats
522 label_related_issues: Assumptes relacionats
523 label_applied_status: Estat aplicat
524 label_loading: "S'està carregant..."
525 label_relation_new: Relació nova
526 label_relation_delete: Suprimeix la relació
527 label_relates_to: relacionat amb
528 label_duplicates: duplicats
529 label_duplicated_by: duplicat per
530 label_blocks: bloqueja
531 label_blocked_by: bloquejats per
532 label_precedes: anterior a
533 label_follows: posterior a
534 label_end_to_start: final al començament
535 label_end_to_end: final al final
536 label_start_to_start: començament al començament
537 label_start_to_end: començament al final
538 label_stay_logged_in: "Manté l'entrada"
539 label_disabled: inhabilitat
540 label_show_completed_versions: Mostra les versions completes
541 label_me: jo mateix
542 label_board: Fòrum
543 label_board_new: Fòrum nou
544 label_board_plural: Fòrums
545 label_topic_plural: Temes
546 label_message_plural: Missatges
547 label_message_last: Últim missatge
548 label_message_new: Missatge nou
549 label_message_posted: Missatge afegit
550 label_reply_plural: Respostes
551 label_send_information: "Envia la informació del compte a l'usuari"
552 label_year: Any
553 label_month: Mes
554 label_week: Setmana
555 label_date_from: Des de
556 label_date_to: A
557 label_language_based: "Basat en l'idioma de l'usuari"
558 label_sort_by: "Ordena per {{value}}"
559 label_send_test_email: Envia un correu electrònic de prova
560 label_feeds_access_key_created_on: "Clau d'accés del RSS creada fa {{value}}"
561 label_module_plural: Mòduls
562 label_added_time_by: "Afegit per {{author}} fa {{age}}"
563 label_updated_time: "Actualitzat fa {{value}}"
564 label_jump_to_a_project: Salta al projecte...
565 label_file_plural: Fitxers
566 label_changeset_plural: Conjunt de canvis
567 label_default_columns: Columnes predeterminades
568 label_no_change_option: (sense canvis)
569 label_bulk_edit_selected_issues: Edita en bloc els assumptes seleccionats
570 label_theme: Tema
571 label_default: Predeterminat
572 label_search_titles_only: Cerca només en els títols
573 label_user_mail_option_all: "Per qualsevol esdeveniment en tots els meus projectes"
574 label_user_mail_option_selected: "Per qualsevol esdeveniment en els projectes seleccionats..."
575 label_user_mail_option_none: "Només per les coses que vigilo o hi estic implicat"
576 label_user_mail_no_self_notified: "No vull ser notificat pels canvis que faig jo mateix"
577 label_registration_activation_by_email: activació del compte per correu electrònic
578 label_registration_manual_activation: activació del compte manual
579 label_registration_automatic_activation: activació del compte automàtica
580 label_display_per_page: "Per pàgina: {{value}}'"
581 label_age: Edat
582 label_change_properties: Canvia les propietats
583 label_general: General
584 label_more: Més
585 label_scm: SCM
586 label_plugins: Connectors
587 label_ldap_authentication: Autenticació LDAP
588 label_downloads_abbr: Baixades
589 label_optional_description: Descripció opcional
590 label_add_another_file: Afegeix un altre fitxer
591 label_preferences: Preferències
592 label_chronological_order: En ordre cronològic
593 label_reverse_chronological_order: En ordre cronològic invers
594 label_planning: Planificació
595 label_incoming_emails: "Correu electrònics d'entrada"
596 label_generate_key: Genera una clau
597 label_issue_watchers: Vigilants
598
599 button_login: Entra
600 button_submit: Tramet
601 button_save: Desa
602 button_check_all: Activa-ho tot
603 button_uncheck_all: Desactiva-ho tot
604 button_delete: Suprimeix
605 button_create: Crea
606 button_test: Test
607 button_edit: Edit
608 button_add: Afegeix
609 button_change: Canvia
610 button_apply: Aplica
611 button_clear: Neteja
612 button_lock: Bloca
613 button_unlock: Desbloca
614 button_download: Baixa
615 button_list: Llista
616 button_view: Visualitza
617 button_move: Mou
618 button_back: Enrere
619 button_cancel: Cancel·la
620 button_activate: Activa
621 button_sort: Ordena
622 button_log_time: "Hora d'entrada"
623 button_rollback: Torna a aquesta versió
624 button_watch: Vigila
625 button_unwatch: No vigilis
626 button_reply: Resposta
627 button_archive: Arxiva
628 button_unarchive: Desarxiva
629 button_reset: Reinicia
630 button_rename: Reanomena
631 button_change_password: Canvia la contrasenya
632 button_copy: Copia
633 button_annotate: Anota
634 button_update: Actualitza
635 button_configure: Configura
636 button_quote: Cita
637
638 status_active: actiu
639 status_registered: informat
640 status_locked: bloquejat
641
642 text_select_mail_notifications: "Seleccioneu les accions per les quals s'hauria d'enviar una notificació per correu electrònic."
643 text_regexp_info: ex. ^[A-Z0-9]+$
644 text_min_max_length_info: 0 significa sense restricció
645 text_project_destroy_confirmation: Segur que voleu suprimir aquest projecte i les dades relacionades?
646 text_subprojects_destroy_warning: "També seran suprimits els seus subprojectes: {{value}}."
647 text_workflow_edit: Seleccioneu un rol i un seguidor per a editar el flux de treball
648 text_are_you_sure: Segur?
649 text_journal_changed: "canviat des de {{old}} a {{new}}"
650 text_journal_set_to: "establert a {{value}}"
651 text_journal_deleted: suprimit
652 text_tip_task_begin_day: "tasca que s'inicia aquest dia"
653 text_tip_task_end_day: tasca que finalitza aquest dia
654 text_tip_task_begin_end_day: "tasca que s'inicia i finalitza aquest dia"
655 text_project_identifier_info: "Es permeten lletres en minúscules (a-z), números i guions.<br />Un cop desat, l'identificador no es pot modificar."
656 text_caracters_maximum: "{{count}} caràcters com a màxim."
657 text_caracters_minimum: "Com a mínim ha de tenir {{count}} caràcters."
658 text_length_between: "Longitud entre {{min}} i {{max}} caràcters."
659 text_tracker_no_workflow: "No s'ha definit cap flux de treball per a aquest seguidor"
660 text_unallowed_characters: Caràcters no permesos
661 text_comma_separated: Es permeten valors múltiples (separats per una coma).
662 text_issues_ref_in_commit_messages: Referència i soluciona els assumptes en els missatges publicats
663 text_issue_added: "L'assumpte {{id}} ha sigut informat per {{author}}."
664 text_issue_updated: "L'assumpte {{id}} ha sigut actualitzat per {{author}}."
665 text_wiki_destroy_confirmation: Segur que voleu suprimir aquest wiki i tots els seus continguts?
666 text_issue_category_destroy_question: "Alguns assumptes ({{count}}) estan assignats a aquesta categoria. Què voleu fer?"
667 text_issue_category_destroy_assignments: Suprimeix les assignacions de la categoria
668 text_issue_category_reassign_to: Torna a assignar els assumptes a aquesta categoria
669 text_user_mail_option: "Per als projectes no seleccionats, només rebreu notificacions sobre les coses que vigileu o que hi esteu implicat (ex. assumptes que en sou l'autor o hi esteu assignat)."
670 text_no_configuration_data: "Encara no s'han configurat els rols, seguidors, estats de l'assumpte i flux de treball.\nÉs altament recomanable que carregueu la configuració predeterminada. Podreu modificar-la un cop carregada."
671 text_load_default_configuration: Carrega la configuració predeterminada
672 text_status_changed_by_changeset: "Aplicat en el conjunt de canvis {{value}}."
673 text_issues_destroy_confirmation: "Segur que voleu suprimir els assumptes seleccionats?"
674 text_select_project_modules: "Seleccioneu els mòduls a habilitar per a aquest projecte:"
675 text_default_administrator_account_changed: "S'ha canviat el compte d'administrador predeterminat"
676 text_file_repository_writable: Es pot escriure en el dipòsit de fitxers
677 text_rmagick_available: RMagick disponible (opcional)
678 text_destroy_time_entries_question: "S'han informat %.02f hores en els assumptes que aneu a suprimir. Què voleu fer?"
679 text_destroy_time_entries: Suprimeix les hores informades
680 text_assign_time_entries_to_project: Assigna les hores informades al projecte
681 text_reassign_time_entries: 'Torna a assignar les hores informades a aquest assumpte:'
682 text_user_wrote: "{{value}} va escriure:'"
683 text_enumeration_destroy_question: "{{count}} objectes estan assignats a aquest valor.'"
684 text_enumeration_category_reassign_to: 'Torna a assignar-los a aquest valor:'
685 text_email_delivery_not_configured: "El lliurament per correu electrònic no està configurat i les notificacions estan inhabilitades.\nConfigureu el servidor SMTP a config/email.yml i reinicieu l'aplicació per habilitar-lo."
686
687 default_role_manager: Gestor
688 default_role_developper: Desenvolupador
689 default_role_reporter: Informador
690 default_tracker_bug: Error
691 default_tracker_feature: Característica
692 default_tracker_support: Suport
693 default_issue_status_new: Nou
694 default_issue_status_assigned: Assignat
695 default_issue_status_resolved: Resolt
696 default_issue_status_feedback: Comentaris
697 default_issue_status_closed: Tancat
698 default_issue_status_rejected: Rebutjat
699 default_doc_category_user: "Documentació d'usuari"
700 default_doc_category_tech: Documentació tècnica
701 default_priority_low: Baixa
702 default_priority_normal: Normal
703 default_priority_high: Alta
704 default_priority_urgent: Urgent
705 default_priority_immediate: Immediata
706 default_activity_design: Disseny
707 default_activity_development: Desenvolupament
708
709 enumeration_issue_priorities: Prioritat dels assumptes
710 enumeration_doc_categories: Categories del document
711 enumeration_activities: Activitats (seguidor de temps)
712 setting_plain_text_mail: plain text only (no HTML)
713 permission_view_files: View files
714 permission_edit_issues: Edit issues
715 permission_edit_own_time_entries: Edit own time logs
716 permission_manage_public_queries: Manage public queries
717 permission_add_issues: Add issues
718 permission_log_time: Log spent time
719 permission_view_changesets: View changesets
720 permission_view_time_entries: View spent time
721 permission_manage_versions: Manage versions
722 permission_manage_wiki: Manage wiki
723 permission_manage_categories: Manage issue categories
724 permission_protect_wiki_pages: Protect wiki pages
725 permission_comment_news: Comment news
726 permission_delete_messages: Delete messages
727 permission_select_project_modules: Select project modules
728 permission_manage_documents: Manage documents
729 permission_edit_wiki_pages: Edit wiki pages
730 permission_add_issue_watchers: Add watchers
731 permission_view_gantt: View gantt chart
732 permission_move_issues: Move issues
733 permission_manage_issue_relations: Manage issue relations
734 permission_delete_wiki_pages: Delete wiki pages
735 permission_manage_boards: Manage boards
736 permission_delete_wiki_pages_attachments: Delete attachments
737 permission_view_wiki_edits: View wiki history
738 permission_add_messages: Post messages
739 permission_view_messages: View messages
740 permission_manage_files: Manage files
741 permission_edit_issue_notes: Edit notes
742 permission_manage_news: Manage news
743 permission_view_calendar: View calendrier
744 permission_manage_members: Manage members
745 permission_edit_messages: Edit messages
746 permission_delete_issues: Delete issues
747 permission_view_issue_watchers: View watchers list
748 permission_manage_repository: Manage repository
749 permission_commit_access: Commit access
750 permission_browse_repository: Browse repository
751 permission_view_documents: View documents
752 permission_edit_project: Edit project
753 permission_add_issue_notes: Add notes
754 permission_save_queries: Save queries
755 permission_view_wiki_pages: View wiki
756 permission_rename_wiki_pages: Rename wiki pages
757 permission_edit_time_entries: Edit time logs
758 permission_edit_own_issue_notes: Edit own notes
759 setting_gravatar_enabled: Use Gravatar user icons
760 label_example: Example
761 text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
762 permission_edit_own_messages: Edit own messages
763 permission_delete_own_messages: Delete own messages
764 label_user_activity: "{{value}}'s activity"
765 label_updated_time_by: "Updated by {{author}} {{age}} ago"
766 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
767 setting_diff_max_lines_displayed: Max number of diff lines displayed
768 text_plugin_assets_writable: Plugin assets directory writable
769 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
770 button_create_and_continue: Create and continue
771 text_custom_field_possible_values_info: 'One line for each value'
772 label_display: Display
773 field_editable: Editable
774 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
This diff has been collapsed as it changes many lines, (1488 lines changed) Show them Hide them
@@ -1,714 +1,778
1 # CZ translation by Maxim Krušina | Massimo Filippi, s.r.o. | maxim@mxm.cz
2 # Based on original CZ translation by Jan Kadleček
1 cs:
2 date:
3 formats:
4 # Use the strftime parameters for formats.
5 # When no format has been given, it uses default.
6 # You can provide other formats here if you like!
7 default: "%Y-%m-%d"
8 short: "%b %d"
9 long: "%B %d, %Y"
10
11 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
12 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
13
14 # Don't forget the nil at the beginning; there's no such thing as a 0th month
15 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
16 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
17 # Used in date_select and datime_select.
18 order: [ :year, :month, :day ]
3 19
4 _gloc_rule_default: '|n| n==1 ? "" : "_plural" '
20 time:
21 formats:
22 default: "%a, %d %b %Y %H:%M:%S %z"
23 short: "%d %b %H:%M"
24 long: "%B %d, %Y %H:%M"
25 am: "am"
26 pm: "pm"
27
28 datetime:
29 distance_in_words:
30 half_a_minute: "half a minute"
31 less_than_x_seconds:
32 one: "less than 1 second"
33 other: "less than {{count}} seconds"
34 x_seconds:
35 one: "1 second"
36 other: "{{count}} seconds"
37 less_than_x_minutes:
38 one: "less than a minute"
39 other: "less than {{count}} minutes"
40 x_minutes:
41 one: "1 minute"
42 other: "{{count}} minutes"
43 about_x_hours:
44 one: "about 1 hour"
45 other: "about {{count}} hours"
46 x_days:
47 one: "1 day"
48 other: "{{count}} days"
49 about_x_months:
50 one: "about 1 month"
51 other: "about {{count}} months"
52 x_months:
53 one: "1 month"
54 other: "{{count}} months"
55 about_x_years:
56 one: "about 1 year"
57 other: "about {{count}} years"
58 over_x_years:
59 one: "over 1 year"
60 other: "over {{count}} years"
61
62 # Used in array.to_sentence.
63 support:
64 array:
65 sentence_connector: "and"
66 skip_last_comma: false
67
68 activerecord:
69 errors:
70 messages:
71 inclusion: "není zahrnuto v seznamu"
72 exclusion: "je rezervováno"
73 invalid: "je neplatné"
74 confirmation: "se neshoduje s potvrzením"
75 accepted: "musí být akceptováno"
76 empty: "nemůže být prázdný"
77 blank: "nemůže být prázdný"
78 too_long: "je příliš dlouhý"
79 too_short: "je příliš krátký"
80 wrong_length: "má chybnou délku"
81 taken: "je již použito"
82 not_a_number: "není číslo"
83 not_a_date: "není platné datum"
84 greater_than: "must be greater than {{count}}"
85 greater_than_or_equal_to: "must be greater than or equal to {{count}}"
86 equal_to: "must be equal to {{count}}"
87 less_than: "must be less than {{count}}"
88 less_than_or_equal_to: "must be less than or equal to {{count}}"
89 odd: "must be odd"
90 even: "must be even"
91 greater_than_start_date: "musí být větší než počáteční datum"
92 not_same_project: "nepatří stejnému projektu"
93 circular_dependency: "Tento vztah by vytvořil cyklickou závislost"
5 94
6 actionview_datehelper_select_day_prefix:
7 actionview_datehelper_select_month_names: Leden,Únor,Březen,Duben,Květen,Červen,Červenec,Srpen,Září,Říjen,Listopad,Prosinec
8 actionview_datehelper_select_month_names_abbr: Led,Úno,Bře,Dub,Kvě,Čer,Čvc,Srp,Zář,Říj,Lis,Pro
9 actionview_datehelper_select_month_prefix:
10 actionview_datehelper_select_year_prefix:
11 actionview_datehelper_time_in_words_day: 1 den
12 actionview_datehelper_time_in_words_day_plural: %d dny
13 actionview_datehelper_time_in_words_hour_about: asi hodinou
14 actionview_datehelper_time_in_words_hour_about_plural: asi %d hodinami
15 actionview_datehelper_time_in_words_hour_about_single: asi hodinou
16 actionview_datehelper_time_in_words_minute: 1 minutou
17 actionview_datehelper_time_in_words_minute_half: půl minutou
18 actionview_datehelper_time_in_words_minute_less_than: méně než minutou
19 actionview_datehelper_time_in_words_minute_plural: %d minutami
20 actionview_datehelper_time_in_words_minute_single: 1 minutou
21 actionview_datehelper_time_in_words_second_less_than: méně než sekundou
22 actionview_datehelper_time_in_words_second_less_than_plural: méně než %d sekundami
23 actionview_instancetag_blank_option: Prosím vyberte
24
25 activerecord_error_inclusion: není zahrnuto v seznamu
26 activerecord_error_exclusion: je rezervováno
27 activerecord_error_invalid: je neplatné
28 activerecord_error_confirmation: se neshoduje s potvrzením
29 activerecord_error_accepted: musí být akceptováno
30 activerecord_error_empty: nemůže být prázdný
31 activerecord_error_blank: nemůže být prázdný
32 activerecord_error_too_long: je příliš dlouhý
33 activerecord_error_too_short: je příliš krátký
34 activerecord_error_wrong_length: má chybnou délku
35 activerecord_error_taken: je již použito
36 activerecord_error_not_a_number: není číslo
37 activerecord_error_not_a_date: není platné datum
38 activerecord_error_greater_than_start_date: musí být větší než počáteční datum
39 activerecord_error_not_same_project: nepatří stejnému projektu
40 activerecord_error_circular_dependency: Tento vztah by vytvořil cyklickou závislost
41
42 general_fmt_age: %d rok
43 general_fmt_age_plural: %d roků
44 general_fmt_date: %%m/%%d/%%Y
45 general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p
46 general_fmt_datetime_short: %%b %%d, %%I:%%M %%p
47 general_fmt_time: %%I:%%M %%p
48 general_text_No: 'Ne'
49 general_text_Yes: 'Ano'
50 general_text_no: 'ne'
51 general_text_yes: 'ano'
52 general_lang_name: 'Čeština'
53 general_csv_separator: ','
54 general_csv_decimal_separator: '.'
55 general_csv_encoding: UTF-8
56 general_pdf_encoding: UTF-8
57 general_day_names: Pondělí,Úterý,Středa,Čtvrtek,Pátek,Sobota,Neděle
58 general_first_day_of_week: '1'
59
60 notice_account_updated: Účet byl úspěšně změněn.
61 notice_account_invalid_creditentials: Chybné jméno nebo heslo
62 notice_account_password_updated: Heslo bylo úspěšně změněno.
63 notice_account_wrong_password: Chybné heslo
64 notice_account_register_done: Účet byl úspěšně vytvořen. Pro aktivaci účtu klikněte na odkaz v emailu, který vám byl zaslán.
65 notice_account_unknown_email: Neznámý uživatel.
66 notice_can_t_change_password: Tento účet používá externí autentifikaci. Zde heslo změnit nemůžete.
67 notice_account_lost_email_sent: Byl vám zaslán email s intrukcemi jak si nastavíte nové heslo.
68 notice_account_activated: Váš účet byl aktivován. Nyní se můžete přihlásit.
69 notice_successful_create: Úspěšně vytvořeno.
70 notice_successful_update: Úspěšně aktualizováno.
71 notice_successful_delete: Úspěšně odstraněno.
72 notice_successful_connection: Úspěšné připojení.
73 notice_file_not_found: Stránka na kterou se snažíte zobrazit neexistuje nebo byla smazána.
74 notice_locking_conflict: Údaje byly změněny jiným uživatelem.
75 notice_scm_error: Entry and/or revision doesn't exist in the repository.
76 notice_not_authorized: Nemáte dostatečná práva pro zobrazení této stránky.
77 notice_email_sent: Na adresu %s byl odeslán email
78 notice_email_error: Při odesílání emailu nastala chyba (%s)
79 notice_feeds_access_key_reseted: Váš klíč pro přístup k RSS byl resetován.
80 notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s."
81 notice_no_issue_selected: "Nebyl zvolen žádný úkol. Prosím, zvolte úkoly, které chcete editovat"
82 notice_account_pending: "Váš účet byl vytvořen, nyní čeká na schválení administrátorem."
83 notice_default_data_loaded: Výchozí konfigurace úspěšně nahrána.
84
85 error_can_t_load_default_data: "Výchozí konfigurace nebyla nahrána: %s"
86 error_scm_not_found: "Položka a/nebo revize neexistují v repository."
87 error_scm_command_failed: "Při pokusu o přístup k repository došlo k chybě: %s"
88 error_issue_not_found_in_project: 'Úkol nebyl nalezen nebo nepatří k tomuto projektu'
89
90 mail_subject_lost_password: Vaše heslo (%s)
91 mail_body_lost_password: 'Pro změnu vašeho hesla klikněte na následující odkaz:'
92 mail_subject_register: Aktivace účtu (%s)
93 mail_body_register: 'Pro aktivaci vašeho účtu klikněte na následující odkaz:'
94 mail_body_account_information_external: Pomocí vašeho účtu "%s" se můžete přihlásit.
95 mail_body_account_information: Informace o vašem účtu
96 mail_subject_account_activation_request: Aktivace %s účtu
97 mail_body_account_activation_request: Byl zaregistrován nový uživatel "%s". Aktivace jeho účtu závisí na vašem potvrzení.
98
99 gui_validation_error: 1 chyba
100 gui_validation_error_plural: %d chyb(y)
101
102 field_name: Název
103 field_description: Popis
104 field_summary: Přehled
105 field_is_required: Povinné pole
106 field_firstname: Jméno
107 field_lastname: Příjmení
108 field_mail: Email
109 field_filename: Soubor
110 field_filesize: Velikost
111 field_downloads: Staženo
112 field_author: Autor
113 field_created_on: Vytvořeno
114 field_updated_on: Aktualizováno
115 field_field_format: Formát
116 field_is_for_all: Pro všechny projekty
117 field_possible_values: Možné hodnoty
118 field_regexp: Regulární výraz
119 field_min_length: Minimální délka
120 field_max_length: Maximální délka
121 field_value: Hodnota
122 field_category: Kategorie
123 field_title: Název
124 field_project: Projekt
125 field_issue: Úkol
126 field_status: Stav
127 field_notes: Poznámka
128 field_is_closed: Úkol uzavřen
129 field_is_default: Výchozí stav
130 field_tracker: Fronta
131 field_subject: Předmět
132 field_due_date: Uzavřít do
133 field_assigned_to: Přiřazeno
134 field_priority: Priorita
135 field_fixed_version: Přiřazeno k verzi
136 field_user: Uživatel
137 field_role: Role
138 field_homepage: Homepage
139 field_is_public: Veřejný
140 field_parent: Nadřazený projekt
141 field_is_in_chlog: Úkoly zobrazené v změnovém logu
142 field_is_in_roadmap: Úkoly zobrazené v plánu
143 field_login: Přihlášení
144 field_mail_notification: Emailová oznámení
145 field_admin: Administrátor
146 field_last_login_on: Poslední přihlášení
147 field_language: Jazyk
148 field_effective_date: Datum
149 field_password: Heslo
150 field_new_password: Nové heslo
151 field_password_confirmation: Potvrzení
152 field_version: Verze
153 field_type: Typ
154 field_host: Host
155 field_port: Port
156 field_account: Účet
157 field_base_dn: Base DN
158 field_attr_login: Přihlášení (atribut)
159 field_attr_firstname: Jméno (atribut)
160 field_attr_lastname: Příjemní (atribut)
161 field_attr_mail: Email (atribut)
162 field_onthefly: Automatické vytváření uživatelů
163 field_start_date: Začátek
164 field_done_ratio: %% Hotovo
165 field_auth_source: Autentifikační mód
166 field_hide_mail: Nezobrazovat můj email
167 field_comments: Komentář
168 field_url: URL
169 field_start_page: Výchozí stránka
170 field_subproject: Podprojekt
171 field_hours: Hodiny
172 field_activity: Aktivita
173 field_spent_on: Datum
174 field_identifier: Identifikátor
175 field_is_filter: Použít jako filtr
176 field_issue_to_id: Související úkol
177 field_delay: Zpoždění
178 field_assignable: Úkoly mohou být přiřazeny této roli
179 field_redirect_existing_links: Přesměrovat stvávající odkazy
180 field_estimated_hours: Odhadovaná doba
181 field_column_names: Sloupce
182 field_time_zone: Časové pásmo
183 field_searchable: Umožnit vyhledávání
184 field_default_value: Výchozí hodnota
185 field_comments_sorting: Zobrazit komentáře
186
187 setting_app_title: Název aplikace
188 setting_app_subtitle: Podtitulek aplikace
189 setting_welcome_text: Uvítací text
190 setting_default_language: Výchozí jazyk
191 setting_login_required: Auten. vyžadována
192 setting_self_registration: Povolena automatická registrace
193 setting_attachment_max_size: Maximální velikost přílohy
194 setting_issues_export_limit: Limit pro export úkolů
195 setting_mail_from: Odesílat emaily z adresy
196 setting_bcc_recipients: Příjemci skryté kopie (bcc)
197 setting_host_name: Host name
198 setting_text_formatting: Formátování textu
199 setting_wiki_compression: Komperese historie Wiki
200 setting_feeds_limit: Feed content limit
201 setting_default_projects_public: Nové projekty nastavovat jako veřejné
202 setting_autofetch_changesets: Autofetch commits
203 setting_sys_api_enabled: Povolit WS pro správu repozitory
204 setting_commit_ref_keywords: Klíčová slova pro odkazy
205 setting_commit_fix_keywords: Klíčová slova pro uzavření
206 setting_autologin: Automatické přihlašování
207 setting_date_format: Formát data
208 setting_time_format: Formát času
209 setting_cross_project_issue_relations: Povolit vazby úkolů napříč projekty
210 setting_issue_list_default_columns: Výchozí sloupce zobrazené v seznamu úkolů
211 setting_repositories_encodings: Kódování
212 setting_emails_footer: Patička emailů
213 setting_protocol: Protokol
214 setting_per_page_options: Povolené počty řádků na stránce
215 setting_user_format: Formát zobrazení uživatele
216 setting_activity_days_default: Days displayed on project activity
217 setting_display_subprojects_issues: Display subprojects issues on main projects by default
218
219 project_module_issue_tracking: Sledování úkolů
220 project_module_time_tracking: Sledování času
221 project_module_news: Novinky
222 project_module_documents: Dokumenty
223 project_module_files: Soubory
224 project_module_wiki: Wiki
225 project_module_repository: Repository
226 project_module_boards: Diskuse
227
228 label_user: Uživatel
229 label_user_plural: Uživatelé
230 label_user_new: Nový uživatel
231 label_project: Projekt
232 label_project_new: Nový projekt
233 label_project_plural: Projekty
234 label_project_all: Všechny projekty
235 label_project_latest: Poslední projekty
236 label_issue: Úkol
237 label_issue_new: Nový úkol
238 label_issue_plural: Úkoly
239 label_issue_view_all: Všechny úkoly
240 label_issues_by: Úkoly od uživatele %s
241 label_issue_added: Úkol přidán
242 label_issue_updated: Úkol aktualizován
243 label_document: Dokument
244 label_document_new: Nový dokument
245 label_document_plural: Dokumenty
246 label_document_added: Dokument přidán
247 label_role: Role
248 label_role_plural: Role
249 label_role_new: Nová role
250 label_role_and_permissions: Role a práva
251 label_member: Člen
252 label_member_new: Nový člen
253 label_member_plural: Členové
254 label_tracker: Fronta
255 label_tracker_plural: Fronty
256 label_tracker_new: Nová fronta
257 label_workflow: Workflow
258 label_issue_status: Stav úkolu
259 label_issue_status_plural: Stavy úkolů
260 label_issue_status_new: Nový stav
261 label_issue_category: Kategorie úkolu
262 label_issue_category_plural: Kategorie úkolů
263 label_issue_category_new: Nová kategorie
264 label_custom_field: Uživatelské pole
265 label_custom_field_plural: Uživatelská pole
266 label_custom_field_new: Nové uživatelské pole
267 label_enumerations: Seznamy
268 label_enumeration_new: Nová hodnota
269 label_information: Informace
270 label_information_plural: Informace
271 label_please_login: Prosím přihlašte se
272 label_register: Registrovat
273 label_password_lost: Zapomenuté heslo
274 label_home: Úvodní
275 label_my_page: Moje stránka
276 label_my_account: Můj účet
277 label_my_projects: Moje projekty
278 label_administration: Administrace
279 label_login: Přihlášení
280 label_logout: Odhlášení
281 label_help: Nápověda
282 label_reported_issues: Nahlášené úkoly
283 label_assigned_to_me_issues: Mé úkoly
284 label_last_login: Poslední přihlášení
285 label_last_updates: Poslední změna
286 label_last_updates_plural: %d poslední změny
287 label_registered_on: Registrován
288 label_activity: Aktivita
289 label_overall_activity: Celková aktivita
290 label_new: Nový
291 label_logged_as: Přihlášen jako
292 label_environment: Prostředí
293 label_authentication: Autentifikace
294 label_auth_source: Mód autentifikace
295 label_auth_source_new: Nový mód autentifikace
296 label_auth_source_plural: Módy autentifikace
297 label_subproject_plural: Podprojekty
298 label_min_max_length: Min - Max délka
299 label_list: Seznam
300 label_date: Datum
301 label_integer: Celé číslo
302 label_float: Desetiné číslo
303 label_boolean: Ano/Ne
304 label_string: Text
305 label_text: Dlouhý text
306 label_attribute: Atribut
307 label_attribute_plural: Atributy
308 label_download: %d Download
309 label_download_plural: %d Downloads
310 label_no_data: Žádné položky
311 label_change_status: Změnit stav
312 label_history: Historie
313 label_attachment: Soubor
314 label_attachment_new: Nový soubor
315 label_attachment_delete: Odstranit soubor
316 label_attachment_plural: Soubory
317 label_file_added: Soubor přidán
318 label_report: Přeheled
319 label_report_plural: Přehledy
320 label_news: Novinky
321 label_news_new: Přidat novinku
322 label_news_plural: Novinky
323 label_news_latest: Poslední novinky
324 label_news_view_all: Zobrazit všechny novinky
325 label_news_added: Novinka přidána
326 label_change_log: Protokol změn
327 label_settings: Nastavení
328 label_overview: Přehled
329 label_version: Verze
330 label_version_new: Nová verze
331 label_version_plural: Verze
332 label_confirmation: Potvrzení
333 label_export_to: 'Také k dispozici:'
334 label_read: Načítá se...
335 label_public_projects: Veřejné projekty
336 label_open_issues: otevřený
337 label_open_issues_plural: otevřené
338 label_closed_issues: uzavřený
339 label_closed_issues_plural: uzavřené
340 label_total: Celkem
341 label_permissions: Práva
342 label_current_status: Aktuální stav
343 label_new_statuses_allowed: Nové povolené stavy
344 label_all: vše
345 label_none: nic
346 label_nobody: nikdo
347 label_next: Další
348 label_previous: Předchozí
349 label_used_by: Použito
350 label_details: Detaily
351 label_add_note: Přidat poznámku
352 label_per_page: Na stránku
353 label_calendar: Kalendář
354 label_months_from: měsíců od
355 label_gantt: Ganttův graf
356 label_internal: Interní
357 label_last_changes: posledních %d změn
358 label_change_view_all: Zobrazit všechny změny
359 label_personalize_page: Přizpůsobit tuto stránku
360 label_comment: Komentář
361 label_comment_plural: Komentáře
362 label_comment_add: Přidat komentáře
363 label_comment_added: Komentář přidán
364 label_comment_delete: Odstranit komentář
365 label_query: Uživatelský dotaz
366 label_query_plural: Uživatelské dotazy
367 label_query_new: Nový dotaz
368 label_filter_add: Přidat filtr
369 label_filter_plural: Filtry
370 label_equals: je
371 label_not_equals: není
372 label_in_less_than: je měší než
373 label_in_more_than: je větší než
374 label_in: v
375 label_today: dnes
376 label_all_time: vše
377 label_yesterday: včera
378 label_this_week: tento týden
379 label_last_week: minulý týden
380 label_last_n_days: posledních %d dnů
381 label_this_month: tento měsíc
382 label_last_month: minulý měsíc
383 label_this_year: tento rok
384 label_date_range: Časový rozsah
385 label_less_than_ago: před méně jak (dny)
386 label_more_than_ago: před více jak (dny)
387 label_ago: před (dny)
388 label_contains: obsahuje
389 label_not_contains: neobsahuje
390 label_day_plural: dny
391 label_repository: Repository
392 label_repository_plural: Repository
393 label_browse: Procházet
394 label_modification: %d změna
395 label_modification_plural: %d změn
396 label_revision: Revize
397 label_revision_plural: Revizí
398 label_associated_revisions: Související verze
399 label_added: přidáno
400 label_modified: změněno
401 label_deleted: odstraněno
402 label_latest_revision: Poslední revize
403 label_latest_revision_plural: Poslední revize
404 label_view_revisions: Zobrazit revize
405 label_max_size: Maximální velikost
406 label_on: 'zapnuto'
407 label_sort_highest: Přesunout na začátek
408 label_sort_higher: Přesunout nahoru
409 label_sort_lower: Přesunout dolů
410 label_sort_lowest: Přesunout na konec
411 label_roadmap: Plán
412 label_roadmap_due_in: Zbývá %s
413 label_roadmap_overdue: %s pozdě
414 label_roadmap_no_issues: Pro tuto verzi nejsou žádné úkoly
415 label_search: Hledat
416 label_result_plural: Výsledky
417 label_all_words: Všechna slova
418 label_wiki: Wiki
419 label_wiki_edit: Wiki úprava
420 label_wiki_edit_plural: Wiki úpravy
421 label_wiki_page: Wiki stránka
422 label_wiki_page_plural: Wiki stránky
423 label_index_by_title: Index dle názvu
424 label_index_by_date: Index dle data
425 label_current_version: Aktuální verze
426 label_preview: Náhled
427 label_feed_plural: Příspěvky
428 label_changes_details: Detail všech změn
429 label_issue_tracking: Sledování úkolů
430 label_spent_time: Strávený čas
431 label_f_hour: %.2f hodina
432 label_f_hour_plural: %.2f hodin
433 label_time_tracking: Sledování času
434 label_change_plural: Změny
435 label_statistics: Statistiky
436 label_commits_per_month: Commitů za měsíc
437 label_commits_per_author: Commitů za autora
438 label_view_diff: Zobrazit rozdíly
439 label_diff_inline: uvnitř
440 label_diff_side_by_side: vedle sebe
441 label_options: Nastavení
442 label_copy_workflow_from: Kopírovat workflow z
443 label_permissions_report: Přehled práv
444 label_watched_issues: Sledované úkoly
445 label_related_issues: Související úkoly
446 label_applied_status: Použitý stav
447 label_loading: Nahrávám...
448 label_relation_new: Nová souvislost
449 label_relation_delete: Odstranit souvislost
450 label_relates_to: související s
451 label_duplicates: duplicity
452 label_blocks: bloků
453 label_blocked_by: zablokován
454 label_precedes: předchází
455 label_follows: následuje
456 label_end_to_start: od konce do začátku
457 label_end_to_end: od konce do konce
458 label_start_to_start: od začátku do začátku
459 label_start_to_end: od začátku do konce
460 label_stay_logged_in: Zůstat přihlášený
461 label_disabled: zakázán
462 label_show_completed_versions: Ukázat dokončené verze
463 label_me:
464 label_board: Fórum
465 label_board_new: Nové fórum
466 label_board_plural: Fóra
467 label_topic_plural: Témata
468 label_message_plural: Zprávy
469 label_message_last: Poslední zpráva
470 label_message_new: Nová zpráva
471 label_message_posted: Zpráva přidána
472 label_reply_plural: Odpovědi
473 label_send_information: Zaslat informace o účtu uživateli
474 label_year: Rok
475 label_month: Měsíc
476 label_week: Týden
477 label_date_from: Od
478 label_date_to: Do
479 label_language_based: Podle výchozího jazyku
480 label_sort_by: Seřadit podle %s
481 label_send_test_email: Poslat testovací email
482 label_feeds_access_key_created_on: Přístupový klíč pro RSS byl vytvořen před %s
483 label_module_plural: Moduly
484 label_added_time_by: 'Přidáno uživatelem %s před %s'
485 label_updated_time: 'Aktualizováno před %s'
486 label_jump_to_a_project: Zvolit projekt...
487 label_file_plural: Soubory
488 label_changeset_plural: Changesety
489 label_default_columns: Výchozí sloupce
490 label_no_change_option: (beze změny)
491 label_bulk_edit_selected_issues: Bulk edit selected issues
492 label_theme: Téma
493 label_default: Výchozí
494 label_search_titles_only: Vyhledávat pouze v názvech
495 label_user_mail_option_all: "Pro všechny události všech mých projektů"
496 label_user_mail_option_selected: "Pro všechny události vybraných projektů..."
497 label_user_mail_option_none: "Pouze pro události které sleduji nebo které se mne týkají"
498 label_user_mail_no_self_notified: "Nezasílat informace o mnou vytvořených změnách"
499 label_registration_activation_by_email: aktivace účtu emailem
500 label_registration_manual_activation: manuální aktivace účtu
501 label_registration_automatic_activation: automatická aktivace účtu
502 label_display_per_page: '%s na stránku'
503 label_age: Věk
504 label_change_properties: Změnit vlastnosti
505 label_general: Obecné
506 label_more: Více
507 label_scm: SCM
508 label_plugins: Doplňky
509 label_ldap_authentication: Autentifikace LDAP
510 label_downloads_abbr: D/L
511 label_optional_description: Volitelný popis
512 label_add_another_file: Přidat další soubor
513 label_preferences: Nastavení
514 label_chronological_order: V chronologickém pořadí
515 label_reverse_chronological_order: V obrácaném chronologickém pořadí
516
517 button_login: Přihlásit
518 button_submit: Potvrdit
519 button_save: Uložit
520 button_check_all: Zašrtnout vše
521 button_uncheck_all: Odšrtnout vše
522 button_delete: Odstranit
523 button_create: Vytvořit
524 button_test: Test
525 button_edit: Upravit
526 button_add: Přidat
527 button_change: Změnit
528 button_apply: Použít
529 button_clear: Smazat
530 button_lock: Zamknout
531 button_unlock: Odemknout
532 button_download: Stáhnout
533 button_list: Vypsat
534 button_view: Zobrazit
535 button_move: Přesunout
536 button_back: Zpět
537 button_cancel: Storno
538 button_activate: Aktivovat
539 button_sort: Seřadit
540 button_log_time: Přidat čas
541 button_rollback: Zpět k této verzi
542 button_watch: Sledovat
543 button_unwatch: Nesledovat
544 button_reply: Odpovědět
545 button_archive: Archivovat
546 button_unarchive: Odarchivovat
547 button_reset: Reset
548 button_rename: Přejmenovat
549 button_change_password: Změnit heslo
550 button_copy: Kopírovat
551 button_annotate: Komentovat
552 button_update: Aktualizovat
553 button_configure: Konfigurovat
554
555 status_active: aktivní
556 status_registered: registrovaný
557 status_locked: uzamčený
558
559 text_select_mail_notifications: Vyberte akci při které bude zasláno upozornění emailem.
560 text_regexp_info: např. ^[A-Z0-9]+$
561 text_min_max_length_info: 0 znamená bez limitu
562 text_project_destroy_confirmation: Jste si jisti, že chcete odstranit tento projekt a všechna související data ?
563 text_workflow_edit: Vyberte roli a frontu k editaci workflow
564 text_are_you_sure: Jste si jisti?
565 text_journal_changed: změněno z %s na %s
566 text_journal_set_to: nastaveno na %s
567 text_journal_deleted: odstraněno
568 text_tip_task_begin_day: úkol začíná v tento den
569 text_tip_task_end_day: úkol končí v tento den
570 text_tip_task_begin_end_day: úkol začíná a končí v tento den
571 text_project_identifier_info: 'Jsou povolena malá písmena (a-z), čísla a pomlčky.<br />Po uložení již není možné identifikátor změnit.'
572 text_caracters_maximum: %d znaků maximálně.
573 text_caracters_minimum: Musí být alespoň %d znaků dlouhé.
574 text_length_between: Délka mezi %d a %d znaky.
575 text_tracker_no_workflow: Pro tuto frontu není definován žádný workflow
576 text_unallowed_characters: Nepovolené znaky
577 text_comma_separated: Povoleno více hodnot (oddělěné čárkou).
578 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
579 text_issue_added: Úkol %s byl vytvořen uživatelem %s.
580 text_issue_updated: Úkol %s byl aktualizován uživatelem %s.
581 text_wiki_destroy_confirmation: Opravdu si přejete odstranit tuto WIKI a celý její obsah?
582 text_issue_category_destroy_question: Některé úkoly (%d) jsou přiřazeny k této kategorii. Co s nimi chtete udělat?
583 text_issue_category_destroy_assignments: Zrušit přiřazení ke kategorii
584 text_issue_category_reassign_to: Přiřadit úkoly do této kategorie
585 text_user_mail_option: "U projektů, které nebyly vybrány, budete dostávat oznámení pouze o vašich či o sledovaných položkách (např. o položkách jejichž jste autor nebo ke kterým jste přiřazen(a))."
586 text_no_configuration_data: "Role, fronty, stavy úkolů ani workflow nebyly zatím nakonfigurovány.\nVelice doporučujeme nahrát výchozí konfiguraci.Po si můžete vše upravit"
587 text_load_default_configuration: Nahrát výchozí konfiguraci
588 text_status_changed_by_changeset: Použito v changesetu %s.
589 text_issues_destroy_confirmation: 'Opravdu si přejete odstranit všechny zvolené úkoly?'
590 text_select_project_modules: 'Aktivní moduly v tomto projektu:'
591 text_default_administrator_account_changed: Výchozí nastavení administrátorského účtu změněno
592 text_file_repository_writable: Povolen zápis do repository
593 text_rmagick_available: RMagick k dispozici (volitelné)
594 text_destroy_time_entries_question: U úkolů, které chcete odstranit je evidováno %.02f práce. Co chete udělat?
595 text_destroy_time_entries: Odstranit evidované hodiny.
596 text_assign_time_entries_to_project: Přiřadit evidované hodiny projektu
597 text_reassign_time_entries: 'Přeřadit evidované hodiny k tomuto úkolu:'
598
599 default_role_manager: Manažer
600 default_role_developper: Vývojář
601 default_role_reporter: Reportér
602 default_tracker_bug: Chyba
603 default_tracker_feature: Požadavek
604 default_tracker_support: Podpora
605 default_issue_status_new: Nový
606 default_issue_status_assigned: Přiřazený
607 default_issue_status_resolved: Vyřešený
608 default_issue_status_feedback: Čeká se
609 default_issue_status_closed: Uzavřený
610 default_issue_status_rejected: Odmítnutý
611 default_doc_category_user: Uživatelská dokumentace
612 default_doc_category_tech: Technická dokumentace
613 default_priority_low: Nízká
614 default_priority_normal: Normální
615 default_priority_high: Vysoká
616 default_priority_urgent: Urgentní
617 default_priority_immediate: Okamžitá
618 default_activity_design: Design
619 default_activity_development: Vývoj
620
621 enumeration_issue_priorities: Priority úkolů
622 enumeration_doc_categories: Kategorie dokumentů
623 enumeration_activities: Aktivity (sledování času)
624 error_scm_annotate: "Položka neexistuje nebo nemůže být komentována."
625 label_planning: Plánování
626 text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.'
627 label_and_its_subprojects: %s and its subprojects
628 mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:"
629 mail_subject_reminder: "%d issue(s) due in the next days"
630 text_user_wrote: '%s wrote:'
631 label_duplicated_by: duplicated by
632 setting_enabled_scm: Enabled SCM
633 text_enumeration_category_reassign_to: 'Reassign them to this value:'
634 text_enumeration_destroy_question: '%d objects are assigned to this value.'
635 label_incoming_emails: Incoming emails
636 label_generate_key: Generate a key
637 setting_mail_handler_api_enabled: Enable WS for incoming emails
638 setting_mail_handler_api_key: API key
639 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
640 field_parent_title: Parent page
641 label_issue_watchers: Watchers
642 setting_commit_logs_encoding: Commit messages encoding
643 button_quote: Quote
644 setting_sequential_project_identifiers: Generate sequential project identifiers
645 notice_unable_delete_version: Unable to delete version
646 label_renamed: renamed
647 label_copied: copied
648 setting_plain_text_mail: plain text only (no HTML)
649 permission_view_files: View files
650 permission_edit_issues: Edit issues
651 permission_edit_own_time_entries: Edit own time logs
652 permission_manage_public_queries: Manage public queries
653 permission_add_issues: Add issues
654 permission_log_time: Log spent time
655 permission_view_changesets: View changesets
656 permission_view_time_entries: View spent time
657 permission_manage_versions: Manage versions
658 permission_manage_wiki: Manage wiki
659 permission_manage_categories: Manage issue categories
660 permission_protect_wiki_pages: Protect wiki pages
661 permission_comment_news: Comment news
662 permission_delete_messages: Delete messages
663 permission_select_project_modules: Select project modules
664 permission_manage_documents: Manage documents
665 permission_edit_wiki_pages: Edit wiki pages
666 permission_add_issue_watchers: Add watchers
667 permission_view_gantt: View gantt chart
668 permission_move_issues: Move issues
669 permission_manage_issue_relations: Manage issue relations
670 permission_delete_wiki_pages: Delete wiki pages
671 permission_manage_boards: Manage boards
672 permission_delete_wiki_pages_attachments: Delete attachments
673 permission_view_wiki_edits: View wiki history
674 permission_add_messages: Post messages
675 permission_view_messages: View messages
676 permission_manage_files: Manage files
677 permission_edit_issue_notes: Edit notes
678 permission_manage_news: Manage news
679 permission_view_calendar: View calendrier
680 permission_manage_members: Manage members
681 permission_edit_messages: Edit messages
682 permission_delete_issues: Delete issues
683 permission_view_issue_watchers: View watchers list
684 permission_manage_repository: Manage repository
685 permission_commit_access: Commit access
686 permission_browse_repository: Browse repository
687 permission_view_documents: View documents
688 permission_edit_project: Edit project
689 permission_add_issue_notes: Add notes
690 permission_save_queries: Save queries
691 permission_view_wiki_pages: View wiki
692 permission_rename_wiki_pages: Rename wiki pages
693 permission_edit_time_entries: Edit time logs
694 permission_edit_own_issue_notes: Edit own notes
695 setting_gravatar_enabled: Use Gravatar user icons
696 label_example: Example
697 text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
698 permission_edit_own_messages: Edit own messages
699 permission_delete_own_messages: Delete own messages
700 label_user_activity: "%s's activity"
701 label_updated_time_by: Updated by %s %s ago
702 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
703 setting_diff_max_lines_displayed: Max number of diff lines displayed
704 text_plugin_assets_writable: Plugin assets directory writable
705 warning_attachments_not_saved: "%d file(s) could not be saved."
706 button_create_and_continue: Create and continue
707 text_custom_field_possible_values_info: 'One line for each value'
708 label_display: Display
709 field_editable: Editable
710 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
711 field_identity_url: OpenID URL
712 setting_openid: Allow OpenID login and registration
713 label_login_with_open_id_option: or login with OpenID
714 field_watcher: Watcher
95 # CZ translation by Maxim Krušina | Massimo Filippi, s.r.o. | maxim@mxm.cz
96 # Based on original CZ translation by Jan Kadleček
97
98 actionview_instancetag_blank_option: Prosím vyberte
99
100 general_text_No: 'Ne'
101 general_text_Yes: 'Ano'
102 general_text_no: 'ne'
103 general_text_yes: 'ano'
104 general_lang_name: 'Čeština'
105 general_csv_separator: ','
106 general_csv_decimal_separator: '.'
107 general_csv_encoding: UTF-8
108 general_pdf_encoding: UTF-8
109 general_first_day_of_week: '1'
110
111 notice_account_updated: Účet byl úspěšně změněn.
112 notice_account_invalid_creditentials: Chybné jméno nebo heslo
113 notice_account_password_updated: Heslo bylo úspěšně změněno.
114 notice_account_wrong_password: Chybné heslo
115 notice_account_register_done: Účet byl úspěšně vytvořen. Pro aktivaci účtu klikněte na odkaz v emailu, který vám byl zaslán.
116 notice_account_unknown_email: Neznámý uživatel.
117 notice_can_t_change_password: Tento účet používá externí autentifikaci. Zde heslo změnit nemůžete.
118 notice_account_lost_email_sent: Byl vám zaslán email s intrukcemi jak si nastavíte nové heslo.
119 notice_account_activated: Váš účet byl aktivován. Nyní se můžete přihlásit.
120 notice_successful_create: Úspěšně vytvořeno.
121 notice_successful_update: Úspěšně aktualizováno.
122 notice_successful_delete: Úspěšně odstraněno.
123 notice_successful_connection: Úspěšné připojení.
124 notice_file_not_found: Stránka na kterou se snažíte zobrazit neexistuje nebo byla smazána.
125 notice_locking_conflict: Údaje byly změněny jiným uživatelem.
126 notice_scm_error: Entry and/or revision doesn't exist in the repository.
127 notice_not_authorized: Nemáte dostatečná práva pro zobrazení této stránky.
128 notice_email_sent: "Na adresu {{value}} byl odeslán email"
129 notice_email_error: "Při odesílání emailu nastala chyba ({{value}})"
130 notice_feeds_access_key_reseted: Váš klíč pro přístup k RSS byl resetován.
131 notice_failed_to_save_issues: "Failed to save {{count}} issue(s) on {{total}} selected: {{ids}}."
132 notice_no_issue_selected: "Nebyl zvolen žádný úkol. Prosím, zvolte úkoly, které chcete editovat"
133 notice_account_pending: "Váš účet byl vytvořen, nyní čeká na schválení administrátorem."
134 notice_default_data_loaded: Výchozí konfigurace úspěšně nahrána.
135
136 error_can_t_load_default_data: "Výchozí konfigurace nebyla nahrána: {{value}}"
137 error_scm_not_found: "Položka a/nebo revize neexistují v repository."
138 error_scm_command_failed: "Při pokusu o přístup k repository došlo k chybě: {{value}}"
139 error_issue_not_found_in_project: 'Úkol nebyl nalezen nebo nepatří k tomuto projektu'
140
141 mail_subject_lost_password: "Vaše heslo ({{value}})"
142 mail_body_lost_password: 'Pro změnu vašeho hesla klikněte na následující odkaz:'
143 mail_subject_register: "Aktivace účtu ({{value}})"
144 mail_body_register: 'Pro aktivaci vašeho účtu klikněte na následující odkaz:'
145 mail_body_account_information_external: "Pomocí vašeho účtu {{value}} se můžete přihlásit."
146 mail_body_account_information: Informace o vašem účtu
147 mail_subject_account_activation_request: "Aktivace {{value}} účtu"
148 mail_body_account_activation_request: "Byl zaregistrován nový uživatel {{value}}. Aktivace jeho účtu závisí na vašem potvrzení."
149
150 gui_validation_error: 1 chyba
151 gui_validation_error_plural: "{{count}} chyb(y)"
152
153 field_name: Název
154 field_description: Popis
155 field_summary: Přehled
156 field_is_required: Povinné pole
157 field_firstname: Jméno
158 field_lastname: Příjmení
159 field_mail: Email
160 field_filename: Soubor
161 field_filesize: Velikost
162 field_downloads: Staženo
163 field_author: Autor
164 field_created_on: Vytvořeno
165 field_updated_on: Aktualizováno
166 field_field_format: Formát
167 field_is_for_all: Pro všechny projekty
168 field_possible_values: Možné hodnoty
169 field_regexp: Regulární výraz
170 field_min_length: Minimální délka
171 field_max_length: Maximální délka
172 field_value: Hodnota
173 field_category: Kategorie
174 field_title: Název
175 field_project: Projekt
176 field_issue: Úkol
177 field_status: Stav
178 field_notes: Poznámka
179 field_is_closed: Úkol uzavřen
180 field_is_default: Výchozí stav
181 field_tracker: Fronta
182 field_subject: Předmět
183 field_due_date: Uzavřít do
184 field_assigned_to: Přiřazeno
185 field_priority: Priorita
186 field_fixed_version: Přiřazeno k verzi
187 field_user: Uživatel
188 field_role: Role
189 field_homepage: Homepage
190 field_is_public: Veřejný
191 field_parent: Nadřazený projekt
192 field_is_in_chlog: Úkoly zobrazené v změnovém logu
193 field_is_in_roadmap: Úkoly zobrazené v plánu
194 field_login: Přihlášení
195 field_mail_notification: Emailová oznámení
196 field_admin: Administrátor
197 field_last_login_on: Poslední přihlášení
198 field_language: Jazyk
199 field_effective_date: Datum
200 field_password: Heslo
201 field_new_password: Nové heslo
202 field_password_confirmation: Potvrzení
203 field_version: Verze
204 field_type: Typ
205 field_host: Host
206 field_port: Port
207 field_account: Účet
208 field_base_dn: Base DN
209 field_attr_login: Přihlášení (atribut)
210 field_attr_firstname: Jméno (atribut)
211 field_attr_lastname: Příjemní (atribut)
212 field_attr_mail: Email (atribut)
213 field_onthefly: Automatické vytváření uživatelů
214 field_start_date: Začátek
215 field_done_ratio: %% Hotovo
216 field_auth_source: Autentifikační mód
217 field_hide_mail: Nezobrazovat můj email
218 field_comments: Komentář
219 field_url: URL
220 field_start_page: Výchozí stránka
221 field_subproject: Podprojekt
222 field_hours: Hodiny
223 field_activity: Aktivita
224 field_spent_on: Datum
225 field_identifier: Identifikátor
226 field_is_filter: Použít jako filtr
227 field_issue_to_id: Související úkol
228 field_delay: Zpoždění
229 field_assignable: Úkoly mohou být přiřazeny této roli
230 field_redirect_existing_links: Přesměrovat stvávající odkazy
231 field_estimated_hours: Odhadovaná doba
232 field_column_names: Sloupce
233 field_time_zone: Časové pásmo
234 field_searchable: Umožnit vyhledávání
235 field_default_value: Výchozí hodnota
236 field_comments_sorting: Zobrazit komentáře
237
238 setting_app_title: Název aplikace
239 setting_app_subtitle: Podtitulek aplikace
240 setting_welcome_text: Uvítací text
241 setting_default_language: Výchozí jazyk
242 setting_login_required: Auten. vyžadována
243 setting_self_registration: Povolena automatická registrace
244 setting_attachment_max_size: Maximální velikost přílohy
245 setting_issues_export_limit: Limit pro export úkolů
246 setting_mail_from: Odesílat emaily z adresy
247 setting_bcc_recipients: Příjemci skryté kopie (bcc)
248 setting_host_name: Host name
249 setting_text_formatting: Formátování textu
250 setting_wiki_compression: Komperese historie Wiki
251 setting_feeds_limit: Feed content limit
252 setting_default_projects_public: Nové projekty nastavovat jako veřejné
253 setting_autofetch_changesets: Autofetch commits
254 setting_sys_api_enabled: Povolit WS pro správu repozitory
255 setting_commit_ref_keywords: Klíčová slova pro odkazy
256 setting_commit_fix_keywords: Klíčová slova pro uzavření
257 setting_autologin: Automatické přihlašování
258 setting_date_format: Formát data
259 setting_time_format: Formát času
260 setting_cross_project_issue_relations: Povolit vazby úkolů napříč projekty
261 setting_issue_list_default_columns: Výchozí sloupce zobrazené v seznamu úkolů
262 setting_repositories_encodings: Kódování
263 setting_emails_footer: Patička emailů
264 setting_protocol: Protokol
265 setting_per_page_options: Povolené počty řádků na stránce
266 setting_user_format: Formát zobrazení uživatele
267 setting_activity_days_default: Days displayed on project activity
268 setting_display_subprojects_issues: Display subprojects issues on main projects by default
269
270 project_module_issue_tracking: Sledování úkolů
271 project_module_time_tracking: Sledování času
272 project_module_news: Novinky
273 project_module_documents: Dokumenty
274 project_module_files: Soubory
275 project_module_wiki: Wiki
276 project_module_repository: Repository
277 project_module_boards: Diskuse
278
279 label_user: Uživatel
280 label_user_plural: Uživatelé
281 label_user_new: Nový uživatel
282 label_project: Projekt
283 label_project_new: Nový projekt
284 label_project_plural: Projekty
285 label_x_projects:
286 zero: no projects
287 one: 1 project
288 other: "{{count}} projects"
289 label_project_all: Všechny projekty
290 label_project_latest: Poslední projekty
291 label_issue: Úkol
292 label_issue_new: Nový úkol
293 label_issue_plural: Úkoly
294 label_issue_view_all: Všechny úkoly
295 label_issues_by: "Úkoly od uživatele {{value}}"
296 label_issue_added: Úkol přidán
297 label_issue_updated: Úkol aktualizován
298 label_document: Dokument
299 label_document_new: Nový dokument
300 label_document_plural: Dokumenty
301 label_document_added: Dokument přidán
302 label_role: Role
303 label_role_plural: Role
304 label_role_new: Nová role
305 label_role_and_permissions: Role a práva
306 label_member: Člen
307 label_member_new: Nový člen
308 label_member_plural: Členové
309 label_tracker: Fronta
310 label_tracker_plural: Fronty
311 label_tracker_new: Nová fronta
312 label_workflow: Workflow
313 label_issue_status: Stav úkolu
314 label_issue_status_plural: Stavy úkolů
315 label_issue_status_new: Nový stav
316 label_issue_category: Kategorie úkolu
317 label_issue_category_plural: Kategorie úkolů
318 label_issue_category_new: Nová kategorie
319 label_custom_field: Uživatelské pole
320 label_custom_field_plural: Uživatelská pole
321 label_custom_field_new: Nové uživatelské pole
322 label_enumerations: Seznamy
323 label_enumeration_new: Nová hodnota
324 label_information: Informace
325 label_information_plural: Informace
326 label_please_login: Prosím přihlašte se
327 label_register: Registrovat
328 label_password_lost: Zapomenuté heslo
329 label_home: Úvodní
330 label_my_page: Moje stránka
331 label_my_account: Můj účet
332 label_my_projects: Moje projekty
333 label_administration: Administrace
334 label_login: Přihlášení
335 label_logout: Odhlášení
336 label_help: Nápověda
337 label_reported_issues: Nahlášené úkoly
338 label_assigned_to_me_issues: Mé úkoly
339 label_last_login: Poslední přihlášení
340 label_registered_on: Registrován
341 label_activity: Aktivita
342 label_overall_activity: Celková aktivita
343 label_new: Nový
344 label_logged_as: Přihlášen jako
345 label_environment: Prostředí
346 label_authentication: Autentifikace
347 label_auth_source: Mód autentifikace
348 label_auth_source_new: Nový mód autentifikace
349 label_auth_source_plural: Módy autentifikace
350 label_subproject_plural: Podprojekty
351 label_min_max_length: Min - Max délka
352 label_list: Seznam
353 label_date: Datum
354 label_integer: Celé číslo
355 label_float: Desetiné číslo
356 label_boolean: Ano/Ne
357 label_string: Text
358 label_text: Dlouhý text
359 label_attribute: Atribut
360 label_attribute_plural: Atributy
361 label_download: "{{count}} Download"
362 label_download_plural: "{{count}} Downloads"
363 label_no_data: Žádné položky
364 label_change_status: Změnit stav
365 label_history: Historie
366 label_attachment: Soubor
367 label_attachment_new: Nový soubor
368 label_attachment_delete: Odstranit soubor
369 label_attachment_plural: Soubory
370 label_file_added: Soubor přidán
371 label_report: Přeheled
372 label_report_plural: Přehledy
373 label_news: Novinky
374 label_news_new: Přidat novinku
375 label_news_plural: Novinky
376 label_news_latest: Poslední novinky
377 label_news_view_all: Zobrazit všechny novinky
378 label_news_added: Novinka přidána
379 label_change_log: Protokol změn
380 label_settings: Nastavení
381 label_overview: Přehled
382 label_version: Verze
383 label_version_new: Nová verze
384 label_version_plural: Verze
385 label_confirmation: Potvrzení
386 label_export_to: 'Také k dispozici:'
387 label_read: Načítá se...
388 label_public_projects: Veřejné projekty
389 label_open_issues: otevřený
390 label_open_issues_plural: otevřené
391 label_closed_issues: uzavřený
392 label_closed_issues_plural: uzavřené
393 label_x_open_issues_abbr_on_total:
394 zero: 0 open / {{total}}
395 one: 1 open / {{total}}
396 other: "{{count}} open / {{total}}"
397 label_x_open_issues_abbr:
398 zero: 0 open
399 one: 1 open
400 other: "{{count}} open"
401 label_x_closed_issues_abbr:
402 zero: 0 closed
403 one: 1 closed
404 other: "{{count}} closed"
405 label_total: Celkem
406 label_permissions: Práva
407 label_current_status: Aktuální stav
408 label_new_statuses_allowed: Nové povolené stavy
409 label_all: vše
410 label_none: nic
411 label_nobody: nikdo
412 label_next: Další
413 label_previous: Předchozí
414 label_used_by: Použito
415 label_details: Detaily
416 label_add_note: Přidat poznámku
417 label_per_page: Na stránku
418 label_calendar: Kalendář
419 label_months_from: měsíců od
420 label_gantt: Ganttův graf
421 label_internal: Interní
422 label_last_changes: "posledních {{count}} změn"
423 label_change_view_all: Zobrazit všechny změny
424 label_personalize_page: Přizpůsobit tuto stránku
425 label_comment: Komentář
426 label_comment_plural: Komentáře
427 label_x_comments:
428 zero: no comments
429 one: 1 comment
430 other: "{{count}} comments"
431 label_comment_add: Přidat komentáře
432 label_comment_added: Komentář přidán
433 label_comment_delete: Odstranit komentář
434 label_query: Uživatelský dotaz
435 label_query_plural: Uživatelské dotazy
436 label_query_new: Nový dotaz
437 label_filter_add: Přidat filtr
438 label_filter_plural: Filtry
439 label_equals: je
440 label_not_equals: není
441 label_in_less_than: je měší než
442 label_in_more_than: je větší než
443 label_in: v
444 label_today: dnes
445 label_all_time: vše
446 label_yesterday: včera
447 label_this_week: tento týden
448 label_last_week: minulý týden
449 label_last_n_days: "posledních {{count}} dnů"
450 label_this_month: tento měsíc
451 label_last_month: minulý měsíc
452 label_this_year: tento rok
453 label_date_range: Časový rozsah
454 label_less_than_ago: před méně jak (dny)
455 label_more_than_ago: před více jak (dny)
456 label_ago: před (dny)
457 label_contains: obsahuje
458 label_not_contains: neobsahuje
459 label_day_plural: dny
460 label_repository: Repository
461 label_repository_plural: Repository
462 label_browse: Procházet
463 label_modification: "{{count}} změna"
464 label_modification_plural: "{{count}} změn"
465 label_revision: Revize
466 label_revision_plural: Revizí
467 label_associated_revisions: Související verze
468 label_added: přidáno
469 label_modified: změněno
470 label_deleted: odstraněno
471 label_latest_revision: Poslední revize
472 label_latest_revision_plural: Poslední revize
473 label_view_revisions: Zobrazit revize
474 label_max_size: Maximální velikost
475 label_sort_highest: Přesunout na začátek
476 label_sort_higher: Přesunout nahoru
477 label_sort_lower: Přesunout dolů
478 label_sort_lowest: Přesunout na konec
479 label_roadmap: Plán
480 label_roadmap_due_in: "Zbývá {{value}}"
481 label_roadmap_overdue: "{{value}} pozdě"
482 label_roadmap_no_issues: Pro tuto verzi nejsou žádné úkoly
483 label_search: Hledat
484 label_result_plural: Výsledky
485 label_all_words: Všechna slova
486 label_wiki: Wiki
487 label_wiki_edit: Wiki úprava
488 label_wiki_edit_plural: Wiki úpravy
489 label_wiki_page: Wiki stránka
490 label_wiki_page_plural: Wiki stránky
491 label_index_by_title: Index dle názvu
492 label_index_by_date: Index dle data
493 label_current_version: Aktuální verze
494 label_preview: Náhled
495 label_feed_plural: Příspěvky
496 label_changes_details: Detail všech změn
497 label_issue_tracking: Sledování úkolů
498 label_spent_time: Strávený čas
499 label_f_hour: "{{value}} hodina"
500 label_f_hour_plural: "{{value}} hodin"
501 label_time_tracking: Sledování času
502 label_change_plural: Změny
503 label_statistics: Statistiky
504 label_commits_per_month: Commitů za měsíc
505 label_commits_per_author: Commitů za autora
506 label_view_diff: Zobrazit rozdíly
507 label_diff_inline: uvnitř
508 label_diff_side_by_side: vedle sebe
509 label_options: Nastavení
510 label_copy_workflow_from: Kopírovat workflow z
511 label_permissions_report: Přehled práv
512 label_watched_issues: Sledované úkoly
513 label_related_issues: Související úkoly
514 label_applied_status: Použitý stav
515 label_loading: Nahrávám...
516 label_relation_new: Nová souvislost
517 label_relation_delete: Odstranit souvislost
518 label_relates_to: související s
519 label_duplicates: duplicity
520 label_blocks: bloků
521 label_blocked_by: zablokován
522 label_precedes: předchází
523 label_follows: následuje
524 label_end_to_start: od konce do začátku
525 label_end_to_end: od konce do konce
526 label_start_to_start: od začátku do začátku
527 label_start_to_end: od začátku do konce
528 label_stay_logged_in: Zůstat přihlášený
529 label_disabled: zakázán
530 label_show_completed_versions: Ukázat dokončené verze
531 label_me:
532 label_board: Fórum
533 label_board_new: Nové fórum
534 label_board_plural: Fóra
535 label_topic_plural: Témata
536 label_message_plural: Zprávy
537 label_message_last: Poslední zpráva
538 label_message_new: Nová zpráva
539 label_message_posted: Zpráva přidána
540 label_reply_plural: Odpovědi
541 label_send_information: Zaslat informace o účtu uživateli
542 label_year: Rok
543 label_month: Měsíc
544 label_week: Týden
545 label_date_from: Od
546 label_date_to: Do
547 label_language_based: Podle výchozího jazyku
548 label_sort_by: "Seřadit podle {{value}}"
549 label_send_test_email: Poslat testovací email
550 label_feeds_access_key_created_on: "Přístupový klíč pro RSS byl vytvořen před {{value}}"
551 label_module_plural: Moduly
552 label_added_time_by: "'Přidáno uživatelem {{author}} před {{age}}'"
553 label_updated_time: "Aktualizováno před {{value}}'"
554 label_jump_to_a_project: Zvolit projekt...
555 label_file_plural: Soubory
556 label_changeset_plural: Changesety
557 label_default_columns: Výchozí sloupce
558 label_no_change_option: (beze změny)
559 label_bulk_edit_selected_issues: Bulk edit selected issues
560 label_theme: Téma
561 label_default: Výchozí
562 label_search_titles_only: Vyhledávat pouze v názvech
563 label_user_mail_option_all: "Pro všechny události všech mých projektů"
564 label_user_mail_option_selected: "Pro všechny události vybraných projektů..."
565 label_user_mail_option_none: "Pouze pro události které sleduji nebo které se mne týkají"
566 label_user_mail_no_self_notified: "Nezasílat informace o mnou vytvořených změnách"
567 label_registration_activation_by_email: aktivace účtu emailem
568 label_registration_manual_activation: manuální aktivace účtu
569 label_registration_automatic_activation: automatická aktivace účtu
570 label_display_per_page: "{{value}} na stránku'"
571 label_age: Věk
572 label_change_properties: Změnit vlastnosti
573 label_general: Obecné
574 label_more: Více
575 label_scm: SCM
576 label_plugins: Doplňky
577 label_ldap_authentication: Autentifikace LDAP
578 label_downloads_abbr: D/L
579 label_optional_description: Volitelný popis
580 label_add_another_file: Přidat další soubor
581 label_preferences: Nastavení
582 label_chronological_order: V chronologickém pořadí
583 label_reverse_chronological_order: V obrácaném chronologickém pořadí
584
585 button_login: Přihlásit
586 button_submit: Potvrdit
587 button_save: Uložit
588 button_check_all: Zašrtnout vše
589 button_uncheck_all: Odšrtnout vše
590 button_delete: Odstranit
591 button_create: Vytvořit
592 button_test: Test
593 button_edit: Upravit
594 button_add: Přidat
595 button_change: Změnit
596 button_apply: Použít
597 button_clear: Smazat
598 button_lock: Zamknout
599 button_unlock: Odemknout
600 button_download: Stáhnout
601 button_list: Vypsat
602 button_view: Zobrazit
603 button_move: Přesunout
604 button_back: Zpět
605 button_cancel: Storno
606 button_activate: Aktivovat
607 button_sort: Seřadit
608 button_log_time: Přidat čas
609 button_rollback: Zpět k této verzi
610 button_watch: Sledovat
611 button_unwatch: Nesledovat
612 button_reply: Odpovědět
613 button_archive: Archivovat
614 button_unarchive: Odarchivovat
615 button_reset: Reset
616 button_rename: Přejmenovat
617 button_change_password: Změnit heslo
618 button_copy: Kopírovat
619 button_annotate: Komentovat
620 button_update: Aktualizovat
621 button_configure: Konfigurovat
622
623 status_active: aktivní
624 status_registered: registrovaný
625 status_locked: uzamčený
626
627 text_select_mail_notifications: Vyberte akci při které bude zasláno upozornění emailem.
628 text_regexp_info: např. ^[A-Z0-9]+$
629 text_min_max_length_info: 0 znamená bez limitu
630 text_project_destroy_confirmation: Jste si jisti, že chcete odstranit tento projekt a všechna související data ?
631 text_workflow_edit: Vyberte roli a frontu k editaci workflow
632 text_are_you_sure: Jste si jisti?
633 text_journal_changed: "změněno z {{old}} na {{new}}"
634 text_journal_set_to: "nastaveno na {{value}}"
635 text_journal_deleted: odstraněno
636 text_tip_task_begin_day: úkol začíná v tento den
637 text_tip_task_end_day: úkol končí v tento den
638 text_tip_task_begin_end_day: úkol začíná a končí v tento den
639 text_project_identifier_info: 'Jsou povolena malá písmena (a-z), čísla a pomlčky.<br />Po uložení již není možné identifikátor změnit.'
640 text_caracters_maximum: "{{count}} znaků maximálně."
641 text_caracters_minimum: "Musí být alespoň {{count}} znaků dlouhé."
642 text_length_between: "Délka mezi {{min}} a {{max}} znaky."
643 text_tracker_no_workflow: Pro tuto frontu není definován žádný workflow
644 text_unallowed_characters: Nepovolené znaky
645 text_comma_separated: Povoleno více hodnot (oddělěné čárkou).
646 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
647 text_issue_added: "Úkol {{id}} byl vytvořen uživatelem {{author}}."
648 text_issue_updated: "Úkol {{id}} byl aktualizován uživatelem {{author}}."
649 text_wiki_destroy_confirmation: Opravdu si přejete odstranit tuto WIKI a celý její obsah?
650 text_issue_category_destroy_question: "Některé úkoly ({{count}}) jsou přiřazeny k této kategorii. Co s nimi chtete udělat?"
651 text_issue_category_destroy_assignments: Zrušit přiřazení ke kategorii
652 text_issue_category_reassign_to: Přiřadit úkoly do této kategorie
653 text_user_mail_option: "U projektů, které nebyly vybrány, budete dostávat oznámení pouze o vašich či o sledovaných položkách (např. o položkách jejichž jste autor nebo ke kterým jste přiřazen(a))."
654 text_no_configuration_data: "Role, fronty, stavy úkolů ani workflow nebyly zatím nakonfigurovány.\nVelice doporučujeme nahrát výchozí konfiguraci.Po si můžete vše upravit"
655 text_load_default_configuration: Nahrát výchozí konfiguraci
656 text_status_changed_by_changeset: "Použito v changesetu {{value}}."
657 text_issues_destroy_confirmation: 'Opravdu si přejete odstranit všechny zvolené úkoly?'
658 text_select_project_modules: 'Aktivní moduly v tomto projektu:'
659 text_default_administrator_account_changed: Výchozí nastavení administrátorského účtu změněno
660 text_file_repository_writable: Povolen zápis do repository
661 text_rmagick_available: RMagick k dispozici (volitelné)
662 text_destroy_time_entries_question: U úkolů, které chcete odstranit je evidováno %.02f práce. Co chete udělat?
663 text_destroy_time_entries: Odstranit evidované hodiny.
664 text_assign_time_entries_to_project: Přiřadit evidované hodiny projektu
665 text_reassign_time_entries: 'Přeřadit evidované hodiny k tomuto úkolu:'
666
667 default_role_manager: Manažer
668 default_role_developper: Vývojář
669 default_role_reporter: Reportér
670 default_tracker_bug: Chyba
671 default_tracker_feature: Požadavek
672 default_tracker_support: Podpora
673 default_issue_status_new: Nový
674 default_issue_status_assigned: Přiřazený
675 default_issue_status_resolved: Vyřešený
676 default_issue_status_feedback: Čeká se
677 default_issue_status_closed: Uzavřený
678 default_issue_status_rejected: Odmítnutý
679 default_doc_category_user: Uživatelská dokumentace
680 default_doc_category_tech: Technická dokumentace
681 default_priority_low: Nízká
682 default_priority_normal: Normální
683 default_priority_high: Vysoká
684 default_priority_urgent: Urgentní
685 default_priority_immediate: Okamžitá
686 default_activity_design: Design
687 default_activity_development: Vývoj
688
689 enumeration_issue_priorities: Priority úkolů
690 enumeration_doc_categories: Kategorie dokumentů
691 enumeration_activities: Aktivity (sledování času)
692 error_scm_annotate: "Položka neexistuje nebo nemůže být komentována."
693 label_planning: Plánování
694 text_subprojects_destroy_warning: "Its subproject(s): {{value}} will be also deleted.'"
695 label_and_its_subprojects: "{{value}} and its subprojects"
696 mail_body_reminder: "{{count}} issue(s) that are assigned to you are due in the next {{days}} days:"
697 mail_subject_reminder: "{{count}} issue(s) due in the next days"
698 text_user_wrote: "{{value}} wrote:'"
699 label_duplicated_by: duplicated by
700 setting_enabled_scm: Enabled SCM
701 text_enumeration_category_reassign_to: 'Reassign them to this value:'
702 text_enumeration_destroy_question: "{{count}} objects are assigned to this value.'"
703 label_incoming_emails: Incoming emails
704 label_generate_key: Generate a key
705 setting_mail_handler_api_enabled: Enable WS for incoming emails
706 setting_mail_handler_api_key: API key
707 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
708 field_parent_title: Parent page
709 label_issue_watchers: Watchers
710 setting_commit_logs_encoding: Commit messages encoding
711 button_quote: Quote
712 setting_sequential_project_identifiers: Generate sequential project identifiers
713 notice_unable_delete_version: Unable to delete version
714 label_renamed: renamed
715 label_copied: copied
716 setting_plain_text_mail: plain text only (no HTML)
717 permission_view_files: View files
718 permission_edit_issues: Edit issues
719 permission_edit_own_time_entries: Edit own time logs
720 permission_manage_public_queries: Manage public queries
721 permission_add_issues: Add issues
722 permission_log_time: Log spent time
723 permission_view_changesets: View changesets
724 permission_view_time_entries: View spent time
725 permission_manage_versions: Manage versions
726 permission_manage_wiki: Manage wiki
727 permission_manage_categories: Manage issue categories
728 permission_protect_wiki_pages: Protect wiki pages
729 permission_comment_news: Comment news
730 permission_delete_messages: Delete messages
731 permission_select_project_modules: Select project modules
732 permission_manage_documents: Manage documents
733 permission_edit_wiki_pages: Edit wiki pages
734 permission_add_issue_watchers: Add watchers
735 permission_view_gantt: View gantt chart
736 permission_move_issues: Move issues
737 permission_manage_issue_relations: Manage issue relations
738 permission_delete_wiki_pages: Delete wiki pages
739 permission_manage_boards: Manage boards
740 permission_delete_wiki_pages_attachments: Delete attachments
741 permission_view_wiki_edits: View wiki history
742 permission_add_messages: Post messages
743 permission_view_messages: View messages
744 permission_manage_files: Manage files
745 permission_edit_issue_notes: Edit notes
746 permission_manage_news: Manage news
747 permission_view_calendar: View calendrier
748 permission_manage_members: Manage members
749 permission_edit_messages: Edit messages
750 permission_delete_issues: Delete issues
751 permission_view_issue_watchers: View watchers list
752 permission_manage_repository: Manage repository
753 permission_commit_access: Commit access
754 permission_browse_repository: Browse repository
755 permission_view_documents: View documents
756 permission_edit_project: Edit project
757 permission_add_issue_notes: Add notes
758 permission_save_queries: Save queries
759 permission_view_wiki_pages: View wiki
760 permission_rename_wiki_pages: Rename wiki pages
761 permission_edit_time_entries: Edit time logs
762 permission_edit_own_issue_notes: Edit own notes
763 setting_gravatar_enabled: Use Gravatar user icons
764 label_example: Example
765 text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
766 permission_edit_own_messages: Edit own messages
767 permission_delete_own_messages: Delete own messages
768 label_user_activity: "{{value}}'s activity"
769 label_updated_time_by: "Updated by {{author}} {{age}} ago"
770 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
771 setting_diff_max_lines_displayed: Max number of diff lines displayed
772 text_plugin_assets_writable: Plugin assets directory writable
773 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
774 button_create_and_continue: Create and continue
775 text_custom_field_possible_values_info: 'One line for each value'
776 label_display: Display
777 field_editable: Editable
778 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
1 NO CONTENT: file renamed from lang/da.yml to config/locales/da.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/de.yml to config/locales/de.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/en.yml to config/locales/en.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/fi.yml to config/locales/fi.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/fr.yml to config/locales/fr.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/he.yml to config/locales/he.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/hu.yml to config/locales/hu.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/it.yml to config/locales/it.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/ja.yml to config/locales/ja.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/lt.yml to config/locales/lt.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/nl.yml to config/locales/nl.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/no.yml to config/locales/no.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/pl.yml to config/locales/pl.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/pt-br.yml to config/locales/pt-BR.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/pt.yml to config/locales/pt.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/ro.yml to config/locales/ro.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/ru.yml to config/locales/ru.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/sk.yml to config/locales/sk.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/sl.yml to config/locales/sl.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/sr.yml to config/locales/sr.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/th.yml to config/locales/th.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/tr.yml to config/locales/tr.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/zh-tw.yml to config/locales/zh-TW.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from lang/zh.yml to config/locales/zh.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from extra/sample_plugin/lang/en.yml to extra/sample_plugin/config/locales/en.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from extra/sample_plugin/lang/fr.yml to extra/sample_plugin/config/locales/fr.yml
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from public/javascripts/calendar/lang/calendar-vn.js to public/javascripts/calendar/lang/calendar-vi.js
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from public/javascripts/jstoolbar/lang/jstoolbar-vn.js to public/javascripts/jstoolbar/lang/jstoolbar-vi.js
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now