To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
root / app / helpers / application_helper.rb @ 1298:4f746d8966dd
History | View | Annotate | Download (46.5 KB)
| 1 | 909:cbb26bc654de | Chris | # encoding: utf-8
|
|---|---|---|---|
| 2 | #
|
||
| 3 | 37:94944d00e43c | chris | # Redmine - project management software
|
| 4 | 1295:622f24f53b42 | Chris | # Copyright (C) 2006-2013 Jean-Philippe Lang
|
| 5 | 0:513646585e45 | Chris | #
|
| 6 | # This program is free software; you can redistribute it and/or
|
||
| 7 | # modify it under the terms of the GNU General Public License
|
||
| 8 | # as published by the Free Software Foundation; either version 2
|
||
| 9 | # of the License, or (at your option) any later version.
|
||
| 10 | #
|
||
| 11 | # This program is distributed in the hope that it will be useful,
|
||
| 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
| 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
| 14 | # GNU General Public License for more details.
|
||
| 15 | #
|
||
| 16 | # You should have received a copy of the GNU General Public License
|
||
| 17 | # along with this program; if not, write to the Free Software
|
||
| 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
| 19 | |||
| 20 | require 'forwardable'
|
||
| 21 | require 'cgi'
|
||
| 22 | |||
| 23 | module ApplicationHelper |
||
| 24 | include Redmine::WikiFormatting::Macros::Definitions |
||
| 25 | include Redmine::I18n |
||
| 26 | include GravatarHelper::PublicMethods |
||
| 27 | 1295:622f24f53b42 | Chris | include Redmine::Pagination::Helper |
| 28 | 0:513646585e45 | Chris | |
| 29 | extend Forwardable
|
||
| 30 | def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter |
||
| 31 | |||
| 32 | # Return true if user is authorized for controller/action, otherwise false
|
||
| 33 | def authorize_for(controller, action) |
||
| 34 | User.current.allowed_to?({:controller => controller, :action => action}, @project) |
||
| 35 | end
|
||
| 36 | |||
| 37 | # Display a link if user is authorized
|
||
| 38 | 22:40f7cfd4df19 | chris | #
|
| 39 | # @param [String] name Anchor text (passed to link_to)
|
||
| 40 | 37:94944d00e43c | chris | # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
|
| 41 | 22:40f7cfd4df19 | chris | # @param [optional, Hash] html_options Options passed to link_to
|
| 42 | # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
|
||
| 43 | 0:513646585e45 | Chris | def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) |
| 44 | 37:94944d00e43c | chris | link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) |
| 45 | 0:513646585e45 | Chris | end
|
| 46 | |||
| 47 | # Displays a link to user's account page if active
|
||
| 48 | def link_to_user(user, options={}) |
||
| 49 | if user.is_a?(User) |
||
| 50 | name = h(user.name(options[:format]))
|
||
| 51 | 1115:433d4f72a19b | Chris | if user.active? || (User.current.admin? && user.logged?) |
| 52 | link_to name, user_path(user), :class => user.css_classes
|
||
| 53 | 0:513646585e45 | Chris | else
|
| 54 | name |
||
| 55 | end
|
||
| 56 | else
|
||
| 57 | h(user.to_s) |
||
| 58 | end
|
||
| 59 | end
|
||
| 60 | |||
| 61 | # Displays a link to +issue+ with its subject.
|
||
| 62 | # Examples:
|
||
| 63 | 441:cbce1fd3b1b7 | Chris | #
|
| 64 | 0:513646585e45 | Chris | # link_to_issue(issue) # => Defect #6: This is the subject
|
| 65 | # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
|
||
| 66 | # link_to_issue(issue, :subject => false) # => Defect #6
|
||
| 67 | # link_to_issue(issue, :project => true) # => Foo - Defect #6
|
||
| 68 | 1115:433d4f72a19b | Chris | # link_to_issue(issue, :subject => false, :tracker => false) # => #6
|
| 69 | 0:513646585e45 | Chris | #
|
| 70 | def link_to_issue(issue, options={}) |
||
| 71 | title = nil
|
||
| 72 | subject = nil
|
||
| 73 | 1115:433d4f72a19b | Chris | text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}" |
| 74 | 0:513646585e45 | Chris | if options[:subject] == false |
| 75 | title = truncate(issue.subject, :length => 60) |
||
| 76 | else
|
||
| 77 | subject = issue.subject |
||
| 78 | if options[:truncate] |
||
| 79 | subject = truncate(subject, :length => options[:truncate]) |
||
| 80 | end
|
||
| 81 | end
|
||
| 82 | 1115:433d4f72a19b | Chris | s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title |
| 83 | s << h(": #{subject}") if subject |
||
| 84 | s = h("#{issue.project} - ") + s if options[:project] |
||
| 85 | 0:513646585e45 | Chris | s |
| 86 | end
|
||
| 87 | |||
| 88 | # Generates a link to an attachment.
|
||
| 89 | # Options:
|
||
| 90 | # * :text - Link text (default to attachment filename)
|
||
| 91 | # * :download - Force download (default: false)
|
||
| 92 | def link_to_attachment(attachment, options={}) |
||
| 93 | text = options.delete(:text) || attachment.filename
|
||
| 94 | 1295:622f24f53b42 | Chris | route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path |
| 95 | html_options = options.slice!(:only_path)
|
||
| 96 | url = send(route_method, attachment, attachment.filename, options) |
||
| 97 | link_to text, url, html_options |
||
| 98 | 0:513646585e45 | Chris | end
|
| 99 | |||
| 100 | # Generates a link to a SCM revision
|
||
| 101 | # Options:
|
||
| 102 | # * :text - Link text (default to the formatted revision)
|
||
| 103 | 1115:433d4f72a19b | Chris | def link_to_revision(revision, repository, options={}) |
| 104 | if repository.is_a?(Project) |
||
| 105 | repository = repository.repository |
||
| 106 | end
|
||
| 107 | 0:513646585e45 | Chris | text = options.delete(:text) || format_revision(revision)
|
| 108 | 119:8661b858af72 | Chris | rev = revision.respond_to?(:identifier) ? revision.identifier : revision
|
| 109 | 1115:433d4f72a19b | Chris | link_to( |
| 110 | h(text), |
||
| 111 | {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
|
||
| 112 | :title => l(:label_revision_id, format_revision(revision)) |
||
| 113 | ) |
||
| 114 | 0:513646585e45 | Chris | end
|
| 115 | 441:cbce1fd3b1b7 | Chris | |
| 116 | 210:0579821a129a | Chris | # Generates a link to a message
|
| 117 | def link_to_message(message, options={}, html_options = nil) |
||
| 118 | link_to( |
||
| 119 | 1295:622f24f53b42 | Chris | truncate(message.subject, :length => 60), |
| 120 | board_message_path(message.board_id, message.parent_id || message.id, {
|
||
| 121 | 210:0579821a129a | Chris | :r => (message.parent_id && message.id),
|
| 122 | :anchor => (message.parent_id ? "message-#{message.id}" : nil) |
||
| 123 | 1295:622f24f53b42 | Chris | }.merge(options)), |
| 124 | 210:0579821a129a | Chris | html_options |
| 125 | ) |
||
| 126 | end
|
||
| 127 | 0:513646585e45 | Chris | |
| 128 | 14:1d32c0a0efbf | Chris | # Generates a link to a project if active
|
| 129 | # Examples:
|
||
| 130 | 441:cbce1fd3b1b7 | Chris | #
|
| 131 | 14:1d32c0a0efbf | Chris | # link_to_project(project) # => link to the specified project overview
|
| 132 | # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
|
||
| 133 | # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
|
||
| 134 | #
|
||
| 135 | def link_to_project(project, options={}, html_options = nil) |
||
| 136 | 1115:433d4f72a19b | Chris | if project.archived?
|
| 137 | 1295:622f24f53b42 | Chris | h(project.name) |
| 138 | elsif options.key?(:action) |
||
| 139 | ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0." |
||
| 140 | url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
|
||
| 141 | link_to project.name, url, html_options |
||
| 142 | 1115:433d4f72a19b | Chris | else
|
| 143 | 1295:622f24f53b42 | Chris | link_to project.name, project_path(project, options), html_options |
| 144 | end
|
||
| 145 | end
|
||
| 146 | |||
| 147 | # Generates a link to a project settings if active
|
||
| 148 | def link_to_project_settings(project, options={}, html_options=nil) |
||
| 149 | if project.active?
|
||
| 150 | link_to project.name, settings_project_path(project, options), html_options |
||
| 151 | elsif project.archived?
|
||
| 152 | h(project.name) |
||
| 153 | else
|
||
| 154 | link_to project.name, project_path(project, options), html_options |
||
| 155 | 14:1d32c0a0efbf | Chris | end
|
| 156 | end
|
||
| 157 | |||
| 158 | 1115:433d4f72a19b | Chris | def wiki_page_path(page, options={}) |
| 159 | url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
|
||
| 160 | end
|
||
| 161 | |||
| 162 | def thumbnail_tag(attachment) |
||
| 163 | 1295:622f24f53b42 | Chris | link_to image_tag(thumbnail_path(attachment)), |
| 164 | named_attachment_path(attachment, attachment.filename), |
||
| 165 | 1115:433d4f72a19b | Chris | :title => attachment.filename
|
| 166 | end
|
||
| 167 | |||
| 168 | 0:513646585e45 | Chris | def toggle_link(name, id, options={}) |
| 169 | 1115:433d4f72a19b | Chris | onclick = "$('##{id}').toggle(); "
|
| 170 | onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") |
||
| 171 | 0:513646585e45 | Chris | onclick << "return false;"
|
| 172 | link_to(name, "#", :onclick => onclick) |
||
| 173 | end
|
||
| 174 | |||
| 175 | def image_to_function(name, function, html_options = {}) |
||
| 176 | html_options.symbolize_keys! |
||
| 177 | tag(:input, html_options.merge({
|
||
| 178 | :type => "image", :src => image_path(name), |
||
| 179 | :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" |
||
| 180 | })) |
||
| 181 | end
|
||
| 182 | |||
| 183 | def format_activity_title(text) |
||
| 184 | h(truncate_single_line(text, :length => 100)) |
||
| 185 | end
|
||
| 186 | 441:cbce1fd3b1b7 | Chris | |
| 187 | 0:513646585e45 | Chris | def format_activity_day(date) |
| 188 | 1115:433d4f72a19b | Chris | date == User.current.today ? l(:label_today).titleize : format_date(date) |
| 189 | 0:513646585e45 | Chris | end
|
| 190 | 441:cbce1fd3b1b7 | Chris | |
| 191 | 0:513646585e45 | Chris | def format_activity_description(text) |
| 192 | 1115:433d4f72a19b | Chris | h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...') |
| 193 | ).gsub(/[\r\n]+/, "<br />").html_safe |
||
| 194 | 0:513646585e45 | Chris | end
|
| 195 | |||
| 196 | def format_version_name(version) |
||
| 197 | if version.project == @project |
||
| 198 | 1295:622f24f53b42 | Chris | h(version) |
| 199 | 0:513646585e45 | Chris | else
|
| 200 | h("#{version.project} - #{version}")
|
||
| 201 | end
|
||
| 202 | end
|
||
| 203 | 441:cbce1fd3b1b7 | Chris | |
| 204 | 0:513646585e45 | Chris | def due_date_distance_in_words(date) |
| 205 | if date
|
||
| 206 | l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) |
||
| 207 | end
|
||
| 208 | end
|
||
| 209 | |||
| 210 | 1115:433d4f72a19b | Chris | # Renders a tree of projects as a nested set of unordered lists
|
| 211 | # The given collection may be a subset of the whole project tree
|
||
| 212 | # (eg. some intermediate nodes are private and can not be seen)
|
||
| 213 | def render_project_nested_lists(projects) |
||
| 214 | s = ''
|
||
| 215 | if projects.any?
|
||
| 216 | ancestors = [] |
||
| 217 | original_project = @project
|
||
| 218 | projects.sort_by(&:lft).each do |project| |
||
| 219 | # set the project environment to please macros.
|
||
| 220 | @project = project
|
||
| 221 | if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
|
||
| 222 | s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
|
||
| 223 | else
|
||
| 224 | ancestors.pop |
||
| 225 | s << "</li>"
|
||
| 226 | while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
|
||
| 227 | ancestors.pop |
||
| 228 | s << "</ul></li>\n"
|
||
| 229 | end
|
||
| 230 | end
|
||
| 231 | classes = (ancestors.empty? ? 'root' : 'child') |
||
| 232 | s << "<li class='#{classes}'><div class='#{classes}'>"
|
||
| 233 | s << h(block_given? ? yield(project) : project.name)
|
||
| 234 | s << "</div>\n"
|
||
| 235 | ancestors << project |
||
| 236 | end
|
||
| 237 | s << ("</li></ul>\n" * ancestors.size)
|
||
| 238 | @project = original_project
|
||
| 239 | end
|
||
| 240 | s.html_safe |
||
| 241 | end
|
||
| 242 | |||
| 243 | 441:cbce1fd3b1b7 | Chris | def render_page_hierarchy(pages, node=nil, options={}) |
| 244 | 0:513646585e45 | Chris | content = ''
|
| 245 | if pages[node]
|
||
| 246 | content << "<ul class=\"pages-hierarchy\">\n"
|
||
| 247 | pages[node].each do |page|
|
||
| 248 | content << "<li>"
|
||
| 249 | 1115:433d4f72a19b | Chris | content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
|
| 250 | 441:cbce1fd3b1b7 | Chris | :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) |
| 251 | content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] |
||
| 252 | 0:513646585e45 | Chris | content << "</li>\n"
|
| 253 | end
|
||
| 254 | content << "</ul>\n"
|
||
| 255 | end
|
||
| 256 | 909:cbb26bc654de | Chris | content.html_safe |
| 257 | 0:513646585e45 | Chris | end
|
| 258 | 441:cbce1fd3b1b7 | Chris | |
| 259 | 0:513646585e45 | Chris | # Renders flash messages
|
| 260 | def render_flash_messages |
||
| 261 | s = ''
|
||
| 262 | flash.each do |k,v|
|
||
| 263 | 1115:433d4f72a19b | Chris | s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}") |
| 264 | 0:513646585e45 | Chris | end
|
| 265 | 909:cbb26bc654de | Chris | s.html_safe |
| 266 | 0:513646585e45 | Chris | end
|
| 267 | 441:cbce1fd3b1b7 | Chris | |
| 268 | 0:513646585e45 | Chris | # Renders tabs and their content
|
| 269 | def render_tabs(tabs) |
||
| 270 | if tabs.any?
|
||
| 271 | render :partial => 'common/tabs', :locals => {:tabs => tabs} |
||
| 272 | else
|
||
| 273 | content_tag 'p', l(:label_no_data), :class => "nodata" |
||
| 274 | end
|
||
| 275 | end
|
||
| 276 | 441:cbce1fd3b1b7 | Chris | |
| 277 | 0:513646585e45 | Chris | # Renders the project quick-jump box
|
| 278 | def render_project_jump_box |
||
| 279 | 441:cbce1fd3b1b7 | Chris | return unless User.current.logged? |
| 280 | 1115:433d4f72a19b | Chris | projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq |
| 281 | 0:513646585e45 | Chris | if projects.any?
|
| 282 | 1115:433d4f72a19b | Chris | options = |
| 283 | ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
|
||
| 284 | '<option value="" disabled="disabled">---</option>').html_safe
|
||
| 285 | |||
| 286 | options << project_tree_options_for_select(projects, :selected => @project) do |p| |
||
| 287 | { :value => project_path(:id => p, :jump => current_menu_item) }
|
||
| 288 | 0:513646585e45 | Chris | end
|
| 289 | 1115:433d4f72a19b | Chris | |
| 290 | select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }') |
||
| 291 | 0:513646585e45 | Chris | end
|
| 292 | end
|
||
| 293 | 441:cbce1fd3b1b7 | Chris | |
| 294 | 0:513646585e45 | Chris | def project_tree_options_for_select(projects, options = {}) |
| 295 | s = ''
|
||
| 296 | project_tree(projects) do |project, level|
|
||
| 297 | 1115:433d4f72a19b | Chris | name_prefix = (level > 0 ? ' ' * 2 * level + '» ' : '').html_safe |
| 298 | 0:513646585e45 | Chris | tag_options = {:value => project.id}
|
| 299 | if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) |
||
| 300 | tag_options[:selected] = 'selected' |
||
| 301 | else
|
||
| 302 | tag_options[:selected] = nil |
||
| 303 | end
|
||
| 304 | tag_options.merge!(yield(project)) if block_given? |
||
| 305 | s << content_tag('option', name_prefix + h(project), tag_options)
|
||
| 306 | end
|
||
| 307 | 909:cbb26bc654de | Chris | s.html_safe |
| 308 | 0:513646585e45 | Chris | end
|
| 309 | 441:cbce1fd3b1b7 | Chris | |
| 310 | 0:513646585e45 | Chris | # Yields the given block for each project with its level in the tree
|
| 311 | 37:94944d00e43c | chris | #
|
| 312 | # Wrapper for Project#project_tree
|
||
| 313 | 0:513646585e45 | Chris | def project_tree(projects, &block) |
| 314 | 37:94944d00e43c | chris | Project.project_tree(projects, &block)
|
| 315 | 0:513646585e45 | Chris | end
|
| 316 | 441:cbce1fd3b1b7 | Chris | |
| 317 | 0:513646585e45 | Chris | def principals_check_box_tags(name, principals) |
| 318 | s = ''
|
||
| 319 | 1294:3e4c3460b6ca | Chris | principals.sort.each do |principal|
|
| 320 | 1120:55944e901e9e | luis | |
| 321 | if principal.type == "User" |
||
| 322 | 948:83866d58f2dd | luis | s << "<label>#{ check_box_tag name, principal.id, false } #{link_to_user principal}</label>\n"
|
| 323 | else
|
||
| 324 | s << "<label>#{ check_box_tag name, principal.id, false } #{h principal} (Group)</label>\n"
|
||
| 325 | end
|
||
| 326 | 1120:55944e901e9e | luis | |
| 327 | 0:513646585e45 | Chris | end
|
| 328 | 909:cbb26bc654de | Chris | s.html_safe |
| 329 | end
|
||
| 330 | |||
| 331 | # Returns a string for users/groups option tags
|
||
| 332 | def principals_options_for_select(collection, selected=nil) |
||
| 333 | s = ''
|
||
| 334 | 1115:433d4f72a19b | Chris | if collection.include?(User.current) |
| 335 | s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id) |
||
| 336 | end
|
||
| 337 | 909:cbb26bc654de | Chris | groups = ''
|
| 338 | collection.sort.each do |element|
|
||
| 339 | selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) |
||
| 340 | (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>) |
||
| 341 | end
|
||
| 342 | unless groups.empty?
|
||
| 343 | s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
|
||
| 344 | end
|
||
| 345 | 1115:433d4f72a19b | Chris | s.html_safe |
| 346 | end
|
||
| 347 | |||
| 348 | # Options for the new membership projects combo-box
|
||
| 349 | def options_for_membership_project_select(principal, projects) |
||
| 350 | options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") |
||
| 351 | options << project_tree_options_for_select(projects) do |p|
|
||
| 352 | 1295:622f24f53b42 | Chris | {:disabled => principal.projects.to_a.include?(p)}
|
| 353 | 1115:433d4f72a19b | Chris | end
|
| 354 | options |
||
| 355 | 0:513646585e45 | Chris | end
|
| 356 | |||
| 357 | # Truncates and returns the string as a single line
|
||
| 358 | def truncate_single_line(string, *args) |
||
| 359 | truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') |
||
| 360 | end
|
||
| 361 | 441:cbce1fd3b1b7 | Chris | |
| 362 | 0:513646585e45 | Chris | # Truncates at line break after 250 characters or options[:length]
|
| 363 | def truncate_lines(string, options={}) |
||
| 364 | length = options[:length] || 250 |
||
| 365 | if string.to_s =~ /\A(.{#{length}}.*?)$/m |
||
| 366 | "#{$1}..."
|
||
| 367 | else
|
||
| 368 | string |
||
| 369 | end
|
||
| 370 | end
|
||
| 371 | |||
| 372 | 1115:433d4f72a19b | Chris | def anchor(text) |
| 373 | text.to_s.gsub(' ', '_') |
||
| 374 | end
|
||
| 375 | |||
| 376 | 0:513646585e45 | Chris | def html_hours(text) |
| 377 | 909:cbb26bc654de | Chris | text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe |
| 378 | 0:513646585e45 | Chris | end
|
| 379 | |||
| 380 | def authoring(created, author, options={}) |
||
| 381 | 909:cbb26bc654de | Chris | l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe |
| 382 | 0:513646585e45 | Chris | end
|
| 383 | 441:cbce1fd3b1b7 | Chris | |
| 384 | 0:513646585e45 | Chris | def time_tag(time) |
| 385 | text = distance_of_time_in_words(Time.now, time)
|
||
| 386 | if @project |
||
| 387 | 1115:433d4f72a19b | Chris | link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
|
| 388 | 0:513646585e45 | Chris | else
|
| 389 | content_tag('acronym', text, :title => format_time(time)) |
||
| 390 | end
|
||
| 391 | end
|
||
| 392 | |||
| 393 | 1115:433d4f72a19b | Chris | def syntax_highlight_lines(name, content) |
| 394 | lines = [] |
||
| 395 | syntax_highlight(name, content).each_line { |line| lines << line }
|
||
| 396 | lines |
||
| 397 | end
|
||
| 398 | |||
| 399 | 0:513646585e45 | Chris | def syntax_highlight(name, content) |
| 400 | Redmine::SyntaxHighlighting.highlight_by_filename(content, name) |
||
| 401 | end
|
||
| 402 | |||
| 403 | def to_path_param(path) |
||
| 404 | 1115:433d4f72a19b | Chris | str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/") |
| 405 | str.blank? ? nil : str
|
||
| 406 | 0:513646585e45 | Chris | end
|
| 407 | |||
| 408 | 909:cbb26bc654de | Chris | def reorder_links(name, url, method = :post) |
| 409 | link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), |
||
| 410 | url.merge({"#{name}[move_to]" => 'highest'}),
|
||
| 411 | :method => method, :title => l(:label_sort_highest)) + |
||
| 412 | link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), |
||
| 413 | url.merge({"#{name}[move_to]" => 'higher'}),
|
||
| 414 | :method => method, :title => l(:label_sort_higher)) + |
||
| 415 | link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), |
||
| 416 | url.merge({"#{name}[move_to]" => 'lower'}),
|
||
| 417 | :method => method, :title => l(:label_sort_lower)) + |
||
| 418 | link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), |
||
| 419 | url.merge({"#{name}[move_to]" => 'lowest'}),
|
||
| 420 | :method => method, :title => l(:label_sort_lowest)) |
||
| 421 | 0:513646585e45 | Chris | end
|
| 422 | |||
| 423 | def breadcrumb(*args) |
||
| 424 | elements = args.flatten |
||
| 425 | 909:cbb26bc654de | Chris | elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil |
| 426 | 0:513646585e45 | Chris | end
|
| 427 | 441:cbce1fd3b1b7 | Chris | |
| 428 | 0:513646585e45 | Chris | def other_formats_links(&block) |
| 429 | 909:cbb26bc654de | Chris | concat('<p class="other-formats">'.html_safe + l(:label_export_to)) |
| 430 | 0:513646585e45 | Chris | yield Redmine::Views::OtherFormatsBuilder.new(self) |
| 431 | 909:cbb26bc654de | Chris | concat('</p>'.html_safe)
|
| 432 | 0:513646585e45 | Chris | end
|
| 433 | 441:cbce1fd3b1b7 | Chris | |
| 434 | 0:513646585e45 | Chris | def page_header_title |
| 435 | if @project.nil? || @project.new_record? |
||
| 436 | 144:09910262eb0b | luisf | a = [h(Setting.app_title), ''] |
| 437 | |||
| 438 | 0:513646585e45 | Chris | else
|
| 439 | 144:09910262eb0b | luisf | pname = [] |
| 440 | 0:513646585e45 | Chris | b = [] |
| 441 | 441:cbce1fd3b1b7 | Chris | ancestors = (@project.root? ? [] : @project.ancestors.visible.all) |
| 442 | 0:513646585e45 | Chris | if ancestors.any?
|
| 443 | root = ancestors.shift |
||
| 444 | 14:1d32c0a0efbf | Chris | b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
|
| 445 | 0:513646585e45 | Chris | if ancestors.size > 2 |
| 446 | 1120:55944e901e9e | luis | b << '…'
|
| 447 | 0:513646585e45 | Chris | ancestors = ancestors[-2, 2] |
| 448 | end
|
||
| 449 | 14:1d32c0a0efbf | Chris | b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
|
| 450 | 1152:5d01a630b843 | chris | b = b.join(' » ').html_safe
|
| 451 | b << (' »'.html_safe)
|
||
| 452 | 0:513646585e45 | Chris | end
|
| 453 | 144:09910262eb0b | luisf | |
| 454 | pname << h(@project)
|
||
| 455 | |||
| 456 | 1152:5d01a630b843 | chris | a = [pname, b] |
| 457 | 144:09910262eb0b | luisf | |
| 458 | 0:513646585e45 | Chris | end
|
| 459 | end
|
||
| 460 | |||
| 461 | def html_title(*args) |
||
| 462 | if args.empty?
|
||
| 463 | 909:cbb26bc654de | Chris | title = @html_title || []
|
| 464 | 0:513646585e45 | Chris | title << @project.name if @project |
| 465 | 909:cbb26bc654de | Chris | title << Setting.app_title unless Setting.app_title == title.last |
| 466 | 0:513646585e45 | Chris | title.select {|t| !t.blank? }.join(' - ')
|
| 467 | else
|
||
| 468 | @html_title ||= []
|
||
| 469 | @html_title += args
|
||
| 470 | end
|
||
| 471 | end
|
||
| 472 | |||
| 473 | 14:1d32c0a0efbf | Chris | # Returns the theme, controller name, and action as css classes for the
|
| 474 | # HTML body.
|
||
| 475 | def body_css_classes |
||
| 476 | css = [] |
||
| 477 | if theme = Redmine::Themes.theme(Setting.ui_theme) |
||
| 478 | css << 'theme-' + theme.name
|
||
| 479 | end
|
||
| 480 | |||
| 481 | 1115:433d4f72a19b | Chris | css << 'controller-' + controller_name
|
| 482 | css << 'action-' + action_name
|
||
| 483 | 14:1d32c0a0efbf | Chris | css.join(' ')
|
| 484 | end
|
||
| 485 | |||
| 486 | 0:513646585e45 | Chris | def accesskey(s) |
| 487 | 1295:622f24f53b42 | Chris | @used_accesskeys ||= []
|
| 488 | key = Redmine::AccessKeys.key_for(s) |
||
| 489 | return nil if @used_accesskeys.include?(key) |
||
| 490 | @used_accesskeys << key
|
||
| 491 | key |
||
| 492 | 0:513646585e45 | Chris | end
|
| 493 | |||
| 494 | # Formats text according to system settings.
|
||
| 495 | # 2 ways to call this method:
|
||
| 496 | # * with a String: textilizable(text, options)
|
||
| 497 | # * with an object and one of its attribute: textilizable(issue, :description, options)
|
||
| 498 | def textilizable(*args) |
||
| 499 | options = args.last.is_a?(Hash) ? args.pop : {}
|
||
| 500 | case args.size
|
||
| 501 | when 1 |
||
| 502 | obj = options[:object]
|
||
| 503 | text = args.shift |
||
| 504 | when 2 |
||
| 505 | obj = args.shift |
||
| 506 | attr = args.shift |
||
| 507 | text = obj.send(attr).to_s |
||
| 508 | else
|
||
| 509 | raise ArgumentError, 'invalid arguments to textilizable' |
||
| 510 | end
|
||
| 511 | return '' if text.blank? |
||
| 512 | project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) |
||
| 513 | only_path = options.delete(:only_path) == false ? false : true |
||
| 514 | |||
| 515 | 1115:433d4f72a19b | Chris | text = text.dup |
| 516 | macros = catch_macros(text) |
||
| 517 | 909:cbb26bc654de | Chris | text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) |
| 518 | 441:cbce1fd3b1b7 | Chris | |
| 519 | 119:8661b858af72 | Chris | @parsed_headings = []
|
| 520 | 929:5f33065ddc4b | Chris | @heading_anchors = {}
|
| 521 | 909:cbb26bc654de | Chris | @current_section = 0 if options[:edit_section_links] |
| 522 | 929:5f33065ddc4b | Chris | |
| 523 | parse_sections(text, project, obj, attr, only_path, options) |
||
| 524 | 1115:433d4f72a19b | Chris | text = parse_non_pre_blocks(text, obj, macros) do |text|
|
| 525 | [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name| |
||
| 526 | 0:513646585e45 | Chris | send method_name, text, project, obj, attr, only_path, options |
| 527 | end
|
||
| 528 | end
|
||
| 529 | 929:5f33065ddc4b | Chris | parse_headings(text, project, obj, attr, only_path, options) |
| 530 | 441:cbce1fd3b1b7 | Chris | |
| 531 | 119:8661b858af72 | Chris | if @parsed_headings.any? |
| 532 | replace_toc(text, @parsed_headings)
|
||
| 533 | end
|
||
| 534 | 441:cbce1fd3b1b7 | Chris | |
| 535 | 1115:433d4f72a19b | Chris | text.html_safe |
| 536 | 0:513646585e45 | Chris | end
|
| 537 | 441:cbce1fd3b1b7 | Chris | |
| 538 | 1115:433d4f72a19b | Chris | def parse_non_pre_blocks(text, obj, macros) |
| 539 | 0:513646585e45 | Chris | s = StringScanner.new(text)
|
| 540 | tags = [] |
||
| 541 | parsed = ''
|
||
| 542 | while !s.eos?
|
||
| 543 | s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
|
||
| 544 | text, full_tag, closing, tag = s[1], s[2], s[3], s[4] |
||
| 545 | if tags.empty?
|
||
| 546 | yield text
|
||
| 547 | 1115:433d4f72a19b | Chris | inject_macros(text, obj, macros) if macros.any?
|
| 548 | else
|
||
| 549 | inject_macros(text, obj, macros, false) if macros.any? |
||
| 550 | 0:513646585e45 | Chris | end
|
| 551 | parsed << text |
||
| 552 | if tag
|
||
| 553 | if closing
|
||
| 554 | if tags.last == tag.downcase
|
||
| 555 | tags.pop |
||
| 556 | end
|
||
| 557 | else
|
||
| 558 | tags << tag.downcase |
||
| 559 | end
|
||
| 560 | parsed << full_tag |
||
| 561 | end
|
||
| 562 | end
|
||
| 563 | # Close any non closing tags
|
||
| 564 | while tag = tags.pop
|
||
| 565 | parsed << "</#{tag}>"
|
||
| 566 | end
|
||
| 567 | 1115:433d4f72a19b | Chris | parsed |
| 568 | 0:513646585e45 | Chris | end
|
| 569 | 441:cbce1fd3b1b7 | Chris | |
| 570 | 0:513646585e45 | Chris | def parse_inline_attachments(text, project, obj, attr, only_path, options) |
| 571 | # when using an image link, try to use an attachment, if possible
|
||
| 572 | 1294:3e4c3460b6ca | Chris | attachments = options[:attachments] || []
|
| 573 | attachments += obj.attachments if obj.respond_to?(:attachments) |
||
| 574 | if attachments.present?
|
||
| 575 | 909:cbb26bc654de | Chris | text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| |
| 576 | 441:cbce1fd3b1b7 | Chris | filename, ext, alt, alttext = $1.downcase, $2, $3, $4 |
| 577 | 0:513646585e45 | Chris | # search for the picture in attachments
|
| 578 | 909:cbb26bc654de | Chris | if found = Attachment.latest_attach(attachments, filename) |
| 579 | 1295:622f24f53b42 | Chris | image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
|
| 580 | 0:513646585e45 | Chris | desc = found.description.to_s.gsub('"', '') |
| 581 | if !desc.blank? && alttext.blank?
|
||
| 582 | alt = " title=\"#{desc}\" alt=\"#{desc}\""
|
||
| 583 | end
|
||
| 584 | 1115:433d4f72a19b | Chris | "src=\"#{image_url}\"#{alt}"
|
| 585 | 0:513646585e45 | Chris | else
|
| 586 | 1115:433d4f72a19b | Chris | m |
| 587 | 0:513646585e45 | Chris | end
|
| 588 | end
|
||
| 589 | end
|
||
| 590 | end
|
||
| 591 | |||
| 592 | # Wiki links
|
||
| 593 | #
|
||
| 594 | # Examples:
|
||
| 595 | # [[mypage]]
|
||
| 596 | # [[mypage|mytext]]
|
||
| 597 | # wiki links can refer other project wikis, using project name or identifier:
|
||
| 598 | # [[project:]] -> wiki starting page
|
||
| 599 | # [[project:|mytext]]
|
||
| 600 | # [[project:mypage]]
|
||
| 601 | # [[project:mypage|mytext]]
|
||
| 602 | def parse_wiki_links(text, project, obj, attr, only_path, options) |
||
| 603 | text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m| |
||
| 604 | link_project = project |
||
| 605 | esc, all, page, title = $1, $2, $3, $5 |
||
| 606 | if esc.nil?
|
||
| 607 | if page =~ /^([^\:]+)\:(.*)$/ |
||
| 608 | 1295:622f24f53b42 | Chris | identifier, page = $1, $2 |
| 609 | link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier) |
||
| 610 | title ||= identifier if page.blank?
|
||
| 611 | 0:513646585e45 | Chris | end
|
| 612 | |||
| 613 | if link_project && link_project.wiki
|
||
| 614 | # extract anchor
|
||
| 615 | anchor = nil
|
||
| 616 | if page =~ /^(.+?)\#(.+)$/ |
||
| 617 | page, anchor = $1, $2 |
||
| 618 | end
|
||
| 619 | 909:cbb26bc654de | Chris | anchor = sanitize_anchor_name(anchor) if anchor.present?
|
| 620 | 0:513646585e45 | Chris | # check if page exists
|
| 621 | wiki_page = link_project.wiki.find_page(page) |
||
| 622 | 909:cbb26bc654de | Chris | url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page |
| 623 | "##{anchor}"
|
||
| 624 | else
|
||
| 625 | case options[:wiki_links] |
||
| 626 | when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') |
||
| 627 | when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export |
||
| 628 | 0:513646585e45 | Chris | else
|
| 629 | 37:94944d00e43c | chris | wiki_page_id = page.present? ? Wiki.titleize(page) : nil |
| 630 | 1115:433d4f72a19b | Chris | parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil |
| 631 | 1120:55944e901e9e | luis | url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, |
| 632 | 1115:433d4f72a19b | Chris | :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent) |
| 633 | 0:513646585e45 | Chris | end
|
| 634 | 909:cbb26bc654de | Chris | end
|
| 635 | link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) |
||
| 636 | 0:513646585e45 | Chris | else
|
| 637 | # project or wiki doesn't exist
|
||
| 638 | 1115:433d4f72a19b | Chris | all |
| 639 | 0:513646585e45 | Chris | end
|
| 640 | else
|
||
| 641 | 1115:433d4f72a19b | Chris | all |
| 642 | 0:513646585e45 | Chris | end
|
| 643 | end
|
||
| 644 | end
|
||
| 645 | 441:cbce1fd3b1b7 | Chris | |
| 646 | 0:513646585e45 | Chris | # Redmine links
|
| 647 | #
|
||
| 648 | # Examples:
|
||
| 649 | # Issues:
|
||
| 650 | # #52 -> Link to issue #52
|
||
| 651 | # Changesets:
|
||
| 652 | # r52 -> Link to revision 52
|
||
| 653 | # commit:a85130f -> Link to scmid starting with a85130f
|
||
| 654 | # Documents:
|
||
| 655 | # document#17 -> Link to document with id 17
|
||
| 656 | # document:Greetings -> Link to the document with title "Greetings"
|
||
| 657 | # document:"Some document" -> Link to the document with title "Some document"
|
||
| 658 | # Versions:
|
||
| 659 | # version#3 -> Link to version with id 3
|
||
| 660 | # version:1.0.0 -> Link to version named "1.0.0"
|
||
| 661 | # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
|
||
| 662 | # Attachments:
|
||
| 663 | # attachment:file.zip -> Link to the attachment of the current object named file.zip
|
||
| 664 | # Source files:
|
||
| 665 | # source:some/file -> Link to the file located at /some/file in the project's repository
|
||
| 666 | # source:some/file@52 -> Link to the file's revision 52
|
||
| 667 | # source:some/file#L120 -> Link to line 120 of the file
|
||
| 668 | # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
|
||
| 669 | # export:some/file -> Force the download of the file
|
||
| 670 | 210:0579821a129a | Chris | # Forum messages:
|
| 671 | 0:513646585e45 | Chris | # message#1218 -> Link to message with id 1218
|
| 672 | 210:0579821a129a | Chris | #
|
| 673 | # Links can refer other objects from other projects, using project identifier:
|
||
| 674 | # identifier:r52
|
||
| 675 | # identifier:document:"Some document"
|
||
| 676 | # identifier:version:1.0.0
|
||
| 677 | # identifier:source:some/file
|
||
| 678 | 1294:3e4c3460b6ca | Chris | def parse_redmine_links(text, default_project, obj, attr, only_path, options) |
| 679 | text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m| |
||
| 680 | 1115:433d4f72a19b | Chris | leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17 |
| 681 | 0:513646585e45 | Chris | link = nil
|
| 682 | 1294:3e4c3460b6ca | Chris | project = default_project |
| 683 | 210:0579821a129a | Chris | if project_identifier
|
| 684 | project = Project.visible.find_by_identifier(project_identifier)
|
||
| 685 | end
|
||
| 686 | 0:513646585e45 | Chris | if esc.nil?
|
| 687 | if prefix.nil? && sep == 'r' |
||
| 688 | 1115:433d4f72a19b | Chris | if project
|
| 689 | repository = nil
|
||
| 690 | if repo_identifier
|
||
| 691 | repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
|
||
| 692 | else
|
||
| 693 | repository = project.repository |
||
| 694 | end
|
||
| 695 | # project.changesets.visible raises an SQL error because of a double join on repositories
|
||
| 696 | if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier)) |
||
| 697 | link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision}, |
||
| 698 | :class => 'changeset', |
||
| 699 | :title => truncate_single_line(changeset.comments, :length => 100)) |
||
| 700 | end
|
||
| 701 | 0:513646585e45 | Chris | end
|
| 702 | elsif sep == '#' |
||
| 703 | oid = identifier.to_i |
||
| 704 | case prefix
|
||
| 705 | when nil |
||
| 706 | 1115:433d4f72a19b | Chris | if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) |
| 707 | anchor = comment_id ? "note-#{comment_id}" : nil |
||
| 708 | link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, |
||
| 709 | 0:513646585e45 | Chris | :class => issue.css_classes,
|
| 710 | :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") |
||
| 711 | end
|
||
| 712 | when 'document' |
||
| 713 | 210:0579821a129a | Chris | if document = Document.visible.find_by_id(oid) |
| 714 | 0:513646585e45 | Chris | link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
|
| 715 | :class => 'document' |
||
| 716 | end
|
||
| 717 | when 'version' |
||
| 718 | 210:0579821a129a | Chris | if version = Version.visible.find_by_id(oid) |
| 719 | 0:513646585e45 | Chris | link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
|
| 720 | :class => 'version' |
||
| 721 | end
|
||
| 722 | when 'message' |
||
| 723 | 210:0579821a129a | Chris | if message = Message.visible.find_by_id(oid, :include => :parent) |
| 724 | link = link_to_message(message, {:only_path => only_path}, :class => 'message')
|
||
| 725 | 0:513646585e45 | Chris | end
|
| 726 | 909:cbb26bc654de | Chris | when 'forum' |
| 727 | if board = Board.visible.find_by_id(oid) |
||
| 728 | link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
|
||
| 729 | :class => 'board' |
||
| 730 | end
|
||
| 731 | when 'news' |
||
| 732 | if news = News.visible.find_by_id(oid) |
||
| 733 | link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
|
||
| 734 | :class => 'news' |
||
| 735 | end
|
||
| 736 | 0:513646585e45 | Chris | when 'project' |
| 737 | if p = Project.visible.find_by_id(oid) |
||
| 738 | 14:1d32c0a0efbf | Chris | link = link_to_project(p, {:only_path => only_path}, :class => 'project')
|
| 739 | 0:513646585e45 | Chris | end
|
| 740 | end
|
||
| 741 | elsif sep == ':' |
||
| 742 | # removes the double quotes if any
|
||
| 743 | name = identifier.gsub(%r{^"(.*)"$}, "\\1") |
||
| 744 | case prefix
|
||
| 745 | when 'document' |
||
| 746 | 210:0579821a129a | Chris | if project && document = project.documents.visible.find_by_title(name)
|
| 747 | 0:513646585e45 | Chris | link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
|
| 748 | :class => 'document' |
||
| 749 | end
|
||
| 750 | when 'version' |
||
| 751 | 210:0579821a129a | Chris | if project && version = project.versions.visible.find_by_name(name)
|
| 752 | 0:513646585e45 | Chris | link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
|
| 753 | :class => 'version' |
||
| 754 | end
|
||
| 755 | 909:cbb26bc654de | Chris | when 'forum' |
| 756 | if project && board = project.boards.visible.find_by_name(name)
|
||
| 757 | link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
|
||
| 758 | :class => 'board' |
||
| 759 | end
|
||
| 760 | when 'news' |
||
| 761 | if project && news = project.news.visible.find_by_title(name)
|
||
| 762 | link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
|
||
| 763 | :class => 'news' |
||
| 764 | end
|
||
| 765 | 1115:433d4f72a19b | Chris | when 'commit', 'source', 'export' |
| 766 | if project
|
||
| 767 | repository = nil
|
||
| 768 | 1294:3e4c3460b6ca | Chris | if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$} |
| 769 | 1115:433d4f72a19b | Chris | repo_prefix, repo_identifier, name = $1, $2, $3 |
| 770 | repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
|
||
| 771 | else
|
||
| 772 | repository = project.repository |
||
| 773 | end
|
||
| 774 | if prefix == 'commit' |
||
| 775 | 1295:622f24f53b42 | Chris | if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first) |
| 776 | 1115:433d4f72a19b | Chris | link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier}, |
| 777 | :class => 'changeset', |
||
| 778 | 1295:622f24f53b42 | Chris | :title => truncate_single_line(changeset.comments, :length => 100) |
| 779 | 1115:433d4f72a19b | Chris | end
|
| 780 | else
|
||
| 781 | if repository && User.current.allowed_to?(:browse_repository, project) |
||
| 782 | 1295:622f24f53b42 | Chris | name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
|
| 783 | 1115:433d4f72a19b | Chris | path, rev, anchor = $1, $3, $5 |
| 784 | link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param, |
||
| 785 | :path => to_path_param(path),
|
||
| 786 | :rev => rev,
|
||
| 787 | :anchor => anchor},
|
||
| 788 | :class => (prefix == 'export' ? 'source download' : 'source') |
||
| 789 | end
|
||
| 790 | end
|
||
| 791 | repo_prefix = nil
|
||
| 792 | 0:513646585e45 | Chris | end
|
| 793 | when 'attachment' |
||
| 794 | attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) |
||
| 795 | 1294:3e4c3460b6ca | Chris | if attachments && attachment = Attachment.latest_attach(attachments, name) |
| 796 | 1295:622f24f53b42 | Chris | link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment') |
| 797 | 0:513646585e45 | Chris | end
|
| 798 | when 'project' |
||
| 799 | 1295:622f24f53b42 | Chris | if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first |
| 800 | 14:1d32c0a0efbf | Chris | link = link_to_project(p, {:only_path => only_path}, :class => 'project')
|
| 801 | 0:513646585e45 | Chris | end
|
| 802 | end
|
||
| 803 | end
|
||
| 804 | end
|
||
| 805 | 1115:433d4f72a19b | Chris | (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
|
| 806 | 0:513646585e45 | Chris | end
|
| 807 | end
|
||
| 808 | 441:cbce1fd3b1b7 | Chris | |
| 809 | 1115:433d4f72a19b | Chris | HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE) |
| 810 | 909:cbb26bc654de | Chris | |
| 811 | def parse_sections(text, project, obj, attr, only_path, options) |
||
| 812 | return unless options[:edit_section_links] |
||
| 813 | text.gsub!(HEADING_RE) do |
||
| 814 | 1115:433d4f72a19b | Chris | heading = $1
|
| 815 | 909:cbb26bc654de | Chris | @current_section += 1 |
| 816 | if @current_section > 1 |
||
| 817 | content_tag('div',
|
||
| 818 | link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), |
||
| 819 | :class => 'contextual', |
||
| 820 | 1115:433d4f72a19b | Chris | :title => l(:button_edit_section)) + heading.html_safe |
| 821 | 909:cbb26bc654de | Chris | else
|
| 822 | 1115:433d4f72a19b | Chris | heading |
| 823 | 909:cbb26bc654de | Chris | end
|
| 824 | end
|
||
| 825 | end
|
||
| 826 | 441:cbce1fd3b1b7 | Chris | |
| 827 | 37:94944d00e43c | chris | # Headings and TOC
|
| 828 | 119:8661b858af72 | Chris | # Adds ids and links to headings unless options[:headings] is set to false
|
| 829 | 37:94944d00e43c | chris | def parse_headings(text, project, obj, attr, only_path, options) |
| 830 | 119:8661b858af72 | Chris | return if options[:headings] == false |
| 831 | 441:cbce1fd3b1b7 | Chris | |
| 832 | 37:94944d00e43c | chris | text.gsub!(HEADING_RE) do |
| 833 | 909:cbb26bc654de | Chris | level, attrs, content = $2.to_i, $3, $4 |
| 834 | 37:94944d00e43c | chris | item = strip_tags(content).strip |
| 835 | 909:cbb26bc654de | Chris | anchor = sanitize_anchor_name(item) |
| 836 | # used for single-file wiki export
|
||
| 837 | anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) |
||
| 838 | 929:5f33065ddc4b | Chris | @heading_anchors[anchor] ||= 0 |
| 839 | idx = (@heading_anchors[anchor] += 1) |
||
| 840 | if idx > 1 |
||
| 841 | anchor = "#{anchor}-#{idx}"
|
||
| 842 | end
|
||
| 843 | 119:8661b858af72 | Chris | @parsed_headings << [level, anchor, item]
|
| 844 | 441:cbce1fd3b1b7 | Chris | "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>"
|
| 845 | 119:8661b858af72 | Chris | end
|
| 846 | end
|
||
| 847 | 441:cbce1fd3b1b7 | Chris | |
| 848 | 1115:433d4f72a19b | Chris | MACROS_RE = /( |
| 849 | 909:cbb26bc654de | Chris | (!)? # escaping
|
| 850 | (
|
||
| 851 | \{\{ # opening tag
|
||
| 852 | ([\w]+) # macro name
|
||
| 853 | 1115:433d4f72a19b | Chris | (\(([^\n\r]*?)\))? # optional arguments
|
| 854 | ([\n\r].*?[\n\r])? # optional block of text
|
||
| 855 | 909:cbb26bc654de | Chris | \}\} # closing tag
|
| 856 | )
|
||
| 857 | 1115:433d4f72a19b | Chris | )/mx unless const_defined?(:MACROS_RE) |
| 858 | 909:cbb26bc654de | Chris | |
| 859 | 1115:433d4f72a19b | Chris | MACRO_SUB_RE = /( |
| 860 | \{\{
|
||
| 861 | macro\((\d+)\)
|
||
| 862 | \}\}
|
||
| 863 | )/x unless const_defined?(:MACRO_SUB_RE) |
||
| 864 | |||
| 865 | # Extracts macros from text
|
||
| 866 | def catch_macros(text) |
||
| 867 | macros = {}
|
||
| 868 | 909:cbb26bc654de | Chris | text.gsub!(MACROS_RE) do |
| 869 | 1115:433d4f72a19b | Chris | all, macro = $1, $4.downcase |
| 870 | if macro_exists?(macro) || all =~ MACRO_SUB_RE |
||
| 871 | index = macros.size |
||
| 872 | macros[index] = all |
||
| 873 | "{{macro(#{index})}}"
|
||
| 874 | 909:cbb26bc654de | Chris | else
|
| 875 | all |
||
| 876 | end
|
||
| 877 | end
|
||
| 878 | 1115:433d4f72a19b | Chris | macros |
| 879 | end
|
||
| 880 | |||
| 881 | # Executes and replaces macros in text
|
||
| 882 | def inject_macros(text, obj, macros, execute=true) |
||
| 883 | text.gsub!(MACRO_SUB_RE) do |
||
| 884 | all, index = $1, $2.to_i |
||
| 885 | orig = macros.delete(index) |
||
| 886 | if execute && orig && orig =~ MACROS_RE |
||
| 887 | esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip) |
||
| 888 | if esc.nil?
|
||
| 889 | h(exec_macro(macro, obj, args, block) || all) |
||
| 890 | else
|
||
| 891 | h(all) |
||
| 892 | end
|
||
| 893 | elsif orig
|
||
| 894 | h(orig) |
||
| 895 | else
|
||
| 896 | h(all) |
||
| 897 | end
|
||
| 898 | end
|
||
| 899 | 909:cbb26bc654de | Chris | end
|
| 900 | |||
| 901 | 119:8661b858af72 | Chris | TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) |
| 902 | 441:cbce1fd3b1b7 | Chris | |
| 903 | 119:8661b858af72 | Chris | # Renders the TOC with given headings
|
| 904 | def replace_toc(text, headings) |
||
| 905 | 37:94944d00e43c | chris | text.gsub!(TOC_RE) do |
| 906 | 1115:433d4f72a19b | Chris | # Keep only the 4 first levels
|
| 907 | headings = headings.select{|level, anchor, item| level <= 4}
|
||
| 908 | 37:94944d00e43c | chris | if headings.empty?
|
| 909 | ''
|
||
| 910 | else
|
||
| 911 | div_class = 'toc'
|
||
| 912 | div_class << ' right' if $1 == '>' |
||
| 913 | div_class << ' left' if $1 == '<' |
||
| 914 | out = "<ul class=\"#{div_class}\"><li>"
|
||
| 915 | root = headings.map(&:first).min
|
||
| 916 | current = root |
||
| 917 | started = false
|
||
| 918 | headings.each do |level, anchor, item|
|
||
| 919 | if level > current
|
||
| 920 | out << '<ul><li>' * (level - current)
|
||
| 921 | elsif level < current
|
||
| 922 | out << "</li></ul>\n" * (current - level) + "</li><li>" |
||
| 923 | elsif started
|
||
| 924 | out << '</li><li>'
|
||
| 925 | end
|
||
| 926 | out << "<a href=\"##{anchor}\">#{item}</a>"
|
||
| 927 | current = level |
||
| 928 | started = true
|
||
| 929 | end
|
||
| 930 | out << '</li></ul>' * (current - root)
|
||
| 931 | out << '</li></ul>'
|
||
| 932 | end
|
||
| 933 | end
|
||
| 934 | end
|
||
| 935 | 0:513646585e45 | Chris | |
| 936 | # Same as Rails' simple_format helper without using paragraphs
|
||
| 937 | def simple_format_without_paragraph(text) |
||
| 938 | text.to_s. |
||
| 939 | gsub(/\r\n?/, "\n"). # \r\n and \r -> \n |
||
| 940 | gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br |
||
| 941 | 909:cbb26bc654de | Chris | gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br |
| 942 | html_safe |
||
| 943 | 0:513646585e45 | Chris | end
|
| 944 | |||
| 945 | def lang_options_for_select(blank=true) |
||
| 946 | 1115:433d4f72a19b | Chris | (blank ? [["(auto)", ""]] : []) + languages_options |
| 947 | 0:513646585e45 | Chris | end
|
| 948 | |||
| 949 | def label_tag_for(name, option_tags = nil, options = {}) |
||
| 950 | label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") |
||
| 951 | content_tag("label", label_text)
|
||
| 952 | end
|
||
| 953 | |||
| 954 | 1115:433d4f72a19b | Chris | def labelled_form_for(*args, &proc) |
| 955 | 909:cbb26bc654de | Chris | args << {} unless args.last.is_a?(Hash)
|
| 956 | options = args.last |
||
| 957 | 1115:433d4f72a19b | Chris | if args.first.is_a?(Symbol) |
| 958 | options.merge!(:as => args.shift)
|
||
| 959 | end
|
||
| 960 | options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
|
||
| 961 | 909:cbb26bc654de | Chris | form_for(*args, &proc) |
| 962 | end
|
||
| 963 | |||
| 964 | 1115:433d4f72a19b | Chris | def labelled_fields_for(*args, &proc) |
| 965 | 909:cbb26bc654de | Chris | args << {} unless args.last.is_a?(Hash)
|
| 966 | options = args.last |
||
| 967 | 1115:433d4f72a19b | Chris | options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
|
| 968 | fields_for(*args, &proc) |
||
| 969 | end
|
||
| 970 | |||
| 971 | def labelled_remote_form_for(*args, &proc) |
||
| 972 | ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2." |
||
| 973 | args << {} unless args.last.is_a?(Hash)
|
||
| 974 | options = args.last |
||
| 975 | options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
|
||
| 976 | 909:cbb26bc654de | Chris | form_for(*args, &proc) |
| 977 | 0:513646585e45 | Chris | end
|
| 978 | |||
| 979 | 1115:433d4f72a19b | Chris | def error_messages_for(*objects) |
| 980 | html = ""
|
||
| 981 | objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
|
||
| 982 | errors = objects.map {|o| o.errors.full_messages}.flatten
|
||
| 983 | if errors.any?
|
||
| 984 | html << "<div id='errorExplanation'><ul>\n"
|
||
| 985 | errors.each do |error|
|
||
| 986 | html << "<li>#{h error}</li>\n"
|
||
| 987 | end
|
||
| 988 | html << "</ul></div>\n"
|
||
| 989 | end
|
||
| 990 | html.html_safe |
||
| 991 | 1120:55944e901e9e | luis | end
|
| 992 | 1115:433d4f72a19b | Chris | |
| 993 | def delete_link(url, options={}) |
||
| 994 | options = {
|
||
| 995 | :method => :delete, |
||
| 996 | :data => {:confirm => l(:text_are_you_sure)}, |
||
| 997 | :class => 'icon icon-del' |
||
| 998 | }.merge(options) |
||
| 999 | |||
| 1000 | link_to l(:button_delete), url, options
|
||
| 1001 | end
|
||
| 1002 | |||
| 1003 | def preview_link(url, form, target='preview', options={}) |
||
| 1004 | content_tag 'a', l(:label_preview), { |
||
| 1005 | 1120:55944e901e9e | luis | :href => "#", |
| 1006 | :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|, |
||
| 1007 | 1115:433d4f72a19b | Chris | :accesskey => accesskey(:preview) |
| 1008 | }.merge(options) |
||
| 1009 | end
|
||
| 1010 | |||
| 1011 | def link_to_function(name, function, html_options={}) |
||
| 1012 | content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options)) |
||
| 1013 | end
|
||
| 1014 | |||
| 1015 | # Helper to render JSON in views
|
||
| 1016 | def raw_json(arg) |
||
| 1017 | arg.to_json.to_s.gsub('/', '\/').html_safe |
||
| 1018 | end
|
||
| 1019 | |||
| 1020 | def back_url |
||
| 1021 | url = params[:back_url]
|
||
| 1022 | if url.nil? && referer = request.env['HTTP_REFERER'] |
||
| 1023 | url = CGI.unescape(referer.to_s)
|
||
| 1024 | end
|
||
| 1025 | url |
||
| 1026 | end
|
||
| 1027 | |||
| 1028 | 0:513646585e45 | Chris | def back_url_hidden_field_tag |
| 1029 | 1115:433d4f72a19b | Chris | url = back_url |
| 1030 | hidden_field_tag('back_url', url, :id => nil) unless url.blank? |
||
| 1031 | 0:513646585e45 | Chris | end
|
| 1032 | |||
| 1033 | def check_all_links(form_name) |
||
| 1034 | link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + |
||
| 1035 | 909:cbb26bc654de | Chris | " | ".html_safe +
|
| 1036 | 0:513646585e45 | Chris | link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") |
| 1037 | end
|
||
| 1038 | |||
| 1039 | def progress_bar(pcts, options={}) |
||
| 1040 | pcts = [pcts, pcts] unless pcts.is_a?(Array) |
||
| 1041 | pcts = pcts.collect(&:round)
|
||
| 1042 | pcts[1] = pcts[1] - pcts[0] |
||
| 1043 | pcts << (100 - pcts[1] - pcts[0]) |
||
| 1044 | width = options[:width] || '100px;' |
||
| 1045 | legend = options[:legend] || '' |
||
| 1046 | content_tag('table',
|
||
| 1047 | content_tag('tr',
|
||
| 1048 | 909:cbb26bc654de | Chris | (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + |
| 1049 | (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) + |
||
| 1050 | (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe) |
||
| 1051 | ), :class => 'progress', :style => "width: #{width};").html_safe + |
||
| 1052 | 1295:622f24f53b42 | Chris | content_tag('p', legend, :class => 'percent').html_safe |
| 1053 | 0:513646585e45 | Chris | end
|
| 1054 | 441:cbce1fd3b1b7 | Chris | |
| 1055 | 0:513646585e45 | Chris | def checked_image(checked=true) |
| 1056 | if checked
|
||
| 1057 | image_tag 'toggle_check.png'
|
||
| 1058 | end
|
||
| 1059 | end
|
||
| 1060 | 441:cbce1fd3b1b7 | Chris | |
| 1061 | 0:513646585e45 | Chris | def context_menu(url) |
| 1062 | unless @context_menu_included |
||
| 1063 | content_for :header_tags do |
||
| 1064 | javascript_include_tag('context_menu') +
|
||
| 1065 | stylesheet_link_tag('context_menu')
|
||
| 1066 | end
|
||
| 1067 | 14:1d32c0a0efbf | Chris | if l(:direction) == 'rtl' |
| 1068 | content_for :header_tags do |
||
| 1069 | stylesheet_link_tag('context_menu_rtl')
|
||
| 1070 | end
|
||
| 1071 | end
|
||
| 1072 | 0:513646585e45 | Chris | @context_menu_included = true |
| 1073 | end
|
||
| 1074 | 1115:433d4f72a19b | Chris | javascript_tag "contextMenuInit('#{ url_for(url) }')"
|
| 1075 | 0:513646585e45 | Chris | end
|
| 1076 | |||
| 1077 | def calendar_for(field_id) |
||
| 1078 | include_calendar_headers_tags |
||
| 1079 | 1115:433d4f72a19b | Chris | javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
|
| 1080 | 0:513646585e45 | Chris | end
|
| 1081 | |||
| 1082 | def include_calendar_headers_tags |
||
| 1083 | unless @calendar_headers_tags_included |
||
| 1084 | @calendar_headers_tags_included = true |
||
| 1085 | content_for :header_tags do |
||
| 1086 | 1115:433d4f72a19b | Chris | start_of_week = Setting.start_of_week
|
| 1087 | start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank? |
||
| 1088 | # Redmine uses 1..7 (monday..sunday) in settings and locales
|
||
| 1089 | # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
|
||
| 1090 | start_of_week = start_of_week.to_i % 7
|
||
| 1091 | |||
| 1092 | tags = javascript_tag( |
||
| 1093 | "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
|
||
| 1094 | 1120:55944e901e9e | luis | "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
|
| 1095 | 1115:433d4f72a19b | Chris | path_to_image('/images/calendar.png') +
|
| 1096 | 1295:622f24f53b42 | Chris | "', showButtonPanel: true, showWeek: true, showOtherMonths: true, selectOtherMonths: true};")
|
| 1097 | 1115:433d4f72a19b | Chris | jquery_locale = l('jquery.locale', :default => current_language.to_s) |
| 1098 | unless jquery_locale == 'en' |
||
| 1099 | 1120:55944e901e9e | luis | tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
|
| 1100 | 0:513646585e45 | Chris | end
|
| 1101 | 1115:433d4f72a19b | Chris | tags |
| 1102 | 0:513646585e45 | Chris | end
|
| 1103 | end
|
||
| 1104 | end
|
||
| 1105 | |||
| 1106 | 1115:433d4f72a19b | Chris | # Overrides Rails' stylesheet_link_tag with themes and plugins support.
|
| 1107 | # Examples:
|
||
| 1108 | # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
|
||
| 1109 | # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
|
||
| 1110 | #
|
||
| 1111 | def stylesheet_link_tag(*sources) |
||
| 1112 | options = sources.last.is_a?(Hash) ? sources.pop : {}
|
||
| 1113 | plugin = options.delete(:plugin)
|
||
| 1114 | sources = sources.map do |source|
|
||
| 1115 | if plugin
|
||
| 1116 | "/plugin_assets/#{plugin}/stylesheets/#{source}"
|
||
| 1117 | elsif current_theme && current_theme.stylesheets.include?(source)
|
||
| 1118 | current_theme.stylesheet_path(source) |
||
| 1119 | else
|
||
| 1120 | source |
||
| 1121 | end
|
||
| 1122 | end
|
||
| 1123 | super sources, options
|
||
| 1124 | end
|
||
| 1125 | |||
| 1126 | # Overrides Rails' image_tag with themes and plugins support.
|
||
| 1127 | # Examples:
|
||
| 1128 | # image_tag('image.png') # => picks image.png from the current theme or defaults
|
||
| 1129 | # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
|
||
| 1130 | #
|
||
| 1131 | def image_tag(source, options={}) |
||
| 1132 | if plugin = options.delete(:plugin) |
||
| 1133 | source = "/plugin_assets/#{plugin}/images/#{source}"
|
||
| 1134 | elsif current_theme && current_theme.images.include?(source)
|
||
| 1135 | source = current_theme.image_path(source) |
||
| 1136 | end
|
||
| 1137 | super source, options
|
||
| 1138 | end
|
||
| 1139 | |||
| 1140 | # Overrides Rails' javascript_include_tag with plugins support
|
||
| 1141 | # Examples:
|
||
| 1142 | # javascript_include_tag('scripts') # => picks scripts.js from defaults
|
||
| 1143 | # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
|
||
| 1144 | #
|
||
| 1145 | def javascript_include_tag(*sources) |
||
| 1146 | options = sources.last.is_a?(Hash) ? sources.pop : {}
|
||
| 1147 | if plugin = options.delete(:plugin) |
||
| 1148 | sources = sources.map do |source|
|
||
| 1149 | if plugin
|
||
| 1150 | "/plugin_assets/#{plugin}/javascripts/#{source}"
|
||
| 1151 | else
|
||
| 1152 | source |
||
| 1153 | end
|
||
| 1154 | end
|
||
| 1155 | end
|
||
| 1156 | super sources, options
|
||
| 1157 | end
|
||
| 1158 | |||
| 1159 | 0:513646585e45 | Chris | def content_for(name, content = nil, &block) |
| 1160 | @has_content ||= {}
|
||
| 1161 | @has_content[name] = true |
||
| 1162 | super(name, content, &block)
|
||
| 1163 | end
|
||
| 1164 | |||
| 1165 | def has_content?(name) |
||
| 1166 | (@has_content && @has_content[name]) || false |
||
| 1167 | end
|
||
| 1168 | |||
| 1169 | 1115:433d4f72a19b | Chris | def sidebar_content? |
| 1170 | has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
|
||
| 1171 | end
|
||
| 1172 | |||
| 1173 | def view_layouts_base_sidebar_hook_response |
||
| 1174 | @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar) |
||
| 1175 | end
|
||
| 1176 | |||
| 1177 | 909:cbb26bc654de | Chris | def email_delivery_enabled? |
| 1178 | !!ActionMailer::Base.perform_deliveries |
||
| 1179 | end
|
||
| 1180 | |||
| 1181 | 0:513646585e45 | Chris | # Returns the avatar image tag for the given +user+ if avatars are enabled
|
| 1182 | # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
|
||
| 1183 | def avatar(user, options = { }) |
||
| 1184 | if Setting.gravatar_enabled? |
||
| 1185 | 1115:433d4f72a19b | Chris | options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
|
| 1186 | 0:513646585e45 | Chris | email = nil
|
| 1187 | if user.respond_to?(:mail) |
||
| 1188 | email = user.mail |
||
| 1189 | elsif user.to_s =~ %r{<(.+?)>} |
||
| 1190 | email = $1
|
||
| 1191 | end
|
||
| 1192 | return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil |
||
| 1193 | 22:40f7cfd4df19 | chris | else
|
| 1194 | ''
|
||
| 1195 | 0:513646585e45 | Chris | end
|
| 1196 | end
|
||
| 1197 | 441:cbce1fd3b1b7 | Chris | |
| 1198 | 909:cbb26bc654de | Chris | def sanitize_anchor_name(anchor) |
| 1199 | 1115:433d4f72a19b | Chris | if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' |
| 1200 | 1295:622f24f53b42 | Chris | anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-') |
| 1201 | 1115:433d4f72a19b | Chris | else
|
| 1202 | # TODO: remove when ruby1.8 is no longer supported
|
||
| 1203 | anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') |
||
| 1204 | end
|
||
| 1205 | 909:cbb26bc654de | Chris | end
|
| 1206 | |||
| 1207 | 245:051f544170fe | Chris | # Returns the javascript tags that are included in the html layout head
|
| 1208 | def javascript_heads |
||
| 1209 | 1295:622f24f53b42 | Chris | tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application') |
| 1210 | 245:051f544170fe | Chris | unless User.current.pref.warn_on_leaving_unsaved == '0' |
| 1211 | 1115:433d4f72a19b | Chris | tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });") |
| 1212 | 245:051f544170fe | Chris | end
|
| 1213 | tags |
||
| 1214 | end
|
||
| 1215 | 0:513646585e45 | Chris | |
| 1216 | 14:1d32c0a0efbf | Chris | def favicon |
| 1217 | 909:cbb26bc654de | Chris | "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
|
| 1218 | 14:1d32c0a0efbf | Chris | end
|
| 1219 | 441:cbce1fd3b1b7 | Chris | |
| 1220 | def robot_exclusion_tag |
||
| 1221 | 909:cbb26bc654de | Chris | '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
|
| 1222 | 441:cbce1fd3b1b7 | Chris | end
|
| 1223 | |||
| 1224 | 503:5d608412b003 | chris | def stylesheet_platform_font_tag |
| 1225 | agent = request.env['HTTP_USER_AGENT']
|
||
| 1226 | name = 'fonts-generic'
|
||
| 1227 | if agent and agent =~ %r{Windows} |
||
| 1228 | name = 'fonts-ms'
|
||
| 1229 | elsif agent and agent =~ %r{Macintosh} |
||
| 1230 | name = 'fonts-mac'
|
||
| 1231 | end
|
||
| 1232 | stylesheet_link_tag name, :media => 'all' |
||
| 1233 | end
|
||
| 1234 | |||
| 1235 | 119:8661b858af72 | Chris | # Returns true if arg is expected in the API response
|
| 1236 | def include_in_api_response?(arg) |
||
| 1237 | unless @included_in_api_response |
||
| 1238 | param = params[:include]
|
||
| 1239 | @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',') |
||
| 1240 | @included_in_api_response.collect!(&:strip) |
||
| 1241 | end
|
||
| 1242 | @included_in_api_response.include?(arg.to_s)
|
||
| 1243 | end
|
||
| 1244 | 14:1d32c0a0efbf | Chris | |
| 1245 | 119:8661b858af72 | Chris | # Returns options or nil if nometa param or X-Redmine-Nometa header
|
| 1246 | # was set in the request
|
||
| 1247 | def api_meta(options) |
||
| 1248 | if params[:nometa].present? || request.headers['X-Redmine-Nometa'] |
||
| 1249 | # compatibility mode for activeresource clients that raise
|
||
| 1250 | # an error when unserializing an array with attributes
|
||
| 1251 | nil
|
||
| 1252 | else
|
||
| 1253 | options |
||
| 1254 | end
|
||
| 1255 | end
|
||
| 1256 | 441:cbce1fd3b1b7 | Chris | |
| 1257 | 0:513646585e45 | Chris | private |
| 1258 | |||
| 1259 | def wiki_helper |
||
| 1260 | helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) |
||
| 1261 | extend helper |
||
| 1262 | return self |
||
| 1263 | end
|
||
| 1264 | 441:cbce1fd3b1b7 | Chris | |
| 1265 | def link_to_content_update(text, url_params = {}, html_options = {}) |
||
| 1266 | link_to(text, url_params, html_options) |
||
| 1267 | 0:513646585e45 | Chris | end
|
| 1268 | end |