Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
908dcc4eb4 | ||
|
|
20bdba13cf | ||
|
|
46c0d72782 | ||
|
|
36b5d4f6af | ||
|
|
b366375e7e | ||
|
|
629c106a46 | ||
|
|
f19ae06a72 | ||
|
|
69b99aa518 | ||
|
|
cb5d0c469d | ||
|
|
57d10ed893 | ||
|
|
28e4ff8957 |
@@ -75,7 +75,11 @@ class UsersController < ApplicationController
|
|||||||
@user.admin = params[:user][:admin] if params[:user][:admin]
|
@user.admin = params[:user][:admin] if params[:user][:admin]
|
||||||
@user.login = params[:user][:login] if params[:user][:login]
|
@user.login = params[:user][:login] if params[:user][:login]
|
||||||
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
|
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
|
||||||
if @user.update_attributes(params[:user])
|
@user.attributes = params[:user]
|
||||||
|
# Was the account actived ? (do it before User#save clears the change)
|
||||||
|
was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
|
||||||
|
if @user.save
|
||||||
|
Mailer.deliver_account_activated(@user) if was_activated
|
||||||
flash[:notice] = l(:notice_successful_update)
|
flash[:notice] = l(:notice_successful_update)
|
||||||
# Give a string to redirect_to otherwise it would use status param as the response code
|
# Give a string to redirect_to otherwise it would use status param as the response code
|
||||||
redirect_to(url_for(:action => 'list', :status => params[:status], :page => params[:page]))
|
redirect_to(url_for(:action => 'list', :status => params[:status], :page => params[:page]))
|
||||||
|
|||||||
@@ -487,11 +487,11 @@ module ApplicationHelper
|
|||||||
full_messages = []
|
full_messages = []
|
||||||
object.errors.each do |attr, msg|
|
object.errors.each do |attr, msg|
|
||||||
next if msg.nil?
|
next if msg.nil?
|
||||||
msg = msg.first if msg.is_a? Array
|
msg = [msg] unless msg.is_a?(Array)
|
||||||
if attr == "base"
|
if attr == "base"
|
||||||
full_messages << l(msg)
|
full_messages << l(*msg)
|
||||||
else
|
else
|
||||||
full_messages << "« " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " » " + l(msg) unless attr == "custom_values"
|
full_messages << "« " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " » " + l(*msg) unless attr == "custom_values"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# retrieve custom values error messages
|
# retrieve custom values error messages
|
||||||
@@ -499,8 +499,8 @@ module ApplicationHelper
|
|||||||
object.custom_values.each do |v|
|
object.custom_values.each do |v|
|
||||||
v.errors.each do |attr, msg|
|
v.errors.each do |attr, msg|
|
||||||
next if msg.nil?
|
next if msg.nil?
|
||||||
msg = msg.first if msg.is_a? Array
|
msg = [msg] unless msg.is_a?(Array)
|
||||||
full_messages << "« " + v.custom_field.name + " » " + l(msg)
|
full_messages << "« " + v.custom_field.name + " » " + l(*msg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class MailHandler < ActionMailer::Base
|
|||||||
# Processes incoming emails
|
# Processes incoming emails
|
||||||
def receive(email)
|
def receive(email)
|
||||||
@email = email
|
@email = email
|
||||||
@user = User.active.find(:first, :conditions => ["LOWER(mail) = ?", email.from.first.to_s.strip.downcase])
|
@user = User.active.find(:first, :conditions => ["LOWER(mail) = ?", email.from.to_a.first.to_s.strip.downcase])
|
||||||
unless @user
|
unless @user
|
||||||
# Unknown user => the email is ignored
|
# Unknown user => the email is ignored
|
||||||
# TODO: ability to create the user's account
|
# TODO: ability to create the user's account
|
||||||
@@ -163,10 +163,17 @@ class MailHandler < ActionMailer::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def get_keyword(attr, options={})
|
def get_keyword(attr, options={})
|
||||||
if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i
|
@keywords ||= {}
|
||||||
$1.strip
|
if @keywords.has_key?(attr)
|
||||||
elsif !@@handler_options[:issue][attr].blank?
|
@keywords[attr]
|
||||||
@@handler_options[:issue][attr]
|
else
|
||||||
|
@keywords[attr] = begin
|
||||||
|
if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}:[ \t]*(.+)\s*$/i, '')
|
||||||
|
$1.strip
|
||||||
|
elsif !@@handler_options[:issue][attr].blank?
|
||||||
|
@@handler_options[:issue][attr]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -188,5 +195,6 @@ class MailHandler < ActionMailer::Base
|
|||||||
@plain_text_body = plain_text_part.body.to_s
|
@plain_text_body = plain_text_part.body.to_s
|
||||||
end
|
end
|
||||||
@plain_text_body.strip!
|
@plain_text_body.strip!
|
||||||
|
@plain_text_body
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+15
-6
@@ -22,6 +22,12 @@ class Mailer < ActionMailer::Base
|
|||||||
|
|
||||||
include ActionController::UrlWriter
|
include ActionController::UrlWriter
|
||||||
|
|
||||||
|
def self.default_url_options
|
||||||
|
h = Setting.host_name
|
||||||
|
h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
|
||||||
|
{ :host => h, :protocol => Setting.protocol }
|
||||||
|
end
|
||||||
|
|
||||||
def issue_add(issue)
|
def issue_add(issue)
|
||||||
redmine_headers 'Project' => issue.project.identifier,
|
redmine_headers 'Project' => issue.project.identifier,
|
||||||
'Issue-Id' => issue.id,
|
'Issue-Id' => issue.id,
|
||||||
@@ -127,6 +133,15 @@ class Mailer < ActionMailer::Base
|
|||||||
:url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
|
:url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# A registered user's account was activated by an administrator
|
||||||
|
def account_activated(user)
|
||||||
|
set_language_if_valid user.language
|
||||||
|
recipients user.mail
|
||||||
|
subject l(:mail_subject_register, Setting.app_title)
|
||||||
|
body :user => user,
|
||||||
|
:login_url => url_for(:controller => 'account', :action => 'login')
|
||||||
|
end
|
||||||
|
|
||||||
def lost_password(token)
|
def lost_password(token)
|
||||||
set_language_if_valid(token.user.language)
|
set_language_if_valid(token.user.language)
|
||||||
recipients token.user.mail
|
recipients token.user.mail
|
||||||
@@ -189,12 +204,6 @@ class Mailer < ActionMailer::Base
|
|||||||
set_language_if_valid Setting.default_language
|
set_language_if_valid Setting.default_language
|
||||||
from Setting.mail_from
|
from Setting.mail_from
|
||||||
|
|
||||||
# URL options
|
|
||||||
h = Setting.host_name
|
|
||||||
h = h.to_s.gsub(%r{\/.*$}, '') unless ActionController::AbstractRequest.relative_url_root.blank?
|
|
||||||
default_url_options[:host] = h
|
|
||||||
default_url_options[:protocol] = Setting.protocol
|
|
||||||
|
|
||||||
# Common headers
|
# Common headers
|
||||||
headers 'X-Mailer' => 'Redmine',
|
headers 'X-Mailer' => 'Redmine',
|
||||||
'X-Redmine-Host' => Setting.host_name,
|
'X-Redmine-Host' => Setting.host_name,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<% line_num = 1 %>
|
<% line_num = 1 %>
|
||||||
<% syntax_highlight(filename, to_utf8(content)).each_line do |line| %>
|
<% syntax_highlight(filename, to_utf8(content)).each_line do |line| %>
|
||||||
<tr><th class="line-num" id="L<%= line_num %>"><%= line_num %></th><td class="line-code"><pre><%= line %></pre></td></tr>
|
<tr><th class="line-num" id="L<%= line_num %>"><a href="#L<%= line_num %>"><%= line_num %></a></th><td class="line-code"><pre><%= line %></pre></td></tr>
|
||||||
<% line_num += 1 %>
|
<% line_num += 1 %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
<% else %>
|
<% else %>
|
||||||
<%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
|
<%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
|
||||||
<p class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></p>
|
<p class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<p class="other-formats">
|
<p class="other-formats">
|
||||||
<%= l(:label_export_to) %>
|
<%= l(:label_export_to) %>
|
||||||
@@ -50,7 +51,6 @@
|
|||||||
<span><%= link_to 'PDF', {:format => 'pdf'}, :class => 'pdf' %></span>
|
<span><%= link_to 'PDF', {:format => 'pdf'}, :class => 'pdf' %></span>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% content_for :sidebar do %>
|
<% content_for :sidebar do %>
|
||||||
<%= render :partial => 'issues/sidebar' %>
|
<%= render :partial => 'issues/sidebar' %>
|
||||||
|
|||||||
@@ -47,18 +47,20 @@
|
|||||||
<%= tag('div', {:id => 'main', :class => (has_content?(:sidebar) ? '' : 'nosidebar')}, true) %>
|
<%= tag('div', {:id => 'main', :class => (has_content?(:sidebar) ? '' : 'nosidebar')}, true) %>
|
||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
<%= yield :sidebar %>
|
<%= yield :sidebar %>
|
||||||
|
<%= call_hook :view_layouts_base_sidebar %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<%= render_flash_messages %>
|
<%= render_flash_messages %>
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
|
<%= call_hook :view_layouts_base_content %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
|
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
|
||||||
|
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> © 2006-2008 Jean-Philippe Lang
|
Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> © 2006-2009 Jean-Philippe Lang
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%= call_hook :view_layouts_base_body_bottom %>
|
<%= call_hook :view_layouts_base_body_bottom %>
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<p><%= l(:notice_account_activated) %></p>
|
||||||
|
<p><%= l(:label_login) %>: <%= link_to @login_url, @login_url %></p>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<%= l(:notice_account_activated) %>
|
||||||
|
<%= l(:label_login) %>: <%= @login_url %>
|
||||||
@@ -46,7 +46,9 @@
|
|||||||
<% form_tag({}, :method => :get) do %>
|
<% form_tag({}, :method => :get) do %>
|
||||||
<h3><%= l(:label_activity) %></h3>
|
<h3><%= l(:label_activity) %></h3>
|
||||||
<p><% @activity.event_types.each do |t| %>
|
<p><% @activity.event_types.each do |t| %>
|
||||||
<label><%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %> <%= l("label_#{t.singularize}_plural")%></label><br />
|
<%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %>
|
||||||
|
<%= link_to(l("label_#{t.singularize}_plural"), {"show_#{t}" => 1, :user_id => params[:user_id]})%>
|
||||||
|
<br />
|
||||||
<% end %></p>
|
<% end %></p>
|
||||||
<% if @project && @project.active_children.any? %>
|
<% if @project && @project.active_children.any? %>
|
||||||
<p><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label></p>
|
<p><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label></p>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<% delete_allowed = User.current.allowed_to?(:manage_files, @project) %>
|
<% delete_allowed = User.current.allowed_to?(:manage_files, @project) %>
|
||||||
|
|
||||||
<table class="list">
|
<table class="list files">
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
<%= sort_header_tag('filename', :caption => l(:field_filename)) %>
|
<%= sort_header_tag('filename', :caption => l(:field_filename)) %>
|
||||||
<%= sort_header_tag('created_on', :caption => l(:label_date), :default_order => 'desc') %>
|
<%= sort_header_tag('created_on', :caption => l(:label_date), :default_order => 'desc') %>
|
||||||
@@ -19,15 +19,19 @@
|
|||||||
<% @containers.each do |container| %>
|
<% @containers.each do |container| %>
|
||||||
<% next if container.attachments.empty? -%>
|
<% next if container.attachments.empty? -%>
|
||||||
<% if container.is_a?(Version) -%>
|
<% if container.is_a?(Version) -%>
|
||||||
<tr><th colspan="6" align="left"><span class="icon icon-package"><b><%=h container %></b></span></th></tr>
|
<tr>
|
||||||
|
<th colspan="6" align="left">
|
||||||
|
<%= link_to(h(container), {:controller => 'versions', :action => 'show', :id => container}, :class => "icon icon-package") %>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
<% container.attachments.each do |file| %>
|
<% container.attachments.each do |file| %>
|
||||||
<tr class="<%= cycle("odd", "even") %>">
|
<tr class="file <%= cycle("odd", "even") %>">
|
||||||
<td><%= link_to_attachment file, :download => true, :title => file.description %></td>
|
<td class="filename"><%= link_to_attachment file, :download => true, :title => file.description %></td>
|
||||||
<td align="center"><%= format_time(file.created_on) %></td>
|
<td class="created_on"><%= format_time(file.created_on) %></td>
|
||||||
<td align="center"><%= number_to_human_size(file.filesize) %></td>
|
<td class="filesize"><%= number_to_human_size(file.filesize) %></td>
|
||||||
<td align="center"><%= file.downloads %></td>
|
<td class="downloads"><%= file.downloads %></td>
|
||||||
<td align="center"><small><%= file.digest %></small></td>
|
<td class="digest"><%= file.digest %></td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<%= link_to(image_tag('delete.png'), {:controller => 'attachments', :action => 'destroy', :id => file},
|
<%= link_to(image_tag('delete.png'), {:controller => 'attachments', :action => 'destroy', :id => file},
|
||||||
:confirm => l(:text_are_you_sure), :method => :post) if delete_allowed %>
|
:confirm => l(:text_are_you_sure), :method => :post) if delete_allowed %>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<% syntax_highlight(@path, to_utf8(@annotate.content)).each_line do |line| %>
|
<% syntax_highlight(@path, to_utf8(@annotate.content)).each_line do |line| %>
|
||||||
<% revision = @annotate.revisions[line_num-1] %>
|
<% revision = @annotate.revisions[line_num-1] %>
|
||||||
<tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>">
|
<tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>">
|
||||||
<th class="line-num"><%= line_num %></th>
|
<th class="line-num" id="L<%= line_num %>"><a href="#L<%= line_num %>"><%= line_num %></a></th>
|
||||||
<td class="revision">
|
<td class="revision">
|
||||||
<%= (revision.identifier ? link_to(format_revision(revision.identifier), :action => 'revision', :id => @project, :rev => revision.identifier) : format_revision(revision.revision)) if revision %></td>
|
<%= (revision.identifier ? link_to(format_revision(revision.identifier), :action => 'revision', :id => @project, :rev => revision.identifier) : format_revision(revision.revision)) if revision %></td>
|
||||||
<td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td>
|
<td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td>
|
||||||
|
|||||||
@@ -11,12 +11,12 @@
|
|||||||
<%= text_field_tag 'settings[issues_export_limit]', Setting.issues_export_limit, :size => 6 %></p>
|
<%= text_field_tag 'settings[issues_export_limit]', Setting.issues_export_limit, :size => 6 %></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<fieldset class="box"><legend><%= l(:setting_issue_list_default_columns) %></legend>
|
<fieldset class="box settings"><legend><%= l(:setting_issue_list_default_columns) %></legend>
|
||||||
<%= hidden_field_tag 'settings[issue_list_default_columns][]', '' %>
|
<%= hidden_field_tag 'settings[issue_list_default_columns][]', '' %>
|
||||||
<p><% Query.new.available_columns.each do |column| %>
|
<% Query.new.available_columns.each do |column| %>
|
||||||
<label><%= check_box_tag 'settings[issue_list_default_columns][]', column.name, Setting.issue_list_default_columns.include?(column.name.to_s) %>
|
<label><%= check_box_tag 'settings[issue_list_default_columns][]', column.name, Setting.issue_list_default_columns.include?(column.name.to_s) %>
|
||||||
<%= column.caption %></label>
|
<%= column.caption %></label><br />
|
||||||
<% end %></p>
|
<% end %>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<%= submit_tag l(:button_save) %>
|
<%= submit_tag l(:button_save) %>
|
||||||
|
|||||||
@@ -5,6 +5,24 @@ Copyright (C) 2006-2009 Jean-Philippe Lang
|
|||||||
http://www.redmine.org/
|
http://www.redmine.org/
|
||||||
|
|
||||||
|
|
||||||
|
== 2009-03-07 v0.8.2
|
||||||
|
|
||||||
|
* Send an email to the user when an administrator activates a registered user
|
||||||
|
* Strip keywords from received email body
|
||||||
|
* Footer updated to 2009
|
||||||
|
* Show RSS-link even when no issues is found
|
||||||
|
* One click filter action in activity view
|
||||||
|
* Clickable/linkable line #'s while browsing the repo or viewing a file
|
||||||
|
* Links to versions on files list
|
||||||
|
* Added request and controller objects to the hooks by default
|
||||||
|
* Fixed: exporting an issue with attachments to PDF raises an error
|
||||||
|
* Fixed: "too few arguments" error may occur on activerecord error translation
|
||||||
|
* Fixed: "Default columns Displayed on the Issues list" setting is not easy to read
|
||||||
|
* Fixed: visited links to closed tickets are not striked through with IE6
|
||||||
|
* Fixed: MailHandler#plain_text_body returns nil if there was nothing to strip
|
||||||
|
* Fixed: MailHandler raises an error when processing an email without From header
|
||||||
|
|
||||||
|
|
||||||
== 2009-02-15 v0.8.1
|
== 2009-02-15 v0.8.1
|
||||||
|
|
||||||
* Select watchers on new issue form
|
* Select watchers on new issue form
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ require 'rfpdf/chinese'
|
|||||||
module Redmine
|
module Redmine
|
||||||
module Export
|
module Export
|
||||||
module PDF
|
module PDF
|
||||||
|
include ActionView::Helpers::NumberHelper
|
||||||
|
|
||||||
class IFPDF < FPDF
|
class IFPDF < FPDF
|
||||||
include GLoc
|
include GLoc
|
||||||
attr_accessor :footer_date
|
attr_accessor :footer_date
|
||||||
|
|||||||
+50
-9
@@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
module Redmine
|
module Redmine
|
||||||
module Hook
|
module Hook
|
||||||
|
include ActionController::UrlWriter
|
||||||
|
|
||||||
@@listener_classes = []
|
@@listener_classes = []
|
||||||
@@listeners = nil
|
@@listeners = nil
|
||||||
@@hook_listeners = {}
|
@@hook_listeners = {}
|
||||||
@@ -55,11 +57,12 @@ module Redmine
|
|||||||
# Calls a hook.
|
# Calls a hook.
|
||||||
# Returns the listeners response.
|
# Returns the listeners response.
|
||||||
def call_hook(hook, context={})
|
def call_hook(hook, context={})
|
||||||
response = ''
|
returning [] do |response|
|
||||||
hook_listeners(hook).each do |listener|
|
hls = hook_listeners(hook)
|
||||||
response << listener.send(hook, context).to_s
|
if hls.any?
|
||||||
|
hls.each {|listener| response << listener.send(hook, context)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
response
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -73,8 +76,9 @@ module Redmine
|
|||||||
Redmine::Hook.add_listener(child)
|
Redmine::Hook.add_listener(child)
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Listener class used for views hooks.
|
# Listener class used for views hooks.
|
||||||
# Listeners that inherit this class will include various helpers by default.
|
# Listeners that inherit this class will include various helpers by default.
|
||||||
class ViewListener < Listener
|
class ViewListener < Listener
|
||||||
@@ -91,17 +95,54 @@ module Redmine
|
|||||||
include ActionView::Helpers::TextHelper
|
include ActionView::Helpers::TextHelper
|
||||||
include ActionController::UrlWriter
|
include ActionController::UrlWriter
|
||||||
include ApplicationHelper
|
include ApplicationHelper
|
||||||
|
|
||||||
|
# Default to creating links using only the path. Subclasses can
|
||||||
|
# change this default as needed
|
||||||
|
def self.default_url_options
|
||||||
|
{:only_path => true }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helper method to directly render a partial using the context:
|
||||||
|
#
|
||||||
|
# class MyHook < Redmine::Hook::ViewListener
|
||||||
|
# render_on :view_issues_show_details_bottom, :partial => "show_more_data"
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def self.render_on(hook, options={})
|
||||||
|
define_method hook do |context|
|
||||||
|
context[:controller].send(:render_to_string, {:locals => context}.merge(options))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Helper module included in ApplicationHelper so that hooks can be called
|
# Helper module included in ApplicationHelper and ActionControllerso that
|
||||||
# in views like this:
|
# hooks can be called in views like this:
|
||||||
|
#
|
||||||
# <%= call_hook(:some_hook) %>
|
# <%= call_hook(:some_hook) %>
|
||||||
# <%= call_hook(:another_hook, :foo => 'bar' %>
|
# <%= call_hook(:another_hook, :foo => 'bar' %>
|
||||||
#
|
#
|
||||||
# Current project is automatically added to the call context.
|
# Or in controllers like:
|
||||||
|
# call_hook(:some_hook)
|
||||||
|
# call_hook(:another_hook, :foo => 'bar'
|
||||||
|
#
|
||||||
|
# Hooks added to views will be concatenated into a string. Hooks added to
|
||||||
|
# controllers will return an array of results.
|
||||||
|
#
|
||||||
|
# Several objects are automatically added to the call context:
|
||||||
|
#
|
||||||
|
# * project => current project
|
||||||
|
# * request => Request instance
|
||||||
|
# * controller => current Controller instance
|
||||||
|
#
|
||||||
module Helper
|
module Helper
|
||||||
def call_hook(hook, context={})
|
def call_hook(hook, context={})
|
||||||
Redmine::Hook.call_hook(hook, {:project => @project}.merge(context))
|
if is_a?(ActionController::Base)
|
||||||
|
default_context = {:controller => self, :project => @project, :request => request}
|
||||||
|
Redmine::Hook.call_hook(hook, default_context.merge(context))
|
||||||
|
else
|
||||||
|
default_context = {:controller => controller, :project => @project, :request => request}
|
||||||
|
Redmine::Hook.call_hook(hook, default_context.merge(context)).join(' ')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Redmine - project management software
|
||||||
|
# Copyright (C) 2006-2009 Jean-Philippe Lang
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
module Redmine
|
||||||
|
module Utils
|
||||||
|
class << self
|
||||||
|
# Returns the relative root url of the application
|
||||||
|
def relative_url_root
|
||||||
|
ActionController::Base.respond_to?('relative_url_root') ?
|
||||||
|
ActionController::Base.relative_url_root.to_s :
|
||||||
|
ActionController::AbstractRequest.relative_url_root.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sets the relative root url of the application
|
||||||
|
def relative_url_root=(arg)
|
||||||
|
if ActionController::Base.respond_to?('relative_url_root=')
|
||||||
|
ActionController::Base.relative_url_root=arg
|
||||||
|
else
|
||||||
|
ActionController::AbstractRequest.relative_url_root=arg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -4,7 +4,7 @@ module Redmine
|
|||||||
module VERSION #:nodoc:
|
module VERSION #:nodoc:
|
||||||
MAJOR = 0
|
MAJOR = 0
|
||||||
MINOR = 8
|
MINOR = 8
|
||||||
TINY = 1
|
TINY = 2
|
||||||
|
|
||||||
# Branch values:
|
# Branch values:
|
||||||
# * official release: nil
|
# * official release: nil
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; bord
|
|||||||
padding: 0px 0px 0px 0px;
|
padding: 0px 0px 0px 0px;
|
||||||
white-space:nowrap;
|
white-space:nowrap;
|
||||||
}
|
}
|
||||||
#top-menu a {color: #fff; padding-right: 8px; font-weight: bold;}
|
#top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
|
||||||
#top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
|
#top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
|
||||||
|
|
||||||
#account {float:right;}
|
#account {float:right;}
|
||||||
@@ -76,7 +76,7 @@ a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
|
|||||||
a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
|
a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
|
||||||
a img{ border: 0; }
|
a img{ border: 0; }
|
||||||
|
|
||||||
a.issue.closed { text-decoration: line-through; }
|
a.issue.closed, a.issue.closed:link, a.issue.closed:visited { text-decoration: line-through; }
|
||||||
|
|
||||||
/***** Tables *****/
|
/***** Tables *****/
|
||||||
table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
|
table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
|
||||||
@@ -104,6 +104,10 @@ tr.entry.file td.filename a { margin-left: 16px; }
|
|||||||
tr.changeset td.author { text-align: center; width: 15%; }
|
tr.changeset td.author { text-align: center; width: 15%; }
|
||||||
tr.changeset td.committed_on { text-align: center; width: 15%; }
|
tr.changeset td.committed_on { text-align: center; width: 15%; }
|
||||||
|
|
||||||
|
tr.file td { text-align: center; }
|
||||||
|
tr.file td.filename { text-align: left; padding-left: 24px; }
|
||||||
|
tr.file td.digest { font-size: 80%; }
|
||||||
|
|
||||||
tr.message { height: 2.6em; }
|
tr.message { height: 2.6em; }
|
||||||
tr.message td.last_message { font-size: 80%; }
|
tr.message td.last_message { font-size: 80%; }
|
||||||
tr.message.locked td.subject a { background-image: url(../images/locked.png); }
|
tr.message.locked td.subject a { background-image: url(../images/locked.png); }
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ table.filecontent th.line-num {
|
|||||||
padding-right: 3px;
|
padding-right: 3px;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
table.filecontent th.line-num a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
table.filecontent td.line-code pre {
|
table.filecontent td.line-code pre {
|
||||||
white-space: pre-wrap; /* CSS2.1 compliant */
|
white-space: pre-wrap; /* CSS2.1 compliant */
|
||||||
white-space: -moz-pre-wrap; /* Mozilla-based browsers */
|
white-space: -moz-pre-wrap; /* Mozilla-based browsers */
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
Received: from osiris ([127.0.0.1])
|
||||||
|
by OSIRIS
|
||||||
|
with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
|
||||||
|
Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
|
||||||
|
To: <redmine@somenet.foo>
|
||||||
|
Subject: New ticket on a given project
|
||||||
|
Date: Sun, 22 Jun 2008 12:28:07 +0200
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
format=flowed;
|
||||||
|
charset="iso-8859-1";
|
||||||
|
reply-type=original
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
X-Priority: 3
|
||||||
|
X-MSMail-Priority: Normal
|
||||||
|
X-Mailer: Microsoft Outlook Express 6.00.2900.2869
|
||||||
|
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
|
||||||
|
turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
|
||||||
|
blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
|
||||||
|
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
|
||||||
|
in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
|
||||||
|
sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
|
||||||
|
id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
|
||||||
|
eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
|
||||||
|
sed, mauris. Pellentesque habitant morbi tristique senectus et netus et
|
||||||
|
malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
|
||||||
|
platea dictumst.
|
||||||
|
|
||||||
|
Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
|
||||||
|
sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
|
||||||
|
Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
|
||||||
|
dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
|
||||||
|
massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
|
||||||
|
pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
|
||||||
|
|
||||||
|
Project: onlinestore
|
||||||
|
Status: Resolved
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ class IssuesControllerTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_show_export_to_pdf
|
def test_show_export_to_pdf
|
||||||
get :show, :id => 1, :format => 'pdf'
|
get :show, :id => 3, :format => 'pdf'
|
||||||
assert_response :success
|
assert_response :success
|
||||||
assert_equal 'application/pdf', @response.content_type
|
assert_equal 'application/pdf', @response.content_type
|
||||||
assert @response.body.starts_with?('%PDF')
|
assert @response.body.starts_with?('%PDF')
|
||||||
|
|||||||
@@ -64,6 +64,22 @@ class UsersControllerTest < Test::Unit::TestCase
|
|||||||
assert_equal 2, Member.find(1).role_id
|
assert_equal 2, Member.find(1).role_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_edit_with_activation_should_send_a_notification
|
||||||
|
u = User.new(:firstname => 'Foo', :lastname => 'Bar', :mail => 'foo.bar@somenet.foo', :language => 'fr')
|
||||||
|
u.login = 'foo'
|
||||||
|
u.status = User::STATUS_REGISTERED
|
||||||
|
u.save!
|
||||||
|
ActionMailer::Base.deliveries.clear
|
||||||
|
Setting.bcc_recipients = '1'
|
||||||
|
|
||||||
|
post :edit, :id => u.id, :user => {:status => User::STATUS_ACTIVE}
|
||||||
|
assert u.reload.active?
|
||||||
|
mail = ActionMailer::Base.deliveries.last
|
||||||
|
assert_not_nil mail
|
||||||
|
assert_equal ['foo.bar@somenet.foo'], mail.bcc
|
||||||
|
assert mail.body.include?(ll('fr', :notice_account_activated))
|
||||||
|
end
|
||||||
|
|
||||||
def test_destroy_membership
|
def test_destroy_membership
|
||||||
post :destroy_membership, :id => 2, :membership_id => 1
|
post :destroy_membership, :id => 2, :membership_id => 1
|
||||||
assert_redirected_to 'users/edit/2'
|
assert_redirected_to 'users/edit/2'
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ require File.dirname(__FILE__) + '/../../../test_helper'
|
|||||||
|
|
||||||
class Redmine::Hook::ManagerTest < Test::Unit::TestCase
|
class Redmine::Hook::ManagerTest < Test::Unit::TestCase
|
||||||
|
|
||||||
|
fixtures :issues
|
||||||
|
|
||||||
# Some hooks that are manually registered in these tests
|
# Some hooks that are manually registered in these tests
|
||||||
class TestHook < Redmine::Hook::Listener; end
|
class TestHook < Redmine::Hook::ViewListener; end
|
||||||
|
|
||||||
class TestHook1 < TestHook
|
class TestHook1 < TestHook
|
||||||
def view_layouts_base_html_head(context)
|
def view_layouts_base_html_head(context)
|
||||||
@@ -39,10 +41,27 @@ class Redmine::Hook::ManagerTest < Test::Unit::TestCase
|
|||||||
"Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}."
|
"Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class TestLinkToHook < TestHook
|
||||||
|
def view_layouts_base_html_head(context)
|
||||||
|
link_to('Issues', :controller => 'issues')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestHookHelperController < ActionController::Base
|
||||||
|
include Redmine::Hook::Helper
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestHookHelperView < ActionView::Base
|
||||||
|
include Redmine::Hook::Helper
|
||||||
|
end
|
||||||
|
|
||||||
Redmine::Hook.clear_listeners
|
Redmine::Hook.clear_listeners
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@hook_module = Redmine::Hook
|
@hook_module = Redmine::Hook
|
||||||
|
@hook_helper = TestHookHelperController.new
|
||||||
|
@view_hook_helper = TestHookHelperView.new(RAILS_ROOT + '/app/views')
|
||||||
end
|
end
|
||||||
|
|
||||||
def teardown
|
def teardown
|
||||||
@@ -67,17 +86,81 @@ class Redmine::Hook::ManagerTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_call_hook
|
def test_call_hook
|
||||||
@hook_module.add_listener(TestHook1)
|
@hook_module.add_listener(TestHook1)
|
||||||
assert_equal 'Test hook 1 listener.', @hook_module.call_hook(:view_layouts_base_html_head)
|
assert_equal ['Test hook 1 listener.'], @hook_helper.call_hook(:view_layouts_base_html_head)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_call_hook_with_context
|
def test_call_hook_with_context
|
||||||
@hook_module.add_listener(TestHook3)
|
@hook_module.add_listener(TestHook3)
|
||||||
assert_equal 'Context keys: bar, foo.', @hook_module.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a')
|
assert_equal ['Context keys: bar, controller, foo, project, request.'],
|
||||||
|
@hook_helper.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a')
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_call_hook_with_multiple_listeners
|
def test_call_hook_with_multiple_listeners
|
||||||
@hook_module.add_listener(TestHook1)
|
@hook_module.add_listener(TestHook1)
|
||||||
@hook_module.add_listener(TestHook2)
|
@hook_module.add_listener(TestHook2)
|
||||||
assert_equal 'Test hook 1 listener.Test hook 2 listener.', @hook_module.call_hook(:view_layouts_base_html_head)
|
assert_equal ['Test hook 1 listener.', 'Test hook 2 listener.'], @hook_helper.call_hook(:view_layouts_base_html_head)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Context: Redmine::Hook::Helper.call_hook default_url
|
||||||
|
def test_call_hook_default_url_options
|
||||||
|
@hook_module.add_listener(TestLinkToHook)
|
||||||
|
|
||||||
|
assert_equal ['<a href="/issues">Issues</a>'], @hook_helper.call_hook(:view_layouts_base_html_head)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Context: Redmine::Hook::Helper.call_hook
|
||||||
|
def test_call_hook_with_project_added_to_context
|
||||||
|
@hook_module.add_listener(TestHook3)
|
||||||
|
assert_match /project/i, @hook_helper.call_hook(:view_layouts_base_html_head)[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_call_hook_from_controller_with_controller_added_to_context
|
||||||
|
@hook_module.add_listener(TestHook3)
|
||||||
|
assert_match /controller/i, @hook_helper.call_hook(:view_layouts_base_html_head)[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_call_hook_from_controller_with_request_added_to_context
|
||||||
|
@hook_module.add_listener(TestHook3)
|
||||||
|
assert_match /request/i, @hook_helper.call_hook(:view_layouts_base_html_head)[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_call_hook_from_view_with_project_added_to_context
|
||||||
|
@hook_module.add_listener(TestHook3)
|
||||||
|
assert_match /project/i, @view_hook_helper.call_hook(:view_layouts_base_html_head)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_call_hook_from_view_with_controller_added_to_context
|
||||||
|
@hook_module.add_listener(TestHook3)
|
||||||
|
assert_match /controller/i, @view_hook_helper.call_hook(:view_layouts_base_html_head)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_call_hook_from_view_with_request_added_to_context
|
||||||
|
@hook_module.add_listener(TestHook3)
|
||||||
|
assert_match /request/i, @view_hook_helper.call_hook(:view_layouts_base_html_head)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_call_hook_from_view_should_join_responses_with_a_space
|
||||||
|
@hook_module.add_listener(TestHook1)
|
||||||
|
@hook_module.add_listener(TestHook2)
|
||||||
|
assert_equal 'Test hook 1 listener. Test hook 2 listener.',
|
||||||
|
@view_hook_helper.call_hook(:view_layouts_base_html_head)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_call_hook_should_not_change_the_default_url_for_email_notifications
|
||||||
|
issue = Issue.find(1)
|
||||||
|
|
||||||
|
ActionMailer::Base.deliveries.clear
|
||||||
|
Mailer.deliver_issue_add(issue)
|
||||||
|
mail = ActionMailer::Base.deliveries.last
|
||||||
|
|
||||||
|
@hook_module.add_listener(TestLinkToHook)
|
||||||
|
@hook_helper.call_hook(:view_layouts_base_html_head)
|
||||||
|
|
||||||
|
ActionMailer::Base.deliveries.clear
|
||||||
|
Mailer.deliver_issue_add(issue)
|
||||||
|
mail2 = ActionMailer::Base.deliveries.last
|
||||||
|
|
||||||
|
assert_equal mail.body, mail2.body
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,11 @@ class MailHandlerTest < Test::Unit::TestCase
|
|||||||
assert_equal 'New ticket on a given project', issue.subject
|
assert_equal 'New ticket on a given project', issue.subject
|
||||||
assert_equal User.find_by_login('jsmith'), issue.author
|
assert_equal User.find_by_login('jsmith'), issue.author
|
||||||
assert_equal Project.find(2), issue.project
|
assert_equal Project.find(2), issue.project
|
||||||
|
assert_equal IssueStatus.find_by_name('Resolved'), issue.status
|
||||||
assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
|
assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
|
||||||
|
# keywords should be removed from the email body
|
||||||
|
assert !issue.description.match(/^Project:/i)
|
||||||
|
assert !issue.description.match(/^Status:/i)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_add_issue_with_status
|
def test_add_issue_with_status
|
||||||
@@ -111,6 +115,7 @@ class MailHandlerTest < Test::Unit::TestCase
|
|||||||
issue.reload
|
issue.reload
|
||||||
assert_equal 'New ticket with custom field values', issue.subject
|
assert_equal 'New ticket with custom field values', issue.subject
|
||||||
assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
|
assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
|
||||||
|
assert !issue.description.match(/^searchable field:/i)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_add_issue_with_cc
|
def test_add_issue_with_cc
|
||||||
@@ -122,6 +127,11 @@ class MailHandlerTest < Test::Unit::TestCase
|
|||||||
assert_equal 1, issue.watchers.size
|
assert_equal 1, issue.watchers.size
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_add_issue_without_from_header
|
||||||
|
Role.anonymous.add_permission!(:add_issues)
|
||||||
|
assert_equal false, submit_email('ticket_without_from_header.eml')
|
||||||
|
end
|
||||||
|
|
||||||
def test_add_issue_note
|
def test_add_issue_note
|
||||||
journal = submit_email('ticket_reply.eml')
|
journal = submit_email('ticket_reply.eml')
|
||||||
assert journal.is_a?(Journal)
|
assert journal.is_a?(Journal)
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ class RepositoryTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_scan_changesets_for_issue_ids
|
def test_scan_changesets_for_issue_ids
|
||||||
Setting.default_language = 'en'
|
Setting.default_language = 'en'
|
||||||
|
set_language_if_valid('en')
|
||||||
|
|
||||||
# choosing a status to apply to fix issues
|
# choosing a status to apply to fix issues
|
||||||
Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
|
Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
|
||||||
|
|||||||
Reference in New Issue
Block a user