##// END OF EJS Templates
Image attachments are now sent inline to be viewed directly in the browser....
Jean-Philippe Lang -
r636:38e0c237a448
parent child
Show More
@@ -1,44 +1,39
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class AttachmentsController < ApplicationController
18 class AttachmentsController < ApplicationController
19 layout 'base'
19 layout 'base'
20 before_filter :find_project, :check_project_privacy
20 before_filter :find_project, :check_project_privacy
21
21
22 # sends an attachment
23 def download
22 def download
24 send_file @attachment.diskfile, :filename => @attachment.filename
23 # images are sent inline
25 rescue
24 send_file @attachment.diskfile, :filename => @attachment.filename,
26 render_404
25 :type => @attachment.content_type,
27 end
26 :disposition => (@attachment.image? ? 'inline' : 'attachment')
28
29 # sends an image to be displayed inline
30 def show
31 render(:nothing => true, :status => 404) and return unless @attachment.diskfile =~ /\.(jpeg|jpg|gif|png)$/i
32 send_file @attachment.diskfile, :filename => @attachment.filename, :type => "image/#{$1}", :disposition => 'inline'
33 rescue
27 rescue
28 # in case the disk file was deleted
34 render_404
29 render_404
35 end
30 end
36
31
37 private
32 private
38 def find_project
33 def find_project
39 @attachment = Attachment.find(params[:id])
34 @attachment = Attachment.find(params[:id])
40 @project = @attachment.project
35 @project = @attachment.project
41 rescue
36 rescue
42 render_404
37 render_404
43 end
38 end
44 end
39 end
@@ -1,290 +1,290
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class RedCloth
18 class RedCloth
19 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
19 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
20 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
20 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
21 def hard_break( text )
21 def hard_break( text )
22 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
22 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
23 end
23 end
24 end
24 end
25
25
26 module ApplicationHelper
26 module ApplicationHelper
27
27
28 # Return current logged in user or nil
28 # Return current logged in user or nil
29 def loggedin?
29 def loggedin?
30 @logged_in_user
30 @logged_in_user
31 end
31 end
32
32
33 # Return true if user is logged in and is admin, otherwise false
33 # Return true if user is logged in and is admin, otherwise false
34 def admin_loggedin?
34 def admin_loggedin?
35 @logged_in_user and @logged_in_user.admin?
35 @logged_in_user and @logged_in_user.admin?
36 end
36 end
37
37
38 # Return true if user is authorized for controller/action, otherwise false
38 # Return true if user is authorized for controller/action, otherwise false
39 def authorize_for(controller, action)
39 def authorize_for(controller, action)
40 # check if action is allowed on public projects
40 # check if action is allowed on public projects
41 if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ controller, action ]
41 if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ controller, action ]
42 return true
42 return true
43 end
43 end
44 # check if user is authorized
44 # check if user is authorized
45 if @logged_in_user and (@logged_in_user.admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], @logged_in_user.role_for_project(@project) ) )
45 if @logged_in_user and (@logged_in_user.admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], @logged_in_user.role_for_project(@project) ) )
46 return true
46 return true
47 end
47 end
48 return false
48 return false
49 end
49 end
50
50
51 # Display a link if user is authorized
51 # Display a link if user is authorized
52 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
52 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
53 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller], options[:action])
53 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller], options[:action])
54 end
54 end
55
55
56 # Display a link to user's account page
56 # Display a link to user's account page
57 def link_to_user(user)
57 def link_to_user(user)
58 link_to user.name, :controller => 'account', :action => 'show', :id => user
58 link_to user.name, :controller => 'account', :action => 'show', :id => user
59 end
59 end
60
60
61 def link_to_issue(issue)
61 def link_to_issue(issue)
62 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
62 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
63 end
63 end
64
64
65 def toggle_link(name, id, options={})
65 def toggle_link(name, id, options={})
66 onclick = "Element.toggle('#{id}'); "
66 onclick = "Element.toggle('#{id}'); "
67 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
67 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
68 onclick << "return false;"
68 onclick << "return false;"
69 link_to(name, "#", :onclick => onclick)
69 link_to(name, "#", :onclick => onclick)
70 end
70 end
71
71
72 def image_to_function(name, function, html_options = {})
72 def image_to_function(name, function, html_options = {})
73 html_options.symbolize_keys!
73 html_options.symbolize_keys!
74 tag(:input, html_options.merge({
74 tag(:input, html_options.merge({
75 :type => "image", :src => image_path(name),
75 :type => "image", :src => image_path(name),
76 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
76 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
77 }))
77 }))
78 end
78 end
79
79
80 def format_date(date)
80 def format_date(date)
81 return nil unless date
81 return nil unless date
82 @date_format_setting ||= Setting.date_format.to_i
82 @date_format_setting ||= Setting.date_format.to_i
83 @date_format_setting == 0 ? l_date(date) : date.strftime("%Y-%m-%d")
83 @date_format_setting == 0 ? l_date(date) : date.strftime("%Y-%m-%d")
84 end
84 end
85
85
86 def format_time(time)
86 def format_time(time)
87 return nil unless time
87 return nil unless time
88 @date_format_setting ||= Setting.date_format.to_i
88 @date_format_setting ||= Setting.date_format.to_i
89 time = time.to_time if time.is_a?(String)
89 time = time.to_time if time.is_a?(String)
90 @date_format_setting == 0 ? l_datetime(time) : (time.strftime("%Y-%m-%d") + ' ' + l_time(time))
90 @date_format_setting == 0 ? l_datetime(time) : (time.strftime("%Y-%m-%d") + ' ' + l_time(time))
91 end
91 end
92
92
93 def day_name(day)
93 def day_name(day)
94 l(:general_day_names).split(',')[day-1]
94 l(:general_day_names).split(',')[day-1]
95 end
95 end
96
96
97 def month_name(month)
97 def month_name(month)
98 l(:actionview_datehelper_select_month_names).split(',')[month-1]
98 l(:actionview_datehelper_select_month_names).split(',')[month-1]
99 end
99 end
100
100
101 def pagination_links_full(paginator, options={}, html_options={})
101 def pagination_links_full(paginator, options={}, html_options={})
102 page_param = options.delete(:page_param) || :page
102 page_param = options.delete(:page_param) || :page
103
103
104 html = ''
104 html = ''
105 html << link_to_remote(('&#171; ' + l(:label_previous)),
105 html << link_to_remote(('&#171; ' + l(:label_previous)),
106 {:update => "content", :url => options.merge(page_param => paginator.current.previous)},
106 {:update => "content", :url => options.merge(page_param => paginator.current.previous)},
107 {:href => url_for(:params => options.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
107 {:href => url_for(:params => options.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
108
108
109 html << (pagination_links_each(paginator, options) do |n|
109 html << (pagination_links_each(paginator, options) do |n|
110 link_to_remote(n.to_s,
110 link_to_remote(n.to_s,
111 {:url => {:params => options.merge(page_param => n)}, :update => 'content'},
111 {:url => {:params => options.merge(page_param => n)}, :update => 'content'},
112 {:href => url_for(:params => options.merge(page_param => n))})
112 {:href => url_for(:params => options.merge(page_param => n))})
113 end || '')
113 end || '')
114
114
115 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
115 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
116 {:update => "content", :url => options.merge(page_param => paginator.current.next)},
116 {:update => "content", :url => options.merge(page_param => paginator.current.next)},
117 {:href => url_for(:params => options.merge(page_param => paginator.current.next))}) if paginator.current.next
117 {:href => url_for(:params => options.merge(page_param => paginator.current.next))}) if paginator.current.next
118 html
118 html
119 end
119 end
120
120
121 # textilize text according to system settings and RedCloth availability
121 # textilize text according to system settings and RedCloth availability
122 def textilizable(text, options = {})
122 def textilizable(text, options = {})
123 return "" if text.blank?
123 return "" if text.blank?
124
124
125 # different methods for formatting wiki links
125 # different methods for formatting wiki links
126 case options[:wiki_links]
126 case options[:wiki_links]
127 when :local
127 when :local
128 # used for local links to html files
128 # used for local links to html files
129 format_wiki_link = Proc.new {|title| "#{title}.html" }
129 format_wiki_link = Proc.new {|title| "#{title}.html" }
130 when :anchor
130 when :anchor
131 # used for single-file wiki export
131 # used for single-file wiki export
132 format_wiki_link = Proc.new {|title| "##{title}" }
132 format_wiki_link = Proc.new {|title| "##{title}" }
133 else
133 else
134 if @project
134 if @project
135 format_wiki_link = Proc.new {|title| url_for :controller => 'wiki', :action => 'index', :id => @project, :page => title }
135 format_wiki_link = Proc.new {|title| url_for :controller => 'wiki', :action => 'index', :id => @project, :page => title }
136 else
136 else
137 format_wiki_link = Proc.new {|title| title }
137 format_wiki_link = Proc.new {|title| title }
138 end
138 end
139 end
139 end
140
140
141 # turn wiki links into textile links:
141 # turn wiki links into textile links:
142 # example:
142 # example:
143 # [[link]] -> "link":link
143 # [[link]] -> "link":link
144 # [[link|title]] -> "title":link
144 # [[link|title]] -> "title":link
145 text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) {|m| link_to(($3 || $1), format_wiki_link.call(Wiki.titleize($1)), :class => 'wiki-page') }
145 text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) {|m| link_to(($3 || $1), format_wiki_link.call(Wiki.titleize($1)), :class => 'wiki-page') }
146
146
147 # turn issue ids into links
147 # turn issue ids into links
148 # example:
148 # example:
149 # #52 -> <a href="/issues/show/52">#52</a>
149 # #52 -> <a href="/issues/show/52">#52</a>
150 text = text.gsub(/#(\d+)(?=\b)/) {|m| link_to "##{$1}", {:controller => 'issues', :action => 'show', :id => $1}, :class => 'issue' }
150 text = text.gsub(/#(\d+)(?=\b)/) {|m| link_to "##{$1}", {:controller => 'issues', :action => 'show', :id => $1}, :class => 'issue' }
151
151
152 # turn revision ids into links (@project needed)
152 # turn revision ids into links (@project needed)
153 # example:
153 # example:
154 # r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (@project.id is 6)
154 # r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (@project.id is 6)
155 text = text.gsub(/(?=\b)r(\d+)(?=\b)/) {|m| link_to "r#{$1}", {:controller => 'repositories', :action => 'revision', :id => @project.id, :rev => $1}, :class => 'changeset' } if @project
155 text = text.gsub(/(?=\b)r(\d+)(?=\b)/) {|m| link_to "r#{$1}", {:controller => 'repositories', :action => 'revision', :id => @project.id, :rev => $1}, :class => 'changeset' } if @project
156
156
157 # when using an image link, try to use an attachment, if possible
157 # when using an image link, try to use an attachment, if possible
158 attachments = options[:attachments]
158 attachments = options[:attachments]
159 if attachments
159 if attachments
160 text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
160 text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
161 align = $1
161 align = $1
162 filename = $2
162 filename = $2
163 rf = Regexp.new(filename, Regexp::IGNORECASE)
163 rf = Regexp.new(filename, Regexp::IGNORECASE)
164 # search for the picture in attachments
164 # search for the picture in attachments
165 if found = attachments.detect { |att| att.filename =~ rf }
165 if found = attachments.detect { |att| att.filename =~ rf }
166 image_url = url_for :controller => 'attachments', :action => 'show', :id => found.id
166 image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id
167 "!#{align}#{image_url}!"
167 "!#{align}#{image_url}!"
168 else
168 else
169 "!#{align}#{filename}!"
169 "!#{align}#{filename}!"
170 end
170 end
171 end
171 end
172 end
172 end
173
173
174 # finally textilize text
174 # finally textilize text
175 @do_textilize ||= (Setting.text_formatting == 'textile') && (ActionView::Helpers::TextHelper.method_defined? "textilize")
175 @do_textilize ||= (Setting.text_formatting == 'textile') && (ActionView::Helpers::TextHelper.method_defined? "textilize")
176 text = @do_textilize ? auto_link(RedCloth.new(text, [:hard_breaks]).to_html) : simple_format(auto_link(h(text)))
176 text = @do_textilize ? auto_link(RedCloth.new(text, [:hard_breaks]).to_html) : simple_format(auto_link(h(text)))
177 end
177 end
178
178
179 # Same as Rails' simple_format helper without using paragraphs
179 # Same as Rails' simple_format helper without using paragraphs
180 def simple_format_without_paragraph(text)
180 def simple_format_without_paragraph(text)
181 text.to_s.
181 text.to_s.
182 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
182 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
183 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
183 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
184 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
184 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
185 end
185 end
186
186
187 def error_messages_for(object_name, options = {})
187 def error_messages_for(object_name, options = {})
188 options = options.symbolize_keys
188 options = options.symbolize_keys
189 object = instance_variable_get("@#{object_name}")
189 object = instance_variable_get("@#{object_name}")
190 if object && !object.errors.empty?
190 if object && !object.errors.empty?
191 # build full_messages here with controller current language
191 # build full_messages here with controller current language
192 full_messages = []
192 full_messages = []
193 object.errors.each do |attr, msg|
193 object.errors.each do |attr, msg|
194 next if msg.nil?
194 next if msg.nil?
195 msg = msg.first if msg.is_a? Array
195 msg = msg.first if msg.is_a? Array
196 if attr == "base"
196 if attr == "base"
197 full_messages << l(msg)
197 full_messages << l(msg)
198 else
198 else
199 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
199 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
200 end
200 end
201 end
201 end
202 # retrieve custom values error messages
202 # retrieve custom values error messages
203 if object.errors[:custom_values]
203 if object.errors[:custom_values]
204 object.custom_values.each do |v|
204 object.custom_values.each do |v|
205 v.errors.each do |attr, msg|
205 v.errors.each do |attr, msg|
206 next if msg.nil?
206 next if msg.nil?
207 msg = msg.first if msg.is_a? Array
207 msg = msg.first if msg.is_a? Array
208 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
208 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
209 end
209 end
210 end
210 end
211 end
211 end
212 content_tag("div",
212 content_tag("div",
213 content_tag(
213 content_tag(
214 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
214 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
215 ) +
215 ) +
216 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
216 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
217 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
217 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
218 )
218 )
219 else
219 else
220 ""
220 ""
221 end
221 end
222 end
222 end
223
223
224 def lang_options_for_select(blank=true)
224 def lang_options_for_select(blank=true)
225 (blank ? [["(auto)", ""]] : []) +
225 (blank ? [["(auto)", ""]] : []) +
226 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.first <=> y.first }
226 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.first <=> y.first }
227 end
227 end
228
228
229 def label_tag_for(name, option_tags = nil, options = {})
229 def label_tag_for(name, option_tags = nil, options = {})
230 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
230 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
231 content_tag("label", label_text)
231 content_tag("label", label_text)
232 end
232 end
233
233
234 def labelled_tabular_form_for(name, object, options, &proc)
234 def labelled_tabular_form_for(name, object, options, &proc)
235 options[:html] ||= {}
235 options[:html] ||= {}
236 options[:html].store :class, "tabular"
236 options[:html].store :class, "tabular"
237 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
237 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
238 end
238 end
239
239
240 def check_all_links(form_name)
240 def check_all_links(form_name)
241 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
241 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
242 " | " +
242 " | " +
243 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
243 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
244 end
244 end
245
245
246 def calendar_for(field_id)
246 def calendar_for(field_id)
247 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
247 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
248 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
248 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
249 end
249 end
250
250
251 def wikitoolbar_for(field_id)
251 def wikitoolbar_for(field_id)
252 return '' unless Setting.text_formatting == 'textile'
252 return '' unless Setting.text_formatting == 'textile'
253 javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
253 javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
254 end
254 end
255 end
255 end
256
256
257 class TabularFormBuilder < ActionView::Helpers::FormBuilder
257 class TabularFormBuilder < ActionView::Helpers::FormBuilder
258 include GLoc
258 include GLoc
259
259
260 def initialize(object_name, object, template, options, proc)
260 def initialize(object_name, object, template, options, proc)
261 set_language_if_valid options.delete(:lang)
261 set_language_if_valid options.delete(:lang)
262 @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
262 @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
263 end
263 end
264
264
265 (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
265 (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
266 src = <<-END_SRC
266 src = <<-END_SRC
267 def #{selector}(field, options = {})
267 def #{selector}(field, options = {})
268 return super if options.delete :no_label
268 return super if options.delete :no_label
269 label_text = l(options[:label]) if options[:label]
269 label_text = l(options[:label]) if options[:label]
270 label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
270 label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
271 label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
271 label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
272 label = @template.content_tag("label", label_text,
272 label = @template.content_tag("label", label_text,
273 :class => (@object && @object.errors[field] ? "error" : nil),
273 :class => (@object && @object.errors[field] ? "error" : nil),
274 :for => (@object_name.to_s + "_" + field.to_s))
274 :for => (@object_name.to_s + "_" + field.to_s))
275 label + super
275 label + super
276 end
276 end
277 END_SRC
277 END_SRC
278 class_eval src, __FILE__, __LINE__
278 class_eval src, __FILE__, __LINE__
279 end
279 end
280
280
281 def select(field, choices, options = {}, html_options = {})
281 def select(field, choices, options = {}, html_options = {})
282 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
282 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
283 label = @template.content_tag("label", label_text,
283 label = @template.content_tag("label", label_text,
284 :class => (@object && @object.errors[field] ? "error" : nil),
284 :class => (@object && @object.errors[field] ? "error" : nil),
285 :for => (@object_name.to_s + "_" + field.to_s))
285 :for => (@object_name.to_s + "_" + field.to_s))
286 label + super
286 label + super
287 end
287 end
288
288
289 end
289 end
290
290
@@ -1,98 +1,102
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/md5"
18 require "digest/md5"
19
19
20 class Attachment < ActiveRecord::Base
20 class Attachment < ActiveRecord::Base
21 belongs_to :container, :polymorphic => true
21 belongs_to :container, :polymorphic => true
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
23
23
24 validates_presence_of :container, :filename
24 validates_presence_of :container, :filename
25 validates_length_of :filename, :maximum => 255
25 validates_length_of :filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
27
27
28 cattr_accessor :storage_path
28 cattr_accessor :storage_path
29 @@storage_path = "#{RAILS_ROOT}/files"
29 @@storage_path = "#{RAILS_ROOT}/files"
30
30
31 def validate
31 def validate
32 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
32 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
33 end
33 end
34
34
35 def file=(incomming_file)
35 def file=(incomming_file)
36 unless incomming_file.nil?
36 unless incomming_file.nil?
37 @temp_file = incomming_file
37 @temp_file = incomming_file
38 if @temp_file.size > 0
38 if @temp_file.size > 0
39 self.filename = sanitize_filename(@temp_file.original_filename)
39 self.filename = sanitize_filename(@temp_file.original_filename)
40 self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
40 self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
41 self.content_type = @temp_file.content_type.chomp
41 self.content_type = @temp_file.content_type.chomp
42 self.filesize = @temp_file.size
42 self.filesize = @temp_file.size
43 end
43 end
44 end
44 end
45 end
45 end
46
46
47 def file
47 def file
48 nil
48 nil
49 end
49 end
50
50
51 # Copy temp file to its final location
51 # Copy temp file to its final location
52 def before_save
52 def before_save
53 if @temp_file && (@temp_file.size > 0)
53 if @temp_file && (@temp_file.size > 0)
54 logger.debug("saving '#{self.diskfile}'")
54 logger.debug("saving '#{self.diskfile}'")
55 File.open(diskfile, "wb") do |f|
55 File.open(diskfile, "wb") do |f|
56 f.write(@temp_file.read)
56 f.write(@temp_file.read)
57 end
57 end
58 self.digest = Digest::MD5.hexdigest(File.read(diskfile))
58 self.digest = Digest::MD5.hexdigest(File.read(diskfile))
59 end
59 end
60 end
60 end
61
61
62 # Deletes file on the disk
62 # Deletes file on the disk
63 def after_destroy
63 def after_destroy
64 if self.filename?
64 if self.filename?
65 File.delete(diskfile) if File.exist?(diskfile)
65 File.delete(diskfile) if File.exist?(diskfile)
66 end
66 end
67 end
67 end
68
68
69 # Returns file's location on disk
69 # Returns file's location on disk
70 def diskfile
70 def diskfile
71 "#{@@storage_path}/#{self.disk_filename}"
71 "#{@@storage_path}/#{self.disk_filename}"
72 end
72 end
73
73
74 def increment_download
74 def increment_download
75 increment!(:downloads)
75 increment!(:downloads)
76 end
76 end
77
77
78 # returns last created projects
78 # returns last created projects
79 def self.most_downloaded
79 def self.most_downloaded
80 find(:all, :limit => 5, :order => "downloads DESC")
80 find(:all, :limit => 5, :order => "downloads DESC")
81 end
81 end
82
82
83 def project
83 def project
84 container.is_a?(Project) ? container : container.project
84 container.is_a?(Project) ? container : container.project
85 end
85 end
86
86
87 def image?
88 self.filename =~ /\.(jpeg|jpg|gif|png)$/i
89 end
90
87 private
91 private
88 def sanitize_filename(value)
92 def sanitize_filename(value)
89 # get only the filename, not the whole path
93 # get only the filename, not the whole path
90 just_filename = value.gsub(/^.*(\\|\/)/, '')
94 just_filename = value.gsub(/^.*(\\|\/)/, '')
91 # NOTE: File.basename doesn't work right with Windows paths on Unix
95 # NOTE: File.basename doesn't work right with Windows paths on Unix
92 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
96 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
93
97
94 # Finally, replace all non alphanumeric, underscore or periods with underscore
98 # Finally, replace all non alphanumeric, underscore or periods with underscore
95 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
99 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
96 end
100 end
97
101
98 end
102 end
General Comments 0
You need to be logged in to leave comments. Login now