Compare commits

..
1 Commits
Author SHA1 Message Date
Jean-Philippe Lang e5ac5000e5 tagged version 0.8.0
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/0.8.0@2207 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-30 11:28:55 +00:00
114 changed files with 788 additions and 1353 deletions
-1
View File
@@ -86,7 +86,6 @@ class AdminController < ApplicationController
@flags = { @flags = {
:default_admin_changed => User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?, :default_admin_changed => User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?,
:file_repository_writable => File.writable?(Attachment.storage_path), :file_repository_writable => File.writable?(Attachment.storage_path),
:plugin_assets_writable => File.writable?(Engines.public_directory),
:rmagick_available => Object.const_defined?(:Magick) :rmagick_available => Object.const_defined?(:Magick)
} }
end end
+1 -5
View File
@@ -175,7 +175,6 @@ class ApplicationController < ActionController::Base
# TODO: move to model # TODO: move to model
def attach_files(obj, attachments) def attach_files(obj, attachments)
attached = [] attached = []
unsaved = []
if attachments && attachments.is_a?(Hash) if attachments && attachments.is_a?(Hash)
attachments.each_value do |attachment| attachments.each_value do |attachment|
file = attachment['file'] file = attachment['file']
@@ -184,10 +183,7 @@ class ApplicationController < ActionController::Base
:file => file, :file => file,
:description => attachment['description'].to_s.strip, :description => attachment['description'].to_s.strip,
:author => User.current) :author => User.current)
a.new_record? ? (unsaved << a) : (attached << a) attached << a unless a.new_record?
end
if unsaved.any?
flash[:warning] = l(:warning_attachments_not_saved, unsaved.size)
end end
end end
attached attached
+9 -28
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,11 +17,7 @@
class AttachmentsController < ApplicationController class AttachmentsController < ApplicationController
before_filter :find_project before_filter :find_project
before_filter :read_authorize, :except => :destroy
before_filter :delete_authorize, :only => :destroy
verify :method => :post, :only => :destroy
def show def show
if @attachment.is_diff? if @attachment.is_diff?
@diff = File.new(@attachment.diskfile, "rb").read @diff = File.new(@attachment.diskfile, "rb").read
@@ -35,40 +31,25 @@ class AttachmentsController < ApplicationController
end end
def download def download
if @attachment.container.is_a?(Version) || @attachment.container.is_a?(Project) @attachment.increment_download if @attachment.container.is_a?(Version)
@attachment.increment_download
end
# images are sent inline # images are sent inline
send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename), send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
:type => @attachment.content_type, :type => @attachment.content_type,
:disposition => (@attachment.image? ? 'inline' : 'attachment') :disposition => (@attachment.image? ? 'inline' : 'attachment')
end end
def destroy
# Make sure association callbacks are called
@attachment.container.attachments.delete(@attachment)
redirect_to :back
rescue ::ActionController::RedirectBackError
redirect_to :controller => 'projects', :action => 'show', :id => @project
end
private private
def find_project def find_project
@attachment = Attachment.find(params[:id]) @attachment = Attachment.find(params[:id])
# Show 404 if the filename in the url is wrong # Show 404 if the filename in the url is wrong
raise ActiveRecord::RecordNotFound if params[:filename] && params[:filename] != @attachment.filename raise ActiveRecord::RecordNotFound if params[:filename] && params[:filename] != @attachment.filename
@project = @attachment.project @project = @attachment.project
permission = @attachment.container.is_a?(Version) ? :view_files : "view_#{@attachment.container.class.name.underscore.pluralize}".to_sym
allowed = User.current.allowed_to?(permission, @project)
allowed ? true : (User.current.logged? ? render_403 : require_login)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def read_authorize
@attachment.visible? ? true : deny_access
end
def delete_authorize
@attachment.deletable? ? true : deny_access
end
end end
+5
View File
@@ -71,6 +71,11 @@ class DocumentsController < ApplicationController
Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('document_added') Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('document_added')
redirect_to :action => 'show', :id => @document redirect_to :action => 'show', :id => @document
end end
def destroy_attachment
@document.attachments.find(params[:attachment_id]).destroy
redirect_to :action => 'show', :id => @document
end
private private
def find_project def find_project
+20 -12
View File
@@ -18,7 +18,7 @@
class IssuesController < ApplicationController class IssuesController < ApplicationController
menu_item :new_issue, :only => :new menu_item :new_issue, :only => :new
before_filter :find_issue, :only => [:show, :edit, :reply] before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment]
before_filter :find_issues, :only => [:bulk_edit, :move, :destroy] before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
before_filter :find_project, :only => [:new, :update_form, :preview] before_filter :find_project, :only => [:new, :update_form, :preview]
before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu] before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
@@ -30,6 +30,8 @@ class IssuesController < ApplicationController
include ProjectsHelper include ProjectsHelper
helper :custom_fields helper :custom_fields
include CustomFieldsHelper include CustomFieldsHelper
helper :ifpdf
include IfpdfHelper
helper :issue_relations helper :issue_relations
include IssueRelationsHelper include IssueRelationsHelper
helper :watchers helper :watchers
@@ -41,7 +43,6 @@ class IssuesController < ApplicationController
include SortHelper include SortHelper
include IssuesHelper include IssuesHelper
helper :timelog helper :timelog
include Redmine::Export::PDF
def index def index
retrieve_query retrieve_query
@@ -67,7 +68,7 @@ class IssuesController < ApplicationController
format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? } format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') } format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
format.pdf { send_data(issues_to_pdf(@issues, @project), :type => 'application/pdf', :filename => 'export.pdf') } format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
end end
else else
# Send html if the query is not valid # Send html if the query is not valid
@@ -105,7 +106,7 @@ class IssuesController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { render :template => 'issues/show.rhtml' } format.html { render :template => 'issues/show.rhtml' }
format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' } format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
end end
end end
@@ -122,10 +123,7 @@ class IssuesController < ApplicationController
render :nothing => true, :layout => true render :nothing => true, :layout => true
return return
end end
if params[:issue].is_a?(Hash) @issue.attributes = params[:issue]
@issue.attributes = params[:issue]
@issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
end
@issue.author = User.current @issue.author = User.current
default_status = IssueStatus.default default_status = IssueStatus.default
@@ -147,8 +145,7 @@ class IssuesController < ApplicationController
attach_files(@issue, params[:attachments]) attach_files(@issue, params[:attachments])
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } : redirect_to :controller => 'issues', :action => 'show', :id => @issue
{ :action => 'show', :id => @issue })
return return
end end
end end
@@ -186,7 +183,7 @@ class IssuesController < ApplicationController
if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
# Log spend time # Log spend time
if User.current.allowed_to?(:log_time, @project) if current_role.allowed_to?(:log_time)
@time_entry.save @time_entry.save
end end
if !journal.new_record? if !journal.new_record?
@@ -318,6 +315,17 @@ class IssuesController < ApplicationController
@issues.each(&:destroy) @issues.each(&:destroy)
redirect_to :action => 'index', :project_id => @project redirect_to :action => 'index', :project_id => @project
end end
def destroy_attachment
a = @issue.attachments.find(params[:attachment_id])
a.destroy
journal = @issue.init_journal(User.current)
journal.details << JournalDetail.new(:property => 'attachment',
:prop_key => a.id,
:old_value => a.filename)
journal.save
redirect_to :action => 'show', :id => @issue
end
def gantt def gantt
@gantt = Redmine::Helpers::Gantt.new(params) @gantt = Redmine::Helpers::Gantt.new(params)
@@ -346,7 +354,7 @@ class IssuesController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? } format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image') format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image')
format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") } format.pdf { send_data(render(:template => "issues/gantt.rfpdf", :layout => false), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
end end
end end
+6 -13
View File
@@ -33,6 +33,8 @@ class ProjectsController < ApplicationController
include SortHelper include SortHelper
helper :custom_fields helper :custom_fields
include CustomFieldsHelper include CustomFieldsHelper
helper :ifpdf
include IfpdfHelper
helper :issues helper :issues
helper IssuesHelper helper IssuesHelper
helper :queries helper :queries
@@ -82,11 +84,6 @@ class ProjectsController < ApplicationController
# Show @project # Show @project
def show def show
if params[:jump]
# try to redirect to the requested menu item
redirect_to_project_menu_item(@project, params[:jump]) && return
end
@members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role} @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
@subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current)) @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@@ -191,13 +188,10 @@ class ProjectsController < ApplicationController
def add_file def add_file
if request.post? if request.post?
container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id])) @version = @project.versions.find_by_id(params[:version_id])
attachments = attach_files(container, params[:attachments]) attachments = attach_files(@version, params[:attachments])
if !attachments.empty? && Setting.notified_events.include?('file_added') Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('file_added')
Mailer.deliver_attachments_added(attachments)
end
redirect_to :controller => 'projects', :action => 'list_files', :id => @project redirect_to :controller => 'projects', :action => 'list_files', :id => @project
return
end end
@versions = @project.versions.sort @versions = @project.versions.sort
end end
@@ -209,8 +203,7 @@ class ProjectsController < ApplicationController
'size' => "#{Attachment.table_name}.filesize", 'size' => "#{Attachment.table_name}.filesize",
'downloads' => "#{Attachment.table_name}.downloads" 'downloads' => "#{Attachment.table_name}.downloads"
@containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)] @versions = @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
@containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
render :layout => !request.xhr? render :layout => !request.xhr?
end end
@@ -127,9 +127,6 @@ class RepositoriesController < ApplicationController
end end
def annotate def annotate
@entry = @repository.entry(@path, @rev)
show_error_not_found and return unless @entry
@annotate = @repository.scm.annotate(@path, @rev) @annotate = @repository.scm.annotate(@path, @rev)
render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty? render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
end end
+1 -1
View File
@@ -40,7 +40,7 @@ class SettingsController < ApplicationController
@options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] } @options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] }
@deliveries = ActionMailer::Base.perform_deliveries @deliveries = ActionMailer::Base.perform_deliveries
@guessed_host_and_path = request.host_with_port.dup @guessed_host_and_path = request.host_with_port
@guessed_host_and_path << ('/'+ request.relative_url_root.gsub(%r{^\/}, '')) unless request.relative_url_root.blank? @guessed_host_and_path << ('/'+ request.relative_url_root.gsub(%r{^\/}, '')) unless request.relative_url_root.blank?
end end
+6
View File
@@ -37,6 +37,12 @@ class VersionsController < ApplicationController
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end end
def destroy_file
@version.attachments.find(params[:attachment_id]).destroy
flash[:notice] = l(:notice_successful_delete)
redirect_to :controller => 'projects', :action => 'list_files', :id => @project
end
def status_by def status_by
respond_to do |format| respond_to do |format|
format.html { render :action => 'show' } format.html { render :action => 'show' }
+8 -1
View File
@@ -21,7 +21,7 @@ class WikiController < ApplicationController
before_filter :find_wiki, :authorize before_filter :find_wiki, :authorize
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy] before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
verify :method => :post, :only => [:destroy, :protect], :redirect_to => { :action => :index } verify :method => :post, :only => [:destroy, :destroy_attachment, :protect], :redirect_to => { :action => :index }
helper :attachments helper :attachments
include AttachmentsHelper include AttachmentsHelper
@@ -181,6 +181,13 @@ class WikiController < ApplicationController
redirect_to :action => 'index', :page => @page.title redirect_to :action => 'index', :page => @page.title
end end
def destroy_attachment
@page = @wiki.find_page(params[:page])
return render_403 unless editable?
@page.attachments.find(params[:attachment_id]).destroy
redirect_to :action => 'index', :page => @page.title
end
private private
def find_wiki def find_wiki
+4 -13
View File
@@ -147,15 +147,6 @@ module ApplicationHelper
end end
content content
end end
# Renders flash messages
def render_flash_messages
s = ''
flash.each do |k,v|
s << content_tag('div', v, :class => "flash #{k}")
end
s
end
# Truncates and returns the string as a single line # Truncates and returns the string as a single line
def truncate_single_line(string, *args) def truncate_single_line(string, *args)
@@ -291,15 +282,16 @@ module ApplicationHelper
attachments = attachments.sort_by(&:created_on).reverse attachments = attachments.sort_by(&:created_on).reverse
text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m| text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
style = $1 style = $1
filename = $6.downcase filename = $6
rf = Regexp.new(Regexp.escape(filename), Regexp::IGNORECASE)
# search for the picture in attachments # search for the picture in attachments
if found = attachments.detect { |att| att.filename.downcase == filename } if found = attachments.detect { |att| att.filename =~ rf }
image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1") desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
alt = desc.blank? ? nil : "(#{desc})" alt = desc.blank? ? nil : "(#{desc})"
"!#{style}#{image_url}#{alt}!" "!#{style}#{image_url}#{alt}!"
else else
m "!#{style}#{filename}!"
end end
end end
end end
@@ -534,7 +526,6 @@ module ApplicationHelper
def back_url_hidden_field_tag def back_url_hidden_field_tag
back_url = params[:back_url] || request.env['HTTP_REFERER'] back_url = params[:back_url] || request.env['HTTP_REFERER']
back_url = CGI.unescape(back_url.to_s)
hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank? hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
end end
+4 -9
View File
@@ -16,15 +16,10 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module AttachmentsHelper module AttachmentsHelper
# Displays view/delete links to the attachments of the given object # displays the links to a collection of attachments
# Options: def link_to_attachments(attachments, options = {})
# :author -- author names are not displayed if set to false if attachments.any?
def link_to_attachments(container, options = {}) render :partial => 'attachments/links', :locals => {:attachments => attachments, :options => options}
options.assert_valid_keys(:author)
if container.attachments.any?
options = {:deletable => container.attachments_deletable?, :author => true}.merge(options)
render :partial => 'attachments/links', :locals => {:attachments => container.attachments, :options => options}
end end
end end
+85
View File
@@ -0,0 +1,85 @@
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv'
require 'rfpdf/chinese'
module IfpdfHelper
class IFPDF < FPDF
include GLoc
attr_accessor :footer_date
def initialize(lang)
super()
set_language_if_valid lang
case current_language.to_s
when 'ja'
extend(PDF_Japanese)
AddSJISFont()
@font_for_content = 'SJIS'
@font_for_footer = 'SJIS'
when 'zh'
extend(PDF_Chinese)
AddGBFont()
@font_for_content = 'GB'
@font_for_footer = 'GB'
when 'zh-tw'
extend(PDF_Chinese)
AddBig5Font()
@font_for_content = 'Big5'
@font_for_footer = 'Big5'
else
@font_for_content = 'Arial'
@font_for_footer = 'Helvetica'
end
SetCreator(Redmine::Info.app_name)
SetFont(@font_for_content)
end
def SetFontStyle(style, size)
SetFont(@font_for_content, style, size)
end
def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
@ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
# these quotation marks are not correctly rendered in the pdf
txt = txt.gsub(/[“”]/, '"') if txt
txt = begin
# 0x5c char handling
txtar = txt.split('\\')
txtar << '' if txt[-1] == ?\\
txtar.collect {|x| @ic.iconv(x)}.join('\\').gsub(/\\/, "\\\\\\\\")
rescue
txt
end || ''
super w,h,txt,border,ln,align,fill,link
end
def Footer
SetFont(@font_for_footer, 'I', 8)
SetY(-15)
SetX(15)
Cell(0, 5, @footer_date, 0, 0, 'L')
SetY(-15)
SetX(-30)
Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
end
end
end
-1
View File
@@ -36,7 +36,6 @@ module IssuesHelper
# Returns a string of css classes that apply to the given issue # Returns a string of css classes that apply to the given issue
def css_issue_classes(issue) def css_issue_classes(issue)
s = "issue status-#{issue.status.position} priority-#{issue.priority.position}" s = "issue status-#{issue.status.position} priority-#{issue.priority.position}"
s << ' closed' if issue.closed?
s << ' overdue' if issue.overdue? s << ' overdue' if issue.overdue?
s s
end end
+1 -3
View File
@@ -16,8 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module TimelogHelper module TimelogHelper
include ApplicationHelper
def render_timelog_breadcrumb def render_timelog_breadcrumb
links = [] links = []
links << link_to(l(:label_project_all), {:project_id => nil, :issue_id => nil}) links << link_to(l(:label_project_all), {:project_id => nil, :issue_id => nil})
@@ -83,7 +81,7 @@ module TimelogHelper
csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end } csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
# csv lines # csv lines
entries.each do |entry| entries.each do |entry|
fields = [format_date(entry.spent_on), fields = [l_date(entry.spent_on),
entry.user, entry.user,
entry.activity, entry.activity,
entry.project, entry.project,
-8
View File
@@ -98,14 +98,6 @@ class Attachment < ActiveRecord::Base
container.project container.project
end end
def visible?(user=User.current)
container.attachments_visible?(user)
end
def deletable?(user=User.current)
container.attachments_deletable?(user)
end
def image? def image?
self.filename =~ /\.(jpe?g|gif|png)$/i self.filename =~ /\.(jpe?g|gif|png)$/i
end end
+1 -1
View File
@@ -18,7 +18,7 @@
class Document < ActiveRecord::Base class Document < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id" belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
acts_as_attachable :delete_permission => :manage_documents has_many :attachments, :as => :container, :dependent => :destroy
acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"}, acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"},
+2 -13
View File
@@ -26,13 +26,13 @@ class Issue < ActiveRecord::Base
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
has_many :journals, :as => :journalized, :dependent => :destroy has_many :journals, :as => :journalized, :dependent => :destroy
has_many :attachments, :as => :container, :dependent => :destroy
has_many :time_entries, :dependent => :delete_all has_many :time_entries, :dependent => :delete_all
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC" has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
acts_as_attachable :after_remove => :attachment_removed
acts_as_customizable acts_as_customizable
acts_as_watchable acts_as_watchable
acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"], acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
@@ -45,7 +45,7 @@ class Issue < ActiveRecord::Base
acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]}, acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
:author_key => :author_id :author_key => :author_id
validates_presence_of :subject, :priority, :project, :tracker, :author, :status validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status
validates_length_of :subject, :maximum => 255 validates_length_of :subject, :maximum => 255
validates_inclusion_of :done_ratio, :in => 0..100 validates_inclusion_of :done_ratio, :in => 0..100
validates_numericality_of :estimated_hours, :allow_nil => true validates_numericality_of :estimated_hours, :allow_nil => true
@@ -266,15 +266,4 @@ class Issue < ActiveRecord::Base
def to_s def to_s
"#{tracker} ##{id}: #{subject}" "#{tracker} ##{id}: #{subject}"
end end
private
# Callback on attachment deletion
def attachment_removed(obj)
journal = init_journal(User.current)
journal.details << JournalDetail.new(:property => 'attachment',
:prop_key => obj.id,
:old_value => obj.filename)
journal.save
end
end end
+2 -9
View File
@@ -90,13 +90,6 @@ class MailHandler < ActionMailer::Base
end end
issue.subject = email.subject.chomp.toutf8 issue.subject = email.subject.chomp.toutf8
issue.description = plain_text_body issue.description = plain_text_body
# custom fields
issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
if value = get_keyword(c.name, :override => true)
h[c.id] = value
end
h
end
issue.save! issue.save!
add_attachments(issue) add_attachments(issue)
logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
@@ -162,8 +155,8 @@ class MailHandler < ActionMailer::Base
end end
end end
def get_keyword(attr, options={}) def get_keyword(attr)
if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i if @@handler_options[:allow_override].include?(attr.to_s) && plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i
$1.strip $1.strip
elsif !@@handler_options[:issue][attr].blank? elsif !@@handler_options[:issue][attr].blank?
@@handler_options[:issue][attr] @@handler_options[:issue][attr]
+3 -9
View File
@@ -28,7 +28,6 @@ class Mailer < ActionMailer::Base
'Issue-Author' => issue.author.login 'Issue-Author' => issue.author.login
redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
recipients issue.recipients recipients issue.recipients
cc(issue.watcher_recipients - @recipients)
subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}" subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
body :issue => issue, body :issue => issue,
:issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue) :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
@@ -40,7 +39,6 @@ class Mailer < ActionMailer::Base
'Issue-Id' => issue.id, 'Issue-Id' => issue.id,
'Issue-Author' => issue.author.login 'Issue-Author' => issue.author.login
redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
@author = journal.user
recipients issue.recipients recipients issue.recipients
# Watchers in cc # Watchers in cc
cc(issue.watcher_recipients - @recipients) cc(issue.watcher_recipients - @recipients)
@@ -75,9 +73,6 @@ class Mailer < ActionMailer::Base
added_to = '' added_to = ''
added_to_url = '' added_to_url = ''
case container.class.name case container.class.name
when 'Project'
added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
added_to = "#{l(:label_project)}: #{container}"
when 'Version' when 'Version'
added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id) added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
added_to = "#{l(:label_version)}: #{container.name}" added_to = "#{l(:label_version)}: #{container.name}"
@@ -210,10 +205,9 @@ class Mailer < ActionMailer::Base
def create_mail def create_mail
# Removes the current user from the recipients and cc # Removes the current user from the recipients and cc
# if he doesn't want to receive notifications about what he does # if he doesn't want to receive notifications about what he does
@author ||= User.current if User.current.pref[:no_self_notified]
if @author.pref[:no_self_notified] recipients.delete(User.current.mail) if recipients
recipients.delete(@author.mail) if recipients cc.delete(User.current.mail) if cc
cc.delete(@author.mail) if cc
end end
# Blind carbon copy recipients # Blind carbon copy recipients
if Setting.bcc_recipients? if Setting.bcc_recipients?
+2 -2
View File
@@ -19,11 +19,11 @@ class Message < ActiveRecord::Base
belongs_to :board belongs_to :board
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC" acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC"
acts_as_attachable has_many :attachments, :as => :container, :dependent => :destroy
belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id' belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id'
acts_as_searchable :columns => ['subject', 'content'], acts_as_searchable :columns => ['subject', 'content'],
:include => {:board => :project}, :include => {:board, :project},
:project_key => 'project_id', :project_key => 'project_id',
:date_column => "#{table_name}.created_on" :date_column => "#{table_name}.created_on"
acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"},
+1 -3
View File
@@ -44,8 +44,6 @@ class Project < ActiveRecord::Base
:association_foreign_key => 'custom_field_id' :association_foreign_key => 'custom_field_id'
acts_as_tree :order => "name", :counter_cache => true acts_as_tree :order => "name", :counter_cache => true
acts_as_attachable :view_permission => :view_files,
:delete_permission => :manage_files
acts_as_customizable acts_as_customizable
acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
@@ -60,7 +58,7 @@ class Project < ActiveRecord::Base
validates_associated :repository, :wiki validates_associated :repository, :wiki
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_length_of :homepage, :maximum => 255 validates_length_of :homepage, :maximum => 255
validates_length_of :identifier, :in => 2..20 validates_length_of :identifier, :in => 3..20
validates_format_of :identifier, :with => /^[a-z0-9\-]*$/ validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
before_destroy :delete_all_members before_destroy :delete_all_members
+2 -2
View File
@@ -31,9 +31,9 @@ class Role < ActiveRecord::Base
raise "Can not copy workflow from a #{role.class}" unless role.is_a?(Role) raise "Can not copy workflow from a #{role.class}" unless role.is_a?(Role)
raise "Can not copy workflow from/to an unsaved role" if proxy_owner.new_record? || role.new_record? raise "Can not copy workflow from/to an unsaved role" if proxy_owner.new_record? || role.new_record?
clear clear
connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, old_status_id, new_status_id, role_id)" + connection.insert "INSERT INTO workflows (tracker_id, old_status_id, new_status_id, role_id)" +
" SELECT tracker_id, old_status_id, new_status_id, #{proxy_owner.id}" + " SELECT tracker_id, old_status_id, new_status_id, #{proxy_owner.id}" +
" FROM #{Workflow.table_name}" + " FROM workflows" +
" WHERE role_id = #{role.id}" " WHERE role_id = #{role.id}"
end end
end end
+2 -2
View File
@@ -32,7 +32,7 @@ class TimeEntry < ActiveRecord::Base
:description => :comments :description => :comments
validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
validates_numericality_of :hours, :allow_nil => true, :message => :activerecord_error_invalid validates_numericality_of :hours, :allow_nil => true
validates_length_of :comments, :maximum => 255, :allow_nil => true validates_length_of :comments, :maximum => 255, :allow_nil => true
def after_initialize def after_initialize
@@ -54,7 +54,7 @@ class TimeEntry < ActiveRecord::Base
end end
def hours=(h) def hours=(h)
write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h) write_attribute :hours, (h.is_a?(String) ? h.to_hours : h)
end end
# tyear, tmonth, tweek assigned where setting spent_on attributes # tyear, tmonth, tweek assigned where setting spent_on attributes
+2 -2
View File
@@ -23,9 +23,9 @@ class Tracker < ActiveRecord::Base
raise "Can not copy workflow from a #{tracker.class}" unless tracker.is_a?(Tracker) raise "Can not copy workflow from a #{tracker.class}" unless tracker.is_a?(Tracker)
raise "Can not copy workflow from/to an unsaved tracker" if proxy_owner.new_record? || tracker.new_record? raise "Can not copy workflow from/to an unsaved tracker" if proxy_owner.new_record? || tracker.new_record?
clear clear
connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, old_status_id, new_status_id, role_id)" + connection.insert "INSERT INTO workflows (tracker_id, old_status_id, new_status_id, role_id)" +
" SELECT #{proxy_owner.id}, old_status_id, new_status_id, role_id" + " SELECT #{proxy_owner.id}, old_status_id, new_status_id, role_id" +
" FROM #{Workflow.table_name}" + " FROM workflows" +
" WHERE tracker_id = #{tracker.id}" " WHERE tracker_id = #{tracker.id}"
end end
end end
+1 -2
View File
@@ -19,8 +19,7 @@ class Version < ActiveRecord::Base
before_destroy :check_integrity before_destroy :check_integrity
belongs_to :project belongs_to :project
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id' has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
acts_as_attachable :view_permission => :view_files, has_many :attachments, :as => :container, :dependent => :destroy
:delete_permission => :manage_files
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id] validates_uniqueness_of :name, :scope => [:project_id]
+1 -5
View File
@@ -21,7 +21,7 @@ require 'enumerator'
class WikiPage < ActiveRecord::Base class WikiPage < ActiveRecord::Base
belongs_to :wiki belongs_to :wiki
has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
acts_as_attachable :delete_permission => :delete_wiki_pages_attachments has_many :attachments, :as => :container, :dependent => :destroy
acts_as_tree :order => 'title' acts_as_tree :order => 'title'
acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"}, acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
@@ -111,10 +111,6 @@ class WikiPage < ActiveRecord::Base
def editable_by?(usr) def editable_by?(usr)
!protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project) !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
end end
def attachments_deletable?(usr=User.current)
editable_by?(usr) && super(usr)
end
def parent_title def parent_title
@parent_title || (self.parent && self.parent.pretty_title) @parent_title || (self.parent && self.parent.pretty_title)
+1 -2
View File
@@ -4,8 +4,7 @@
<table class="list"> <table class="list">
<tr class="odd"><td><%= l(:text_default_administrator_account_changed) %></td><td><%= image_tag (@flags[:default_admin_changed] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr> <tr class="odd"><td><%= l(:text_default_administrator_account_changed) %></td><td><%= image_tag (@flags[:default_admin_changed] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
<tr class="even"><td><%= l(:text_file_repository_writable) %> (<%= Attachment.storage_path %>)</td><td><%= image_tag (@flags[:file_repository_writable] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr> <tr class="even"><td><%= l(:text_file_repository_writable) %></td><td><%= image_tag (@flags[:file_repository_writable] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
<tr class="even"><td><%= l(:text_plugin_assets_writable) %> (<%= Engines.public_directory %>)</td><td><%= image_tag (@flags[:plugin_assets_writable] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
<tr class="odd"><td><%= l(:text_rmagick_available) %></td><td><%= image_tag (@flags[:rmagick_available] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr> <tr class="odd"><td><%= l(:text_rmagick_available) %></td><td><%= image_tag (@flags[:rmagick_available] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
</table> </table>
+3 -3
View File
@@ -3,14 +3,14 @@
<p><%= link_to_attachment attachment, :class => 'icon icon-attachment' -%> <p><%= link_to_attachment attachment, :class => 'icon icon-attachment' -%>
<%= h(" - #{attachment.description}") unless attachment.description.blank? %> <%= h(" - #{attachment.description}") unless attachment.description.blank? %>
<span class="size">(<%= number_to_human_size attachment.filesize %>)</span> <span class="size">(<%= number_to_human_size attachment.filesize %>)</span>
<% if options[:deletable] %> <% if options[:delete_url] %>
<%= link_to image_tag('delete.png'), {:controller => 'attachments', :action => 'destroy', :id => attachment}, <%= link_to image_tag('delete.png'), options[:delete_url].update({:attachment_id => attachment}),
:confirm => l(:text_are_you_sure), :confirm => l(:text_are_you_sure),
:method => :post, :method => :post,
:class => 'delete', :class => 'delete',
:title => l(:button_delete) %> :title => l(:button_delete) %>
<% end %> <% end %>
<% if options[:author] %> <% unless options[:no_author] %>
<span class="author"><%= attachment.author %>, <%= format_time(attachment.created_on) %></span> <span class="author"><%= attachment.author %>, <%= format_time(attachment.created_on) %></span>
<% end %> <% end %>
</p> </p>
+1 -1
View File
@@ -12,7 +12,7 @@
</div> </div>
<h3><%= l(:label_attachment_plural) %></h3> <h3><%= l(:label_attachment_plural) %></h3>
<%= link_to_attachments @document %> <%= link_to_attachments @attachments, :delete_url => (authorize_for('documents', 'destroy_attachment') ? {:controller => 'documents', :action => 'destroy_attachment', :id => @document} : nil) %>
<% if authorize_for('documents', 'add_attachment') %> <% if authorize_for('documents', 'add_attachment') %>
<p><%= link_to l(:label_attachment_new), {}, :onclick => "Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;", <p><%= link_to l(:label_attachment_new), {}, :onclick => "Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;",
+1 -11
View File
@@ -8,7 +8,7 @@
<div id="issue_descr_fields" <%= 'style="display:none"' unless @issue.new_record? || @issue.errors.any? %>> <div id="issue_descr_fields" <%= 'style="display:none"' unless @issue.new_record? || @issue.errors.any? %>>
<p><%= f.text_field :subject, :size => 80, :required => true %></p> <p><%= f.text_field :subject, :size => 80, :required => true %></p>
<p><%= f.text_area :description, <p><%= f.text_area :description, :required => true,
:cols => 60, :cols => 60,
:rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min), :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
:accesskey => accesskey(:edit), :accesskey => accesskey(:edit),
@@ -24,13 +24,11 @@
<p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p> <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p>
<p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p> <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
<% unless @project.issue_categories.empty? %>
<p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %> <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
<%= prompt_to_remote(l(:label_issue_category_new), <%= prompt_to_remote(l(:label_issue_category_new),
l(:label_issue_category_new), 'category[name]', l(:label_issue_category_new), 'category[name]',
{:controller => 'projects', :action => 'add_issue_category', :id => @project}, {:controller => 'projects', :action => 'add_issue_category', :id => @project},
:class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p> :class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
<% end %>
<%= content_tag('p', f.select(:fixed_version_id, <%= content_tag('p', f.select(:fixed_version_id,
(@project.versions.sort.collect {|v| [v.name, v.id]}), (@project.versions.sort.collect {|v| [v.name, v.id]}),
{ :include_blank => true })) unless @project.versions.empty? %> { :include_blank => true })) unless @project.versions.empty? %>
@@ -50,14 +48,6 @@
<p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p> <p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
<% end %> <% end %>
<% if @issue.new_record? && User.current.allowed_to?(:add_issue_watchers, @project) -%>
<p><label><%= l(:label_issue_watchers) %></label>
<% @issue.project.users.sort.each do |user| -%>
<label class="floating"><%= check_box_tag 'issue[watcher_user_ids][]', user.id, @issue.watcher_user_ids.include?(user.id) %> <%=h user %></label>
<% end -%>
</p>
<% end %>
<%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %> <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %>
<%= wikitoolbar_for 'issue_description' %> <%= wikitoolbar_for 'issue_description' %>
-1
View File
@@ -44,7 +44,6 @@
<fieldset><legend><%= l(:field_notes) %></legend> <fieldset><legend><%= l(:field_notes) %></legend>
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %> <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
<%= wikitoolbar_for 'notes' %> <%= wikitoolbar_for 'notes' %>
</fieldset>
</div> </div>
<p><%= submit_tag l(:button_submit) %> <p><%= submit_tag l(:button_submit) %>
+188
View File
@@ -0,0 +1,188 @@
<%
pdf=IfpdfHelper::IFPDF.new(current_language)
pdf.SetTitle("#{l(:label_gantt)} #{@project}")
pdf.AliasNbPages
pdf.footer_date = format_date(Date.today)
pdf.AddPage("L")
pdf.SetFontStyle('B',12)
pdf.SetX(15)
pdf.Cell(70, 20, @project.to_s)
pdf.Ln
pdf.SetFontStyle('B',9)
subject_width = 70
header_heigth = 5
headers_heigth = header_heigth
show_weeks = false
show_days = false
if @gantt.months < 7
show_weeks = true
headers_heigth = 2*header_heigth
if @gantt.months < 3
show_days = true
headers_heigth = 3*header_heigth
end
end
g_width = 210
zoom = (g_width) / (@gantt.date_to - @gantt.date_from + 1)
g_height = 120
t_height = g_height + headers_heigth
y_start = pdf.GetY
#
# Months headers
#
month_f = @gantt.date_from
left = subject_width
height = header_heigth
@gantt.months.times do
width = ((month_f >> 1) - month_f) * zoom
pdf.SetY(y_start)
pdf.SetX(left)
pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
left = left + width
month_f = month_f >> 1
end
#
# Weeks headers
#
if show_weeks
left = subject_width
height = header_heigth
if @gantt.date_from.cwday == 1
# @gantt.date_from is monday
week_f = @gantt.date_from
else
# find next monday after @gantt.date_from
week_f = @gantt.date_from + (7 - @gantt.date_from.cwday + 1)
width = (7 - @gantt.date_from.cwday + 1) * zoom-1
pdf.SetY(y_start + header_heigth)
pdf.SetX(left)
pdf.Cell(width + 1, height, "", "LTR")
left = left + width+1
end
while week_f <= @gantt.date_to
width = (week_f + 6 <= @gantt.date_to) ? 7 * zoom : (@gantt.date_to - week_f + 1) * zoom
pdf.SetY(y_start + header_heigth)
pdf.SetX(left)
pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
left = left + width
week_f = week_f+7
end
end
#
# Days headers
#
if show_days
left = subject_width
height = header_heigth
wday = @gantt.date_from.cwday
pdf.SetFontStyle('B',7)
(@gantt.date_to - @gantt.date_from + 1).to_i.times do
width = zoom
pdf.SetY(y_start + 2 * header_heigth)
pdf.SetX(left)
pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
left = left + width
wday = wday + 1
wday = 1 if wday > 7
end
end
pdf.SetY(y_start)
pdf.SetX(15)
pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
#
# Tasks
#
top = headers_heigth + y_start
pdf.SetFontStyle('B',7)
@gantt.events.each do |i|
pdf.SetY(top)
pdf.SetX(15)
if i.is_a? Issue
pdf.Cell(subject_width-15, 5, "#{i.tracker.name} #{i.id}: #{i.subject}".sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)'), "LR")
else
pdf.Cell(subject_width-15, 5, "#{l(:label_version)}: #{i.name}", "LR")
end
pdf.SetY(top)
pdf.SetX(subject_width)
pdf.Cell(g_width, 5, "", "LR")
pdf.SetY(top+1.5)
if i.is_a? Issue
i_start_date = (i.start_date >= @gantt.date_from ? i.start_date : @gantt.date_from )
i_end_date = (i.due_before <= @gantt.date_to ? i.due_before : @gantt.date_to )
i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
i_done_date = (i_done_date <= @gantt.date_from ? @gantt.date_from : i_done_date )
i_done_date = (i_done_date >= @gantt.date_to ? @gantt.date_to : i_done_date )
i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
i_left = ((i_start_date - @gantt.date_from)*zoom)
i_width = ((i_end_date - i_start_date + 1)*zoom)
d_width = ((i_done_date - i_start_date)*zoom)
l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date
l_width ||= 0
pdf.SetX(subject_width + i_left)
pdf.SetFillColor(200,200,200)
pdf.Cell(i_width, 2, "", 0, 0, "", 1)
if l_width > 0
pdf.SetY(top+1.5)
pdf.SetX(subject_width + i_left)
pdf.SetFillColor(255,100,100)
pdf.Cell(l_width, 2, "", 0, 0, "", 1)
end
if d_width > 0
pdf.SetY(top+1.5)
pdf.SetX(subject_width + i_left)
pdf.SetFillColor(100,100,255)
pdf.Cell(d_width, 2, "", 0, 0, "", 1)
end
pdf.SetY(top+1.5)
pdf.SetX(subject_width + i_left + i_width)
pdf.Cell(30, 2, "#{i.status.name} #{i.done_ratio}%")
else
i_left = ((i.start_date - @gantt.date_from)*zoom)
pdf.SetX(subject_width + i_left)
pdf.SetFillColor(50,200,50)
pdf.Cell(2, 2, "", 0, 0, "", 1)
pdf.SetY(top+1.5)
pdf.SetX(subject_width + i_left + 3)
pdf.Cell(30, 2, "#{i.name}")
end
top = top + 5
pdf.SetDrawColor(200, 200, 200)
pdf.Line(15, top, subject_width+g_width, top)
if pdf.GetY() > 180
pdf.AddPage("L")
top = 20
pdf.Line(15, top, subject_width+g_width, top)
end
pdf.SetDrawColor(0, 0, 0)
end
pdf.Line(15, top, subject_width+g_width, top)
%>
<%= pdf.Output %>
+50
View File
@@ -0,0 +1,50 @@
<% pdf=IfpdfHelper::IFPDF.new(current_language)
title = @project ? "#{@project.name} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
pdf.SetTitle(title)
pdf.AliasNbPages
pdf.footer_date = format_date(Date.today)
pdf.AddPage("L")
row_height = 7
#
# title
#
pdf.SetFontStyle('B',11)
pdf.Cell(190,10, title)
pdf.Ln
#
# headers
#
pdf.SetFontStyle('B',10)
pdf.SetFillColor(230, 230, 230)
pdf.Cell(15, row_height, "#", 0, 0, 'L', 1)
pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1)
pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1)
pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1)
pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1)
pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1)
pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1)
pdf.Line(10, pdf.GetY, 287, pdf.GetY)
pdf.Ln
pdf.Line(10, pdf.GetY, 287, pdf.GetY)
pdf.SetY(pdf.GetY() + 1)
#
# rows
#
pdf.SetFontStyle('',9)
pdf.SetFillColor(255, 255, 255)
@issues.each do |issue|
pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1)
pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1)
pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1)
pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1)
pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.name : '', 0, 0, 'L', 1)
pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1)
pdf.MultiCell(0, row_height, (@project == issue.project ? issue.subject : "#{issue.project.name} - #{issue.subject}"))
pdf.Line(10, pdf.GetY, 287, pdf.GetY)
pdf.SetY(pdf.GetY() + 1)
end
%>
<%= pdf.Output %>
-7
View File
@@ -7,7 +7,6 @@
<%= render :partial => 'issues/form', :locals => {:f => f} %> <%= render :partial => 'issues/form', :locals => {:f => f} %>
</div> </div>
<%= submit_tag l(:button_create) %> <%= submit_tag l(:button_create) %>
<%= submit_tag l(:button_create_and_continue), :name => 'continue' %>
<%= link_to_remote l(:label_preview), <%= link_to_remote l(:label_preview),
{ :url => { :controller => 'issues', :action => 'preview', :project_id => @project }, { :url => { :controller => 'issues', :action => 'preview', :project_id => @project },
:method => 'post', :method => 'post',
@@ -15,12 +14,6 @@
:with => "Form.serialize('issue-form')", :with => "Form.serialize('issue-form')",
:complete => "Element.scrollTo('preview')" :complete => "Element.scrollTo('preview')"
}, :accesskey => accesskey(:preview) %> }, :accesskey => accesskey(:preview) %>
<%= javascript_tag "Form.Element.focus('issue_subject');" %>
<% end %> <% end %>
<div id="preview" class="wiki"></div> <div id="preview" class="wiki"></div>
<% content_for :header_tags do %>
<%= stylesheet_link_tag 'scm' %>
<% end %>
+126
View File
@@ -0,0 +1,126 @@
<% pdf=IfpdfHelper::IFPDF.new(current_language)
pdf.SetTitle("#{@project.name} - ##{@issue.tracker.name} #{@issue.id}")
pdf.AliasNbPages
pdf.footer_date = format_date(Date.today)
pdf.AddPage
pdf.SetFontStyle('B',11)
pdf.Cell(190,10, "#{@issue.project} - #{@issue.tracker} # #{@issue.id}: #{@issue.subject}")
pdf.Ln
y0 = pdf.GetY
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_status) + ":","LT")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, @issue.status.name,"RT")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_priority) + ":","LT")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, @issue.priority.name,"RT")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_author) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, @issue.author.name,"R")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_category) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, (@issue.category ? @issue.category.name : "-"),"R")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_created_on) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(@issue.created_on),"R")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_assigned_to) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, (@issue.assigned_to ? @issue.assigned_to.name : "-"),"R")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_updated_on) + ":","LB")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(@issue.updated_on),"RB")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_due_date) + ":","LB")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(@issue.due_date),"RB")
pdf.Ln
for custom_value in @issue.custom_values
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, custom_value.custom_field.name + ":","L")
pdf.SetFontStyle('',9)
pdf.MultiCell(155,5, (show_value custom_value),"R")
end
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_subject) + ":","LTB")
pdf.SetFontStyle('',9)
pdf.Cell(155,5, @issue.subject,"RTB")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_description) + ":")
pdf.SetFontStyle('',9)
pdf.MultiCell(155,5, @issue.description,"BR")
pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
pdf.Ln
if @issue.changesets.any? && User.current.allowed_to?(:view_changesets, @issue.project)
pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_associated_revisions), "B")
pdf.Ln
for changeset in @issue.changesets
pdf.SetFontStyle('B',8)
pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.author.to_s)
pdf.Ln
unless changeset.comments.blank?
pdf.SetFontStyle('',8)
pdf.MultiCell(190,5, changeset.comments)
end
pdf.Ln
end
end
pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_history), "B")
pdf.Ln
for journal in @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
pdf.SetFontStyle('B',8)
pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
pdf.Ln
pdf.SetFontStyle('I',8)
for detail in journal.details
pdf.Cell(190,5, "- " + show_detail(detail, true))
pdf.Ln
end
if journal.notes?
pdf.SetFontStyle('',8)
pdf.MultiCell(190,5, journal.notes)
end
pdf.Ln
end
if @issue.attachments.any?
pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_attachment_plural), "B")
pdf.Ln
for attachment in @issue.attachments
pdf.SetFontStyle('',8)
pdf.Cell(80,5, attachment.filename)
pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
pdf.Cell(25,5, format_date(attachment.created_on),0,0,"R")
pdf.Cell(65,5, attachment.author.name,0,0,"R")
pdf.Ln
end
end
%>
<%= pdf.Output %>
+6 -4
View File
@@ -19,11 +19,11 @@
<table width="100%"> <table width="100%">
<tr> <tr>
<td style="width:15%" class="status"><b><%=l(:field_status)%>:</b></td><td style="width:35%" class="status"><%= @issue.status.name %></td> <td style="width:15%" class="status"><b><%=l(:field_status)%>:</b></td><td style="width:35%" class="status status-<%= @issue.status.name %>"><%= @issue.status.name %></td>
<td style="width:15%" class="start-date"><b><%=l(:field_start_date)%>:</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td> <td style="width:15%" class="start-date"><b><%=l(:field_start_date)%>:</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td>
</tr> </tr>
<tr> <tr>
<td class="priority"><b><%=l(:field_priority)%>:</b></td><td class="priority"><%= @issue.priority.name %></td> <td class="priority"><b><%=l(:field_priority)%>:</b></td><td class="priority priority-<%= @issue.priority.name %>"><%= @issue.priority.name %></td>
<td class="due-date"><b><%=l(:field_due_date)%>:</b></td><td class="due-date"><%= format_date(@issue.due_date) %></td> <td class="due-date"><b><%=l(:field_due_date)%>:</b></td><td class="due-date"><%= format_date(@issue.due_date) %></td>
</tr> </tr>
<tr> <tr>
@@ -59,7 +59,7 @@ end %>
<hr /> <hr />
<div class="contextual"> <div class="contextual">
<%= link_to_remote_if_authorized(l(:button_quote), { :url => {:action => 'reply', :id => @issue} }, :class => 'icon icon-comment') unless @issue.description.blank? %> <%= link_to_remote_if_authorized l(:button_quote), { :url => {:action => 'reply', :id => @issue} }, :class => 'icon icon-comment' %>
</div> </div>
<p><strong><%=l(:field_description)%></strong></p> <p><strong><%=l(:field_description)%></strong></p>
@@ -67,7 +67,9 @@ end %>
<%= textilizable @issue, :description, :attachments => @issue.attachments %> <%= textilizable @issue, :description, :attachments => @issue.attachments %>
</div> </div>
<%= link_to_attachments @issue %> <% if @issue.attachments.any? %>
<%= link_to_attachments @issue.attachments, :delete_url => (authorize_for('issues', 'destroy_attachment') ? {:controller => 'issues', :action => 'destroy_attachment', :id => @issue} : nil) %>
<% end %>
<% if authorize_for('issue_relations', 'new') || @issue.relations.any? %> <% if authorize_for('issue_relations', 'new') || @issue.relations.any? %>
<hr /> <hr />
+2 -2
View File
@@ -3,10 +3,10 @@
<option selected="selected"><%= l(:label_jump_to_a_project) %></option> <option selected="selected"><%= l(:label_jump_to_a_project) %></option>
<option disabled="disabled">---</option> <option disabled="disabled">---</option>
<% user_projects_by_root.keys.sort.each do |root| %> <% user_projects_by_root.keys.sort.each do |root| %>
<%= content_tag('option', h(root.name), :value => url_for(:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item)) %> <%= content_tag('option', h(root.name), :value => url_for(:controller => 'projects', :action => 'show', :id => root)) %>
<% user_projects_by_root[root].sort.each do |project| %> <% user_projects_by_root[root].sort.each do |project| %>
<% next if project == root %> <% next if project == root %>
<%= content_tag('option', ('&#187; ' + h(project.name)), :value => url_for(:controller => 'projects', :action => 'show', :id => project, :jump => current_menu_item)) %> <%= content_tag('option', ('&#187; ' + h(project.name)), :value => url_for(:controller => 'projects', :action => 'show', :id => project)) %>
<% end %> <% end %>
<% end %> <% end %>
</select> </select>
+2 -1
View File
@@ -50,7 +50,8 @@
</div> </div>
<div id="content"> <div id="content">
<%= render_flash_messages %> <%= content_tag('div', flash[:error], :class => 'flash error') if flash[:error] %>
<%= content_tag('div', flash[:notice], :class => 'flash notice') if flash[:notice] %>
<%= yield %> <%= yield %>
</div> </div>
</div> </div>
+11 -6
View File
@@ -6,12 +6,17 @@ body {
font-size: 0.8em; font-size: 0.8em;
color:#484848; color:#484848;
} }
h1, h2, h3 { font-family: "Trebuchet MS", Verdana, sans-serif; margin: 0px; } h1 {
h1 { font-size: 1.2em; } font-family: "Trebuchet MS", Verdana, sans-serif;
h2, h3 { font-size: 1.1em; } font-size: 1.2em;
a, a:link, a:visited { color: #2A5685;} margin: 0px;
a:hover, a:active { color: #c61a1a; } }
a.wiki-anchor { display: none; } a, a:link, a:visited {
color: #2A5685;
}
a:hover, a:active {
color: #c61a1a;
}
hr { hr {
width: 100%; width: 100%;
height: 1px; height: 1px;
+2 -2
View File
@@ -15,7 +15,7 @@
<div class="wiki"> <div class="wiki">
<%= textilizable(@topic.content, :attachments => @topic.attachments) %> <%= textilizable(@topic.content, :attachments => @topic.attachments) %>
</div> </div>
<%= link_to_attachments @topic, :author => false %> <%= link_to_attachments @topic.attachments, :no_author => true %>
</div> </div>
<br /> <br />
@@ -31,7 +31,7 @@
<div class="message reply"> <div class="message reply">
<h4><%=h message.subject %> - <%= authoring message.created_on, message.author %></h4> <h4><%=h message.subject %> - <%= authoring message.created_on, message.author %></h4>
<div class="wiki"><%= textilizable message, :content, :attachments => message.attachments %></div> <div class="wiki"><%= textilizable message, :content, :attachments => message.attachments %></div>
<%= link_to_attachments message, :author => false %> <%= link_to_attachments message.attachments, :no_author => true %>
</div> </div>
<% end %> <% end %>
<% end %> <% end %>
+1 -1
View File
@@ -47,6 +47,6 @@ entries_by_day = entries.group_by(&:spent_on)
</tr> </tr>
<% end -%> <% end -%>
<% end -%> <% end -%>
</tbody> </tbdoy>
</table> </table>
<% end %> <% end %>
+1 -1
View File
@@ -11,7 +11,7 @@
<p><%= f.text_area :description, :rows => 5, :class => 'wiki-edit' %></p> <p><%= f.text_area :description, :rows => 5, :class => 'wiki-edit' %></p>
<p><%= f.text_field :identifier, :required => true, :disabled => @project.identifier_frozen? %> <p><%= f.text_field :identifier, :required => true, :disabled => @project.identifier_frozen? %>
<% unless @project.identifier_frozen? %> <% unless @project.identifier_frozen? %>
<br /><em><%= l(:text_length_between, 2, 20) %> <%= l(:text_project_identifier_info) %></em> <br /><em><%= l(:text_length_between, 3, 20) %> <%= l(:text_project_identifier_info) %></em>
<% end %></p> <% end %></p>
<p><%= f.text_field :homepage, :size => 60 %></p> <p><%= f.text_field :homepage, :size => 60 %></p>
<p><%= f.check_box :is_public %></p> <p><%= f.check_box :is_public %></p>
+3 -6
View File
@@ -4,13 +4,10 @@
<div class="box"> <div class="box">
<% form_tag({ :action => 'add_file', :id => @project }, :multipart => true, :class => "tabular") do %> <% form_tag({ :action => 'add_file', :id => @project }, :multipart => true, :class => "tabular") do %>
<% if @versions.any? %> <p><label for="version_id"><%=l(:field_version)%> <span class="required">*</span></label>
<p><label for="version_id"><%=l(:field_version)%></label> <%= select_tag "version_id", options_from_collection_for_select(@versions, "id", "name") %></p>
<%= select_tag "version_id", content_tag('option', '') +
options_from_collection_for_select(@versions, "id", "name") %></p>
<% end %>
<p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p> <p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
</div> </div>
<%= submit_tag l(:button_add) %> <%= submit_tag l(:button_add) %>
<% end %> <% end %>
+12 -10
View File
@@ -4,37 +4,39 @@
<h2><%=l(:label_attachment_plural)%></h2> <h2><%=l(:label_attachment_plural)%></h2>
<% delete_allowed = User.current.allowed_to?(:manage_files, @project) %> <% delete_allowed = authorize_for('versions', 'destroy_file') %>
<table class="list"> <table class="list">
<thead><tr> <thead><tr>
<th><%=l(:field_version)%></th>
<%= 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') %>
<%= sort_header_tag('size', :caption => l(:field_filesize), :default_order => 'desc') %> <%= sort_header_tag('size', :caption => l(:field_filesize), :default_order => 'desc') %>
<%= sort_header_tag('downloads', :caption => l(:label_downloads_abbr), :default_order => 'desc') %> <%= sort_header_tag('downloads', :caption => l(:label_downloads_abbr), :default_order => 'desc') %>
<th>MD5</th> <th>MD5</th>
<th></th> <% if delete_allowed %><th></th><% end %>
</tr></thead> </tr></thead>
<tbody> <tbody>
<% @containers.each do |container| %> <% for version in @versions %>
<% next if container.attachments.empty? -%> <% unless version.attachments.empty? %>
<% if container.is_a?(Version) -%> <tr><th colspan="7" align="left"><span class="icon icon-package"><b><%= version.name %></b></span></th></tr>
<tr><th colspan="6" align="left"><span class="icon icon-package"><b><%=h container %></b></span></th></tr> <% for file in version.attachments %>
<% end -%>
<% container.attachments.each do |file| %>
<tr class="<%= cycle("odd", "even") %>"> <tr class="<%= cycle("odd", "even") %>">
<td></td>
<td><%= link_to_attachment file, :download => true, :title => file.description %></td> <td><%= link_to_attachment file, :download => true, :title => file.description %></td>
<td align="center"><%= format_time(file.created_on) %></td> <td align="center"><%= format_time(file.created_on) %></td>
<td align="center"><%= number_to_human_size(file.filesize) %></td> <td align="center"><%= number_to_human_size(file.filesize) %></td>
<td align="center"><%= file.downloads %></td> <td align="center"><%= file.downloads %></td>
<td align="center"><small><%= file.digest %></small></td> <td align="center"><small><%= file.digest %></small></td>
<% if delete_allowed %>
<td align="center"> <td align="center">
<%= link_to(image_tag('delete.png'), {:controller => 'attachments', :action => 'destroy', :id => file}, <%= link_to_if_authorized image_tag('delete.png'), {:controller => 'versions', :action => 'destroy_file', :id => version, :attachment_id => file}, :confirm => l(:text_are_you_sure), :method => :post %>
:confirm => l(:text_are_you_sure), :method => :post) if delete_allowed %>
</td> </td>
<% end %>
</tr> </tr>
<% end <% end
reset_cycle %> reset_cycle %>
<% end %>
<% end %> <% end %>
</tbody> </tbody>
</table> </table>
@@ -17,6 +17,7 @@
<td><%= link_to(version.wiki_page_title, :controller => 'wiki', :page => Wiki.titleize(version.wiki_page_title)) unless version.wiki_page_title.blank? || @project.wiki.nil? %></td> <td><%= link_to(version.wiki_page_title, :controller => 'wiki', :page => Wiki.titleize(version.wiki_page_title)) unless version.wiki_page_title.blank? || @project.wiki.nil? %></td>
<td align="center"><%= link_to_if_authorized l(:button_edit), { :controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %></td> <td align="center"><%= link_to_if_authorized l(:button_edit), { :controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %></td>
<td align="center"><%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %></td> <td align="center"><%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %></td>
</td>
</tr> </tr>
<% end; reset_cycle %> <% end; reset_cycle %>
</tbody> </tbody>
@@ -1,10 +0,0 @@
<p>
<% if @repository.supports_cat? %>
<%= link_to_if action_name != 'entry', l(:button_view), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
<% end %>
<% if @repository.supports_annotate? %>
<%= link_to_if action_name != 'annotate', l(:button_annotate), {:action => 'annotate', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
<% end %>
<%= link_to(l(:button_download), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %>
<%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
</p>
-2
View File
@@ -1,7 +1,5 @@
<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
<p><%= render :partial => 'link_to_functions' %></p>
<% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %> <% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %>
<div class="autoscroll"> <div class="autoscroll">
+10 -1
View File
@@ -1,6 +1,15 @@
<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2> <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
<p><%= render :partial => 'link_to_functions' %></p> <p>
<% if @repository.supports_cat? %>
<%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
<% end %>
<% if @repository.supports_annotate? %>
<%= link_to l(:button_annotate), {:action => 'annotate', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
<% end %>
<%= link_to(l(:button_download), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %>
<%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
</p>
<%= render_properties(@properties) %> <%= render_properties(@properties) %>
-2
View File
@@ -1,7 +1,5 @@
<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
<p><%= render :partial => 'link_to_functions' %></p>
<%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %> <%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %>
<% content_for :header_tags do %> <% content_for :header_tags do %>
+1 -1
View File
@@ -5,7 +5,7 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" />
<style> <style>
body { font:80% Verdana,Tahoma,Arial,sans-serif; } body { font:80% Verdana,Tahoma,Arial,sans-serif; }
h1, h2, h3, h4 { font-family: "Trebuchet MS",Georgia,"Times New Roman",serif; } h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; }
ul.toc { padding: 4px; margin-left: 0; } ul.toc { padding: 4px; margin-left: 0; }
ul.toc li { list-style-type:none; } ul.toc li { list-style-type:none; }
ul.toc li.heading2 { margin-left: 1em; } ul.toc li.heading2 { margin-left: 1em; }
+1 -1
View File
@@ -5,7 +5,7 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" />
<style> <style>
body { font:80% Verdana,Tahoma,Arial,sans-serif; } body { font:80% Verdana,Tahoma,Arial,sans-serif; }
h1, h2, h3, h4 { font-family: "Trebuchet MS",Georgia,"Times New Roman",serif; } h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; }
</style> </style>
</head> </head>
<body> <body>
+1 -1
View File
@@ -28,7 +28,7 @@
<%= render(:partial => "wiki/content", :locals => {:content => @content}) %> <%= render(:partial => "wiki/content", :locals => {:content => @content}) %>
<%= link_to_attachments @page %> <%= link_to_attachments @page.attachments, :delete_url => ((@editable && authorize_for('wiki', 'destroy_attachment')) ? {:controller => 'wiki', :action => 'destroy_attachment', :page => @page.title} : nil) %>
<% if @editable && authorize_for('wiki', 'add_attachment') %> <% if @editable && authorize_for('wiki', 'add_attachment') %>
<p><%= link_to l(:label_attachment_new), {}, :onclick => "Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;", <p><%= link_to l(:label_attachment_new), {}, :onclick => "Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;",
@@ -2,10 +2,9 @@ class SetTopicAuthorsAsWatchers < ActiveRecord::Migration
def self.up def self.up
# Sets active users who created/replied a topic as watchers of the topic # Sets active users who created/replied a topic as watchers of the topic
# so that the new watch functionality at topic level doesn't affect notifications behaviour # so that the new watch functionality at topic level doesn't affect notifications behaviour
Message.connection.execute("INSERT INTO #{Watcher.table_name} (watchable_type, watchable_id, user_id)" + Message.connection.execute("INSERT INTO watchers (watchable_type, watchable_id, user_id)" +
" SELECT DISTINCT 'Message', COALESCE(m.parent_id, m.id), m.author_id" + " SELECT DISTINCT 'Message', COALESCE(messages.parent_id, messages.id), messages.author_id FROM messages, users" +
" FROM #{Message.table_name} m, #{User.table_name} u" + " WHERE messages.author_id = users.id AND users.status = 1")
" WHERE m.author_id = u.id AND u.status = 1")
end end
def self.down def self.down
+1 -28
View File
@@ -1,37 +1,10 @@
== Redmine changelog == Redmine changelog
Redmine - project management software Redmine - project management software
Copyright (C) 2006-2009 Jean-Philippe Lang Copyright (C) 2006-2008 Jean-Philippe Lang
http://www.redmine.org/ http://www.redmine.org/
== 2009-02-15 v0.8.1
* Select watchers on new issue form
* Issue description is no longer a required field
* Files module: ability to add files without version
* Jump to the current tab when using the project quick-jump combo
* Display a warning if some attachments were not saved
* Import custom fields values from emails on issue creation
* Show view/annotate/download links on entry and annotate views
* Admin Info Screen: Display if plugin assets directory is writable
* Adds a 'Create and continue' button on the new issue form
* IMAP: add options to move received emails
* Do not show Category field when categories are not defined
* Lower the project identifier limit to a minimum of two characters
* Add "closed" html class to closed entries in issue list
* Fixed: broken redirect URL on login failure
* Fixed: Deleted files are shown when using Darcs
* Fixed: Darcs adapter works on Win32 only
* Fixed: syntax highlight doesn't appear in new ticket preview
* Fixed: email notification for changes I make still occurs when running Repository.fetch_changesets
* Fixed: no error is raised when entering invalid hours on the issue update form
* Fixed: Details time log report CSV export doesn't honour date format from settings
* Fixed: invalid css classes on issue details
* Fixed: Trac importer creates duplicate custom values
* Fixed: inline attached image should not match partial filename
== 2008-12-30 v0.8.0 == 2008-12-30 v0.8.0
* Setting added in order to limit the number of diff lines that should be displayed * Setting added in order to limit the number of diff lines that should be displayed
-3
View File
@@ -696,6 +696,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -697,6 +697,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -701,6 +701,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -697,6 +697,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -698,6 +698,3 @@ default_activity_development: Entwicklung
enumeration_issue_priorities: Ticket-Prioritäten enumeration_issue_priorities: Ticket-Prioritäten
enumeration_doc_categories: Dokumentenkategorien enumeration_doc_categories: Dokumentenkategorien
enumeration_activities: Aktivitäten (Zeiterfassung) enumeration_activities: Aktivitäten (Zeiterfassung)
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
+1 -5
View File
@@ -85,8 +85,6 @@ error_scm_command_failed: "An error occurred when trying to access the repositor
error_scm_annotate: "The entry does not exist or can not be annotated." error_scm_annotate: "The entry does not exist or can not be annotated."
error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
warning_attachments_not_saved: "%d file(s) could not be saved."
mail_subject_lost_password: Your %s password mail_subject_lost_password: Your %s password
mail_body_lost_password: 'To change your password, click on the following link:' mail_body_lost_password: 'To change your password, click on the following link:'
mail_subject_register: Your %s account activation mail_subject_register: Your %s account activation
@@ -592,7 +590,6 @@ button_check_all: Check all
button_uncheck_all: Uncheck all button_uncheck_all: Uncheck all
button_delete: Delete button_delete: Delete
button_create: Create button_create: Create
button_create_and_continue: Create and continue
button_test: Test button_test: Test
button_edit: Edit button_edit: Edit
button_add: Add button_add: Add
@@ -663,8 +660,7 @@ text_status_changed_by_changeset: Applied in changeset %s.
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?' text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
text_select_project_modules: 'Select modules to enable for this project:' text_select_project_modules: 'Select modules to enable for this project:'
text_default_administrator_account_changed: Default administrator account changed text_default_administrator_account_changed: Default administrator account changed
text_file_repository_writable: Attachments directory writable text_file_repository_writable: File repository writable
text_plugin_assets_writable: Plugin assets directory writable
text_rmagick_available: RMagick available (optional) text_rmagick_available: RMagick available (optional)
text_destroy_time_entries_question: %.02f hours were reported on the issues you are about to delete. What do you want to do ? text_destroy_time_entries_question: %.02f hours were reported on the issues you are about to delete. What do you want to do ?
text_destroy_time_entries: Delete reported hours text_destroy_time_entries: Delete reported hours
-3
View File
@@ -681,6 +681,3 @@ text_user_mail_option: "De los proyectos no seleccionados, sólo recibirá notif
text_user_wrote: '%s escribió:' text_user_wrote: '%s escribió:'
text_wiki_destroy_confirmation: ¿Seguro que quiere borrar el wiki y todo su contenido? text_wiki_destroy_confirmation: ¿Seguro que quiere borrar el wiki y todo su contenido?
text_workflow_edit: Seleccionar un flujo de trabajo para actualizar text_workflow_edit: Seleccionar un flujo de trabajo para actualizar
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -696,6 +696,3 @@ label_user_activity: "Käyttäjän %s historia"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
+1 -5
View File
@@ -85,8 +85,6 @@ error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt
error_scm_annotate: "L'entrée n'existe pas ou ne peut pas être annotée." error_scm_annotate: "L'entrée n'existe pas ou ne peut pas être annotée."
error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas à ce projet" error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas à ce projet"
warning_attachments_not_saved: "%d fichier(s) n'ont pas pu être sauvegardés."
mail_subject_lost_password: Votre mot de passe %s mail_subject_lost_password: Votre mot de passe %s
mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant:' mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant:'
mail_subject_register: Activation de votre compte %s mail_subject_register: Activation de votre compte %s
@@ -582,7 +580,7 @@ label_reverse_chronological_order: Dans l'ordre chronologique inverse
label_planning: Planning label_planning: Planning
label_incoming_emails: Emails entrants label_incoming_emails: Emails entrants
label_generate_key: Générer une clé label_generate_key: Générer une clé
label_issue_watchers: Observateurs label_issue_watchers: Utilisateurs surveillant cette demande
label_example: Exemple label_example: Exemple
button_login: Connexion button_login: Connexion
@@ -592,7 +590,6 @@ button_check_all: Tout cocher
button_uncheck_all: Tout décocher button_uncheck_all: Tout décocher
button_delete: Supprimer button_delete: Supprimer
button_create: Créer button_create: Créer
button_create_and_continue: Créer et continuer
button_test: Tester button_test: Tester
button_edit: Modifier button_edit: Modifier
button_add: Ajouter button_add: Ajouter
@@ -664,7 +661,6 @@ text_issues_destroy_confirmation: 'Etes-vous sûr de vouloir supprimer le(s) dem
text_select_project_modules: 'Selectionner les modules à activer pour ce project:' text_select_project_modules: 'Selectionner les modules à activer pour ce project:'
text_default_administrator_account_changed: Compte administrateur par défaut changé text_default_administrator_account_changed: Compte administrateur par défaut changé
text_file_repository_writable: Répertoire de stockage des fichiers accessible en écriture text_file_repository_writable: Répertoire de stockage des fichiers accessible en écriture
text_plugin_assets_writable: Répertoire public des plugins accessible en écriture
text_rmagick_available: Bibliothèque RMagick présente (optionnelle) text_rmagick_available: Bibliothèque RMagick présente (optionnelle)
text_destroy_time_entries_question: %.02f heures ont été enregistrées sur les demandes à supprimer. Que voulez-vous faire ? text_destroy_time_entries_question: %.02f heures ont été enregistrées sur les demandes à supprimer. Que voulez-vous faire ?
text_destroy_time_entries: Supprimer les heures text_destroy_time_entries: Supprimer les heures
-5
View File
@@ -694,8 +694,3 @@ permission_edit_own_messages: ערוך הודעות של עצמך
permission_delete_own_messages: מחק הודעות של עצמך permission_delete_own_messages: מחק הודעות של עצמך
label_user_activity: "הפעילות של %s" label_user_activity: "הפעילות של %s"
label_updated_time_by: עודכן ע"י %s לפני %s label_updated_time_by: עודכן ע"י %s לפני %s
setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -697,6 +697,3 @@ label_user_activity: "%s tevékenységei"
label_updated_time_by: "Módosította %s ennyivel ezelőtt: %s" label_updated_time_by: "Módosította %s ennyivel ezelőtt: %s"
text_diff_truncated: '... A diff fájl vége nem jelenik meg, mert hosszab, mint a megjeleníthető sorok száma.' text_diff_truncated: '... A diff fájl vége nem jelenik meg, mert hosszab, mint a megjeleníthető sorok száma.'
setting_diff_max_lines_displayed: A megjelenítendő sorok száma (maximum) a diff fájloknál setting_diff_max_lines_displayed: A megjelenítendő sorok száma (maximum) a diff fájloknál
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -696,6 +696,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -697,6 +697,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -696,6 +696,3 @@ label_user_activity: "%s의 작업내역"
label_updated_time_by: %s가 %s 전에 변경 label_updated_time_by: %s가 %s 전에 변경
text_diff_truncated: '... 이 차이점은 표시할 수 있는 최대 줄수를 초과해서 이 차이점은 잘렸습니다.' text_diff_truncated: '... 이 차이점은 표시할 수 있는 최대 줄수를 초과해서 이 차이점은 잘렸습니다.'
setting_diff_max_lines_displayed: 차이점보기에 표시할 최대 줄수 setting_diff_max_lines_displayed: 차이점보기에 표시할 최대 줄수
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -698,6 +698,3 @@ default_activity_development: Vystymas
enumeration_issue_priorities: Darbo prioritetai enumeration_issue_priorities: Darbo prioritetai
enumeration_doc_categories: Dokumento kategorijos enumeration_doc_categories: Dokumento kategorijos
enumeration_activities: Veiklos (laiko sekimas) enumeration_activities: Veiklos (laiko sekimas)
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -698,6 +698,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -697,6 +697,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -715,6 +715,3 @@ label_user_activity: "Aktywność: %s"
label_updated_time_by: Uaktualnione przez %s %s temu label_updated_time_by: Uaktualnione przez %s %s temu
text_diff_truncated: '... Ten plik różnic został przycięty ponieważ jest zbyt długi.' text_diff_truncated: '... Ten plik różnic został przycięty ponieważ jest zbyt długi.'
setting_diff_max_lines_displayed: Maksymalna liczba linii różnicy do pokazania setting_diff_max_lines_displayed: Maksymalna liczba linii różnicy do pokazania
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
+2 -5
View File
@@ -47,8 +47,8 @@ general_text_Yes: 'Sim'
general_text_no: 'não' general_text_no: 'não'
general_text_yes: 'sim' general_text_yes: 'sim'
general_lang_name: 'Português(Brasil)' general_lang_name: 'Português(Brasil)'
general_csv_separator: ';' general_csv_separator: ','
general_csv_decimal_separator: ',' general_csv_decimal_separator: '.'
general_csv_encoding: ISO-8859-1 general_csv_encoding: ISO-8859-1
general_pdf_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1
general_day_names: Segunda,Terça,Quarta,Quinta,Sexta,Sábado,Domingo general_day_names: Segunda,Terça,Quarta,Quinta,Sexta,Sábado,Domingo
@@ -697,6 +697,3 @@ label_user_activity: "Atividade de %s"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -698,6 +698,3 @@ label_user_activity: "Actividade de %s"
label_updated_time_by: Actualizado por %s há %s label_updated_time_by: Actualizado por %s há %s
text_diff_truncated: '... Este diff foi truncado porque excede o tamanho máximo que pode ser mostrado.' text_diff_truncated: '... Este diff foi truncado porque excede o tamanho máximo que pode ser mostrado.'
setting_diff_max_lines_displayed: Número máximo de linhas de diff mostradas setting_diff_max_lines_displayed: Número máximo de linhas de diff mostradas
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -696,6 +696,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -730,6 +730,3 @@ text_user_wrote: '%s написал(а):'
text_wiki_destroy_confirmation: Вы уверены, что хотите удалить данную Wiki и все ее содержимое? text_wiki_destroy_confirmation: Вы уверены, что хотите удалить данную Wiki и все ее содержимое?
text_workflow_edit: Выберите роль и трекер для редактирования последовательности состояний text_workflow_edit: Выберите роль и трекер для редактирования последовательности состояний
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -702,6 +702,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -697,6 +697,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
+1 -4
View File
@@ -696,7 +696,4 @@ default_activity_development: Utveckling
enumeration_issue_priorities: Ärendeprioriteter enumeration_issue_priorities: Ärendeprioriteter
enumeration_doc_categories: Dokumentkategorier enumeration_doc_categories: Dokumentkategorier
enumeration_activities: Aktiviteter (tidsuppföljning) enumeration_activities: Aktiviteter (tidsuppföljning)
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -699,6 +699,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -697,6 +697,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -698,6 +698,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -699,6 +699,3 @@ label_user_activity: "%s's activity"
label_updated_time_by: Updated by %s %s ago label_updated_time_by: Updated by %s %s ago
text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
setting_diff_max_lines_displayed: Max number of diff lines displayed setting_diff_max_lines_displayed: Max number of diff lines displayed
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -698,6 +698,3 @@ default_activity_development: 開發
enumeration_issue_priorities: 項目優先權 enumeration_issue_priorities: 項目優先權
enumeration_doc_categories: 文件分類 enumeration_doc_categories: 文件分類
enumeration_activities: 活動 (時間追蹤) enumeration_activities: 活動 (時間追蹤)
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
-3
View File
@@ -698,6 +698,3 @@ default_activity_development: 开发
enumeration_issue_priorities: 问题优先级 enumeration_issue_priorities: 问题优先级
enumeration_doc_categories: 文档类别 enumeration_doc_categories: 文档类别
enumeration_activities: 活动(时间跟踪) enumeration_activities: 活动(时间跟踪)
text_plugin_assets_writable: Plugin assets directory writable
warning_attachments_not_saved: "%d file(s) could not be saved."
button_create_and_continue: Create and continue
+2 -5
View File
@@ -791,10 +791,7 @@ class RedCloth3 < String
\s? \s?
(?:\(([^)]+?)\)(?="))? # $title (?:\(([^)]+?)\)(?="))? # $title
": ":
( # $url ([\w\/]\S+?) # $url
(\/|[a-zA-Z]+:\/\/|www\.) # $proto
[\w\/]\S+?
)
(\/)? # $slash (\/)? # $slash
([^\w\=\/;\(\)]*?) # $post ([^\w\=\/;\(\)]*?) # $post
(?=<|\s|$) (?=<|\s|$)
@@ -802,7 +799,7 @@ class RedCloth3 < String
#" #"
def inline_textile_link( text ) def inline_textile_link( text )
text.gsub!( LINK_RE ) do |m| text.gsub!( LINK_RE ) do |m|
pre,atts,text,title,url,proto,slash,post = $~[1..8] pre,atts,text,title,url,slash,post = $~[1..7]
url, url_title = check_refs( url ) url, url_title = check_refs( url )
title ||= url_title title ||= url_title
+4 -4
View File
@@ -35,7 +35,7 @@ Redmine::AccessControl.map do |map|
:queries => :index, :queries => :index,
:reports => :issue_report}, :public => true :reports => :issue_report}, :public => true
map.permission :add_issues, {:issues => :new} map.permission :add_issues, {:issues => :new}
map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit]} map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :destroy_attachment]}
map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]} map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
map.permission :add_issue_notes, {:issues => [:edit, :reply]} map.permission :add_issue_notes, {:issues => [:edit, :reply]}
map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
@@ -67,12 +67,12 @@ Redmine::AccessControl.map do |map|
end end
map.project_module :documents do |map| map.project_module :documents do |map|
map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
map.permission :view_documents, :documents => [:index, :show, :download] map.permission :view_documents, :documents => [:index, :show, :download]
end end
map.project_module :files do |map| map.project_module :files do |map|
map.permission :manage_files, {:projects => :add_file}, :require => :loggedin map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
map.permission :view_files, :projects => :list_files, :versions => :download map.permission :view_files, :projects => :list_files, :versions => :download
end end
@@ -83,7 +83,7 @@ Redmine::AccessControl.map do |map|
map.permission :view_wiki_pages, :wiki => [:index, :special] map.permission :view_wiki_pages, :wiki => [:index, :special]
map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate] map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment] map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment]
map.permission :delete_wiki_pages_attachments, {} map.permission :delete_wiki_pages_attachments, :wiki => :destroy_attachment
map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
end end
+1 -3
View File
@@ -24,9 +24,7 @@ module Redmine #:nodoc:
def to_hours def to_hours
s = self.dup s = self.dup
s.strip! s.strip!
if s =~ %r{^(\d+([.,]\d+)?)h?$} unless s =~ %r{^[\d\.,]+$}
s = $1
else
# 2:30 => 2.5 # 2:30 => 2.5
s.gsub!(%r{^(\d+):(\d+)$}) { $1.to_i + $2.to_i / 60.0 } s.gsub!(%r{^(\d+):(\d+)$}) { $1.to_i + $2.to_i / 60.0 }
# 2h30, 2h, 30m => 2.5, 2, 0.5 # 2h30, 2h, 30m => 2.5, 2, 0.5
-459
View File
@@ -1,459 +0,0 @@
# 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.
require 'iconv'
require 'rfpdf/chinese'
module Redmine
module Export
module PDF
class IFPDF < FPDF
include GLoc
attr_accessor :footer_date
def initialize(lang)
super()
set_language_if_valid lang
case current_language.to_s
when 'ja'
extend(PDF_Japanese)
AddSJISFont()
@font_for_content = 'SJIS'
@font_for_footer = 'SJIS'
when 'zh'
extend(PDF_Chinese)
AddGBFont()
@font_for_content = 'GB'
@font_for_footer = 'GB'
when 'zh-tw'
extend(PDF_Chinese)
AddBig5Font()
@font_for_content = 'Big5'
@font_for_footer = 'Big5'
else
@font_for_content = 'Arial'
@font_for_footer = 'Helvetica'
end
SetCreator(Redmine::Info.app_name)
SetFont(@font_for_content)
end
def SetFontStyle(style, size)
SetFont(@font_for_content, style, size)
end
def SetTitle(txt)
txt = begin
utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
hextxt = "<FEFF" # FEFF is BOM
hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
hextxt << ">"
rescue
txt
end || ''
super(txt)
end
def textstring(s)
# Format a text string
if s =~ /^</ # This means the string is hex-dumped.
return s
else
return '('+escape(s)+')'
end
end
def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
@ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
# these quotation marks are not correctly rendered in the pdf
txt = txt.gsub(/[“�]/, '"') if txt
txt = begin
# 0x5c char handling
txtar = txt.split('\\')
txtar << '' if txt[-1] == ?\\
txtar.collect {|x| @ic.iconv(x)}.join('\\').gsub(/\\/, "\\\\\\\\")
rescue
txt
end || ''
super w,h,txt,border,ln,align,fill,link
end
def Footer
SetFont(@font_for_footer, 'I', 8)
SetY(-15)
SetX(15)
Cell(0, 5, @footer_date, 0, 0, 'L')
SetY(-15)
SetX(-30)
Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
end
end
# Returns a PDF string of a list of issues
def issues_to_pdf(issues, project)
pdf = IFPDF.new(current_language)
title = project ? "#{project} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
pdf.SetTitle(title)
pdf.AliasNbPages
pdf.footer_date = format_date(Date.today)
pdf.AddPage("L")
row_height = 7
# title
pdf.SetFontStyle('B',11)
pdf.Cell(190,10, title)
pdf.Ln
# headers
pdf.SetFontStyle('B',10)
pdf.SetFillColor(230, 230, 230)
pdf.Cell(15, row_height, "#", 0, 0, 'L', 1)
pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1)
pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1)
pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1)
pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1)
pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1)
pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1)
pdf.Line(10, pdf.GetY, 287, pdf.GetY)
pdf.Ln
pdf.Line(10, pdf.GetY, 287, pdf.GetY)
pdf.SetY(pdf.GetY() + 1)
# rows
pdf.SetFontStyle('',9)
pdf.SetFillColor(255, 255, 255)
issues.each do |issue|
pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1)
pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1)
pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1)
pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1)
pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.to_s : '', 0, 0, 'L', 1)
pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1)
pdf.MultiCell(0, row_height, (project == issue.project ? issue.subject : "#{issue.project} - #{issue.subject}"))
pdf.Line(10, pdf.GetY, 287, pdf.GetY)
pdf.SetY(pdf.GetY() + 1)
end
pdf.Output
end
# Returns a PDF string of a single issue
def issue_to_pdf(issue)
pdf = IFPDF.new(current_language)
pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
pdf.AliasNbPages
pdf.footer_date = format_date(Date.today)
pdf.AddPage
pdf.SetFontStyle('B',11)
pdf.Cell(190,10, "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}")
pdf.Ln
y0 = pdf.GetY
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_status) + ":","LT")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, issue.status.to_s,"RT")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_priority) + ":","LT")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, issue.priority.to_s,"RT")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_author) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, issue.author.to_s,"R")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_category) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, issue.category.to_s,"R")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_created_on) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(issue.created_on),"R")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_assigned_to) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, issue.assigned_to.to_s,"R")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_updated_on) + ":","LB")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(issue.updated_on),"RB")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_due_date) + ":","LB")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(issue.due_date),"RB")
pdf.Ln
for custom_value in issue.custom_values
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, custom_value.custom_field.name + ":","L")
pdf.SetFontStyle('',9)
pdf.MultiCell(155,5, (show_value custom_value),"R")
end
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_subject) + ":","LTB")
pdf.SetFontStyle('',9)
pdf.Cell(155,5, issue.subject,"RTB")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_description) + ":")
pdf.SetFontStyle('',9)
pdf.MultiCell(155,5, @issue.description,"BR")
pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
pdf.Ln
if issue.changesets.any? && User.current.allowed_to?(:view_changesets, issue.project)
pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_associated_revisions), "B")
pdf.Ln
for changeset in issue.changesets
pdf.SetFontStyle('B',8)
pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.author.to_s)
pdf.Ln
unless changeset.comments.blank?
pdf.SetFontStyle('',8)
pdf.MultiCell(190,5, changeset.comments)
end
pdf.Ln
end
end
pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_history), "B")
pdf.Ln
for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
pdf.SetFontStyle('B',8)
pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
pdf.Ln
pdf.SetFontStyle('I',8)
for detail in journal.details
pdf.Cell(190,5, "- " + show_detail(detail, true))
pdf.Ln
end
if journal.notes?
pdf.SetFontStyle('',8)
pdf.MultiCell(190,5, journal.notes)
end
pdf.Ln
end
if issue.attachments.any?
pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_attachment_plural), "B")
pdf.Ln
for attachment in issue.attachments
pdf.SetFontStyle('',8)
pdf.Cell(80,5, attachment.filename)
pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
pdf.Cell(25,5, format_date(attachment.created_on),0,0,"R")
pdf.Cell(65,5, attachment.author.name,0,0,"R")
pdf.Ln
end
end
pdf.Output
end
# Returns a PDF string of a gantt chart
def gantt_to_pdf(gantt, project)
pdf = IFPDF.new(current_language)
pdf.SetTitle("#{l(:label_gantt)} #{project}")
pdf.AliasNbPages
pdf.footer_date = format_date(Date.today)
pdf.AddPage("L")
pdf.SetFontStyle('B',12)
pdf.SetX(15)
pdf.Cell(70, 20, project.to_s)
pdf.Ln
pdf.SetFontStyle('B',9)
subject_width = 70
header_heigth = 5
headers_heigth = header_heigth
show_weeks = false
show_days = false
if gantt.months < 7
show_weeks = true
headers_heigth = 2*header_heigth
if gantt.months < 3
show_days = true
headers_heigth = 3*header_heigth
end
end
g_width = 210
zoom = (g_width) / (gantt.date_to - gantt.date_from + 1)
g_height = 120
t_height = g_height + headers_heigth
y_start = pdf.GetY
# Months headers
month_f = gantt.date_from
left = subject_width
height = header_heigth
gantt.months.times do
width = ((month_f >> 1) - month_f) * zoom
pdf.SetY(y_start)
pdf.SetX(left)
pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
left = left + width
month_f = month_f >> 1
end
# Weeks headers
if show_weeks
left = subject_width
height = header_heigth
if gantt.date_from.cwday == 1
# gantt.date_from is monday
week_f = gantt.date_from
else
# find next monday after gantt.date_from
week_f = gantt.date_from + (7 - gantt.date_from.cwday + 1)
width = (7 - gantt.date_from.cwday + 1) * zoom-1
pdf.SetY(y_start + header_heigth)
pdf.SetX(left)
pdf.Cell(width + 1, height, "", "LTR")
left = left + width+1
end
while week_f <= gantt.date_to
width = (week_f + 6 <= gantt.date_to) ? 7 * zoom : (gantt.date_to - week_f + 1) * zoom
pdf.SetY(y_start + header_heigth)
pdf.SetX(left)
pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
left = left + width
week_f = week_f+7
end
end
# Days headers
if show_days
left = subject_width
height = header_heigth
wday = gantt.date_from.cwday
pdf.SetFontStyle('B',7)
(gantt.date_to - gantt.date_from + 1).to_i.times do
width = zoom
pdf.SetY(y_start + 2 * header_heigth)
pdf.SetX(left)
pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
left = left + width
wday = wday + 1
wday = 1 if wday > 7
end
end
pdf.SetY(y_start)
pdf.SetX(15)
pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
# Tasks
top = headers_heigth + y_start
pdf.SetFontStyle('B',7)
gantt.events.each do |i|
pdf.SetY(top)
pdf.SetX(15)
if i.is_a? Issue
pdf.Cell(subject_width-15, 5, "#{i.tracker} #{i.id}: #{i.subject}".sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)'), "LR")
else
pdf.Cell(subject_width-15, 5, "#{l(:label_version)}: #{i.name}", "LR")
end
pdf.SetY(top)
pdf.SetX(subject_width)
pdf.Cell(g_width, 5, "", "LR")
pdf.SetY(top+1.5)
if i.is_a? Issue
i_start_date = (i.start_date >= gantt.date_from ? i.start_date : gantt.date_from )
i_end_date = (i.due_before <= gantt.date_to ? i.due_before : gantt.date_to )
i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
i_done_date = (i_done_date <= gantt.date_from ? gantt.date_from : i_done_date )
i_done_date = (i_done_date >= gantt.date_to ? gantt.date_to : i_done_date )
i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
i_left = ((i_start_date - gantt.date_from)*zoom)
i_width = ((i_end_date - i_start_date + 1)*zoom)
d_width = ((i_done_date - i_start_date)*zoom)
l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date
l_width ||= 0
pdf.SetX(subject_width + i_left)
pdf.SetFillColor(200,200,200)
pdf.Cell(i_width, 2, "", 0, 0, "", 1)
if l_width > 0
pdf.SetY(top+1.5)
pdf.SetX(subject_width + i_left)
pdf.SetFillColor(255,100,100)
pdf.Cell(l_width, 2, "", 0, 0, "", 1)
end
if d_width > 0
pdf.SetY(top+1.5)
pdf.SetX(subject_width + i_left)
pdf.SetFillColor(100,100,255)
pdf.Cell(d_width, 2, "", 0, 0, "", 1)
end
pdf.SetY(top+1.5)
pdf.SetX(subject_width + i_left + i_width)
pdf.Cell(30, 2, "#{i.status} #{i.done_ratio}%")
else
i_left = ((i.start_date - gantt.date_from)*zoom)
pdf.SetX(subject_width + i_left)
pdf.SetFillColor(50,200,50)
pdf.Cell(2, 2, "", 0, 0, "", 1)
pdf.SetY(top+1.5)
pdf.SetX(subject_width + i_left + 3)
pdf.Cell(30, 2, "#{i.name}")
end
top = top + 5
pdf.SetDrawColor(200, 200, 200)
pdf.Line(15, top, subject_width+g_width, top)
if pdf.GetY() > 180
pdf.AddPage("L")
top = 20
pdf.Line(15, top, subject_width+g_width, top)
end
pdf.SetDrawColor(0, 0, 0)
end
pdf.Line(15, top, subject_width+g_width, top)
pdf.Output
end
end
end
end
+1 -10
View File
@@ -1,4 +1,4 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang # Copyright (C) 2006-2008 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
@@ -33,18 +33,9 @@ module Redmine
msg = imap.fetch(message_id,'RFC822')[0].attr['RFC822'] msg = imap.fetch(message_id,'RFC822')[0].attr['RFC822']
logger.debug "Receiving message #{message_id}" if logger && logger.debug? logger.debug "Receiving message #{message_id}" if logger && logger.debug?
if MailHandler.receive(msg, options) if MailHandler.receive(msg, options)
logger.debug "Message #{message_id} successfully received" if logger && logger.debug?
if imap_options[:move_on_success]
imap.copy(message_id, imap_options[:move_on_success])
end
imap.store(message_id, "+FLAGS", [:Seen, :Deleted]) imap.store(message_id, "+FLAGS", [:Seen, :Deleted])
else else
logger.debug "Message #{message_id} can not be processed" if logger && logger.debug?
imap.store(message_id, "+FLAGS", [:Seen]) imap.store(message_id, "+FLAGS", [:Seen])
if imap_options[:move_on_failure]
imap.copy(message_id, imap_options[:move_on_failure])
imap.store(message_id, "+FLAGS", [:Deleted])
end
end end
end end
imap.expunge imap.expunge
+2 -13
View File
@@ -52,19 +52,8 @@ module Redmine
# Returns the menu item name according to the current action # Returns the menu item name according to the current action
def current_menu_item def current_menu_item
@current_menu_item ||= menu_items[controller_name.to_sym][:actions][action_name.to_sym] || menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
menu_items[controller_name.to_sym][:default] menu_items[controller_name.to_sym][:default]
end
# Redirects user to the menu item of the given project
# Returns false if user is not authorized
def redirect_to_project_menu_item(project, name)
item = Redmine::MenuManager.items(:project_menu).detect {|i| i.name.to_s == name.to_s}
if item && User.current.allowed_to?(item.url, project) && (item.condition.nil? || item.condition.call(project))
redirect_to({item.param => project}.merge(item.url))
return true
end
false
end end
end end
+12 -19
View File
@@ -67,8 +67,8 @@ module Redmine
path = '.' if path.blank? path = '.' if path.blank?
entries = Entries.new entries = Entries.new
cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output" cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output"
cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier cmd << " --match \"hash #{identifier}\"" if identifier
cmd << " #{shell_quote path}" cmd << " #{path}"
shellout(cmd) do |io| shellout(cmd) do |io|
begin begin
doc = REXML::Document.new(io) doc = REXML::Document.new(io)
@@ -84,14 +84,14 @@ module Redmine
end end
end end
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
entries.compact.sort_by_name entries.sort_by_name
end end
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
path = '.' if path.blank? path = '.' if path.blank?
revisions = Revisions.new revisions = Revisions.new
cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output" cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
cmd << " --from-match #{shell_quote("hash #{identifier_from}")}" if identifier_from cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from
cmd << " --last #{options[:limit].to_i}" if options[:limit] cmd << " --last #{options[:limit].to_i}" if options[:limit]
shellout(cmd) do |io| shellout(cmd) do |io|
begin begin
@@ -118,12 +118,12 @@ module Redmine
path = '*' if path.blank? path = '*' if path.blank?
cmd = "#{DARCS_BIN} diff --repodir #{@url}" cmd = "#{DARCS_BIN} diff --repodir #{@url}"
if identifier_to.nil? if identifier_to.nil?
cmd << " --match #{shell_quote("hash #{identifier_from}")}" cmd << " --match \"hash #{identifier_from}\""
else else
cmd << " --to-match #{shell_quote("hash #{identifier_from}")}" cmd << " --to-match \"hash #{identifier_from}\""
cmd << " --from-match #{shell_quote("hash #{identifier_to}")}" cmd << " --from-match \"hash #{identifier_to}\""
end end
cmd << " -u #{shell_quote path}" cmd << " -u #{path}"
diff = [] diff = []
shellout(cmd) do |io| shellout(cmd) do |io|
io.each_line do |line| io.each_line do |line|
@@ -136,7 +136,7 @@ module Redmine
def cat(path, identifier=nil) def cat(path, identifier=nil)
cmd = "#{DARCS_BIN} show content --repodir #{@url}" cmd = "#{DARCS_BIN} show content --repodir #{@url}"
cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier cmd << " --match \"hash #{identifier}\"" if identifier
cmd << " #{shell_quote path}" cmd << " #{shell_quote path}"
cat = nil cat = nil
shellout(cmd) do |io| shellout(cmd) do |io|
@@ -148,22 +148,15 @@ module Redmine
end end
private private
# Returns an Entry from the given XML element
# or nil if the entry was deleted
def entry_from_xml(element, path_prefix) def entry_from_xml(element, path_prefix)
modified_element = element.elements['modified']
if modified_element.elements['modified_how'].text.match(/removed/)
return nil
end
Entry.new({:name => element.attributes['name'], Entry.new({:name => element.attributes['name'],
:path => path_prefix + element.attributes['name'], :path => path_prefix + element.attributes['name'],
:kind => element.name == 'file' ? 'file' : 'dir', :kind => element.name == 'file' ? 'file' : 'dir',
:size => nil, :size => nil,
:lastrev => Revision.new({ :lastrev => Revision.new({
:identifier => nil, :identifier => nil,
:scmid => modified_element.elements['patch'].attributes['hash'] :scmid => element.elements['modified'].elements['patch'].attributes['hash']
}) })
}) })
end end
@@ -171,7 +164,7 @@ module Redmine
# Retrieve changed paths for a single patch # Retrieve changed paths for a single patch
def get_paths_for_patch(hash) def get_paths_for_patch(hash)
cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output" cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
cmd << " --match #{shell_quote("hash #{hash}")} " cmd << " --match \"hash #{hash}\" "
paths = [] paths = []
shellout(cmd) do |io| shellout(cmd) do |io|
begin begin
+1 -1
View File
@@ -4,7 +4,7 @@ module Redmine
module VERSION #:nodoc: module VERSION #:nodoc:
MAJOR = 0 MAJOR = 0
MINOR = 8 MINOR = 8
TINY = 1 TINY = 0
# Branch values: # Branch values:
# * official release: nil # * official release: nil
+2 -9
View File
@@ -1,4 +1,4 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang # Copyright (C) 2006-2008 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
@@ -71,11 +71,6 @@ Issue attributes control options:
allow_override=ATTRS allow email content to override attributes allow_override=ATTRS allow email content to override attributes
specified by previous options specified by previous options
ATTRS is a comma separated list of attributes ATTRS is a comma separated list of attributes
Processed emails control options:
move_on_success=MAILBOX move emails that were successfully received
to MAILBOX instead of deleting them
move_on_failure=MAILBOX move emails that were ignored to MAILBOX
Examples: Examples:
# No project specified. Emails MUST contain the 'Project' keyword: # No project specified. Emails MUST contain the 'Project' keyword:
@@ -100,9 +95,7 @@ END_DESC
:ssl => ENV['ssl'], :ssl => ENV['ssl'],
:username => ENV['username'], :username => ENV['username'],
:password => ENV['password'], :password => ENV['password'],
:folder => ENV['folder'], :folder => ENV['folder']}
:move_on_success => ENV['move_on_success'],
:move_on_failure => ENV['move_on_failure']}
options = { :issue => {} } options = { :issue => {} }
%w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
+127 -130
View File
@@ -5,12 +5,12 @@
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
@@ -22,10 +22,10 @@ require 'pp'
namespace :redmine do namespace :redmine do
desc 'Trac migration script' desc 'Trac migration script'
task :migrate_from_trac => :environment do task :migrate_from_trac => :environment do
module TracMigrate module TracMigrate
TICKET_MAP = [] TICKET_MAP = []
DEFAULT_STATUS = IssueStatus.default DEFAULT_STATUS = IssueStatus.default
assigned_status = IssueStatus.find_by_position(2) assigned_status = IssueStatus.find_by_position(2)
resolved_status = IssueStatus.find_by_position(3) resolved_status = IssueStatus.find_by_position(3)
@@ -36,7 +36,7 @@ namespace :redmine do
'assigned' => assigned_status, 'assigned' => assigned_status,
'closed' => closed_status 'closed' => closed_status
} }
priorities = Enumeration.get_values('IPRI') priorities = Enumeration.get_values('IPRI')
DEFAULT_PRIORITY = priorities[0] DEFAULT_PRIORITY = priorities[0]
PRIORITY_MAPPING = {'lowest' => priorities[0], PRIORITY_MAPPING = {'lowest' => priorities[0],
@@ -51,7 +51,7 @@ namespace :redmine do
'critical' => priorities[3], 'critical' => priorities[3],
'blocker' => priorities[4] 'blocker' => priorities[4]
} }
TRACKER_BUG = Tracker.find_by_position(1) TRACKER_BUG = Tracker.find_by_position(1)
TRACKER_FEATURE = Tracker.find_by_position(2) TRACKER_FEATURE = Tracker.find_by_position(2)
DEFAULT_TRACKER = TRACKER_BUG DEFAULT_TRACKER = TRACKER_BUG
@@ -60,7 +60,7 @@ namespace :redmine do
'task' => TRACKER_FEATURE, 'task' => TRACKER_FEATURE,
'patch' =>TRACKER_FEATURE 'patch' =>TRACKER_FEATURE
} }
roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC') roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC')
manager_role = roles[0] manager_role = roles[0]
developer_role = roles[1] developer_role = roles[1]
@@ -68,7 +68,7 @@ namespace :redmine do
ROLE_MAPPING = {'admin' => manager_role, ROLE_MAPPING = {'admin' => manager_role,
'developer' => developer_role 'developer' => developer_role
} }
class ::Time class ::Time
class << self class << self
alias :real_now :now alias :real_now :now
@@ -87,10 +87,10 @@ namespace :redmine do
class TracComponent < ActiveRecord::Base class TracComponent < ActiveRecord::Base
set_table_name :component set_table_name :component
end end
class TracMilestone < ActiveRecord::Base class TracMilestone < ActiveRecord::Base
set_table_name :milestone set_table_name :milestone
# If this attribute is set a milestone has a defined target timepoint # If this attribute is set a milestone has a defined target timepoint
def due def due
if read_attribute(:due) && read_attribute(:due) > 0 if read_attribute(:due) && read_attribute(:due) > 0
Time.at(read_attribute(:due)).to_date Time.at(read_attribute(:due)).to_date
@@ -112,37 +112,37 @@ namespace :redmine do
has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description) has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description)
end end
end end
class TracTicketCustom < ActiveRecord::Base class TracTicketCustom < ActiveRecord::Base
set_table_name :ticket_custom set_table_name :ticket_custom
end end
class TracAttachment < ActiveRecord::Base class TracAttachment < ActiveRecord::Base
set_table_name :attachment set_table_name :attachment
set_inheritance_column :none set_inheritance_column :none
def time; Time.at(read_attribute(:time)) end def time; Time.at(read_attribute(:time)) end
def original_filename def original_filename
filename filename
end end
def content_type def content_type
Redmine::MimeType.of(filename) || '' Redmine::MimeType.of(filename) || ''
end end
def exist? def exist?
File.file? trac_fullpath File.file? trac_fullpath
end end
def read def read
File.open("#{trac_fullpath}", 'rb').read File.open("#{trac_fullpath}", 'rb').read
end end
def description def description
read_attribute(:description).to_s.slice(0,255) read_attribute(:description).to_s.slice(0,255)
end end
private private
def trac_fullpath def trac_fullpath
attachment_type = read_attribute(:type) attachment_type = read_attribute(:type)
@@ -150,11 +150,11 @@ namespace :redmine do
"#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}" "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}"
end end
end end
class TracTicket < ActiveRecord::Base class TracTicket < ActiveRecord::Base
set_table_name :ticket set_table_name :ticket
set_inheritance_column :none set_inheritance_column :none
# ticket changes: only migrate status changes and comments # ticket changes: only migrate status changes and comments
has_many :changes, :class_name => "TracTicketChange", :foreign_key => :ticket has_many :changes, :class_name => "TracTicketChange", :foreign_key => :ticket
has_many :attachments, :class_name => "TracAttachment", has_many :attachments, :class_name => "TracAttachment",
@@ -162,29 +162,29 @@ namespace :redmine do
" WHERE #{TracMigrate::TracAttachment.table_name}.type = 'ticket'" + " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'ticket'" +
' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\'' ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\''
has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket
def ticket_type def ticket_type
read_attribute(:type) read_attribute(:type)
end end
def summary def summary
read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary) read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary)
end end
def description def description
read_attribute(:description).blank? ? summary : read_attribute(:description) read_attribute(:description).blank? ? summary : read_attribute(:description)
end end
def time; Time.at(read_attribute(:time)) end def time; Time.at(read_attribute(:time)) end
def changetime; Time.at(read_attribute(:changetime)) end def changetime; Time.at(read_attribute(:changetime)) end
end end
class TracTicketChange < ActiveRecord::Base class TracTicketChange < ActiveRecord::Base
set_table_name :ticket_change set_table_name :ticket_change
def time; Time.at(read_attribute(:time)) end def time; Time.at(read_attribute(:time)) end
end end
TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \ TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \
TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \ TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \
TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \ TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \
@@ -192,35 +192,35 @@ namespace :redmine do
TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \ TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \
WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \ WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \
CamelCase TitleIndex) CamelCase TitleIndex)
class TracWikiPage < ActiveRecord::Base class TracWikiPage < ActiveRecord::Base
set_table_name :wiki set_table_name :wiki
set_primary_key :name set_primary_key :name
has_many :attachments, :class_name => "TracAttachment", has_many :attachments, :class_name => "TracAttachment",
:finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" + :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" +
" WHERE #{TracMigrate::TracAttachment.table_name}.type = 'wiki'" + " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'wiki'" +
' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\'' ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\''
def self.columns def self.columns
# Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0) # Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0)
super.select {|column| column.name.to_s != 'readonly'} super.select {|column| column.name.to_s != 'readonly'}
end end
def time; Time.at(read_attribute(:time)) end def time; Time.at(read_attribute(:time)) end
end end
class TracPermission < ActiveRecord::Base class TracPermission < ActiveRecord::Base
set_table_name :permission set_table_name :permission
end end
class TracSessionAttribute < ActiveRecord::Base class TracSessionAttribute < ActiveRecord::Base
set_table_name :session_attribute set_table_name :session_attribute
end end
def self.find_or_create_user(username, project_member = false) def self.find_or_create_user(username, project_member = false)
return User.anonymous if username.blank? return User.anonymous if username.blank?
u = User.find_by_login(username) u = User.find_by_login(username)
if !u if !u
# Create a new user if not found # Create a new user if not found
@@ -229,7 +229,7 @@ namespace :redmine do
mail = mail_attr.value mail = mail_attr.value
end end
mail = "#{mail}@foo.bar" unless mail.include?("@") mail = "#{mail}@foo.bar" unless mail.include?("@")
name = username name = username
if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name') if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name')
name = name_attr.value name = name_attr.value
@@ -237,7 +237,7 @@ namespace :redmine do
name =~ (/(.*)(\s+\w+)?/) name =~ (/(.*)(\s+\w+)?/)
fn = $1.strip fn = $1.strip
ln = ($2 || '-').strip ln = ($2 || '-').strip
u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'), u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'),
:firstname => fn[0, limit_for(User, 'firstname')].gsub(/[^\w\s\'\-]/i, '-'), :firstname => fn[0, limit_for(User, 'firstname')].gsub(/[^\w\s\'\-]/i, '-'),
:lastname => ln[0, limit_for(User, 'lastname')].gsub(/[^\w\s\'\-]/i, '-') :lastname => ln[0, limit_for(User, 'lastname')].gsub(/[^\w\s\'\-]/i, '-')
@@ -261,7 +261,7 @@ namespace :redmine do
end end
u u
end end
# Basic wiki syntax conversion # Basic wiki syntax conversion
def self.convert_wiki_text(text) def self.convert_wiki_text(text)
# Titles # Titles
@@ -282,7 +282,7 @@ namespace :redmine do
# [milestone:"0.1.0 Mercury"] # [milestone:"0.1.0 Mercury"]
text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"') text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"')
text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"') text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"')
# milestone:0.1.0 # milestone:0.1.0
text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1') text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1')
text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1') text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1')
# Internal Links # Internal Links
@@ -293,11 +293,11 @@ namespace :redmine do
text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"} text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"}
# Links to pages UsingJustWikiCaps # Links to pages UsingJustWikiCaps
text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]') text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]')
# Normalize things that were supposed to not be links # Normalize things that were supposed to not be links
# like !NotALink # like !NotALink
text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2') text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2')
# Revisions links # Revisions links
text = text.gsub(/\[(\d+)\]/, 'r\1') text = text.gsub(/\[(\d+)\]/, 'r\1')
# Ticket number re-writing # Ticket number re-writing
@@ -318,7 +318,7 @@ namespace :redmine do
shebang_re = /^\#\!([a-z]+)/ shebang_re = /^\#\!([a-z]+)/
# Regular expression for end of code # Regular expression for end of code
pre_end_re = /\}\}\}/ pre_end_re = /\}\}\}/
# Go through the whole text..extract it line by line # Go through the whole text..extract it line by line
text = text.gsub(/^(.*)$/) do |line| text = text.gsub(/^(.*)$/) do |line|
m_pre = pre_re.match(line) m_pre = pre_re.match(line)
@@ -338,7 +338,7 @@ namespace :redmine do
end end
end end
end end
line line
end end
# Highlighting # Highlighting
@@ -349,25 +349,25 @@ namespace :redmine do
text = text.gsub(/__/, '+') text = text.gsub(/__/, '+')
text = text.gsub(/~~/, '-') text = text.gsub(/~~/, '-')
text = text.gsub(/`/, '@') text = text.gsub(/`/, '@')
text = text.gsub(/,,/, '~') text = text.gsub(/,,/, '~')
# Lists # Lists
text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "} text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
text text
end end
def self.migrate def self.migrate
establish_connection establish_connection
# Quick database test # Quick database test
TracComponent.count TracComponent.count
migrated_components = 0 migrated_components = 0
migrated_milestones = 0 migrated_milestones = 0
migrated_tickets = 0 migrated_tickets = 0
migrated_custom_values = 0 migrated_custom_values = 0
migrated_ticket_attachments = 0 migrated_ticket_attachments = 0
migrated_wiki_edits = 0 migrated_wiki_edits = 0
migrated_wiki_attachments = 0 migrated_wiki_attachments = 0
#Wiki system initializing... #Wiki system initializing...
@@ -375,21 +375,21 @@ namespace :redmine do
@target_project.reload @target_project.reload
wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart') wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart')
wiki_edit_count = 0 wiki_edit_count = 0
# Components # Components
print "Migrating components" print "Migrating components"
issues_category_map = {} issues_category_map = {}
TracComponent.find(:all).each do |component| TracComponent.find(:all).each do |component|
print '.' print '.'
STDOUT.flush STDOUT.flush
c = IssueCategory.new :project => @target_project, c = IssueCategory.new :project => @target_project,
:name => encode(component.name[0, limit_for(IssueCategory, 'name')]) :name => encode(component.name[0, limit_for(IssueCategory, 'name')])
next unless c.save next unless c.save
issues_category_map[component.name] = c issues_category_map[component.name] = c
migrated_components += 1 migrated_components += 1
end end
puts puts
# Milestones # Milestones
print "Migrating milestones" print "Migrating milestones"
version_map = {} version_map = {}
@@ -415,7 +415,7 @@ namespace :redmine do
migrated_milestones += 1 migrated_milestones += 1
end end
puts puts
# Custom fields # Custom fields
# TODO: read trac.ini instead # TODO: read trac.ini instead
print "Migrating custom fields" print "Migrating custom fields"
@@ -430,14 +430,14 @@ namespace :redmine do
# Or create a new one # Or create a new one
f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize, f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
:field_format => 'string') :field_format => 'string')
next if f.new_record? next if f.new_record?
f.trackers = Tracker.find(:all) f.trackers = Tracker.find(:all)
f.projects << @target_project f.projects << @target_project
custom_field_map[field.name] = f custom_field_map[field.name] = f
end end
puts puts
# Trac 'resolution' field as a Redmine custom field # Trac 'resolution' field as a Redmine custom field
r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" }) r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
r = IssueCustomField.new(:name => 'Resolution', r = IssueCustomField.new(:name => 'Resolution',
@@ -448,44 +448,45 @@ namespace :redmine do
r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
r.save! r.save!
custom_field_map['resolution'] = r custom_field_map['resolution'] = r
# Tickets # Tickets
print "Migrating tickets" print "Migrating tickets"
TracTicket.find(:all, :order => 'id ASC').each do |ticket| TracTicket.find(:all, :order => 'id ASC').each do |ticket|
print '.' print '.'
STDOUT.flush STDOUT.flush
i = Issue.new :project => @target_project, i = Issue.new :project => @target_project,
:subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]), :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
:description => convert_wiki_text(encode(ticket.description)), :description => convert_wiki_text(encode(ticket.description)),
:priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY, :priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY,
:created_on => ticket.time :created_on => ticket.time
i.author = find_or_create_user(ticket.reporter) i.author = find_or_create_user(ticket.reporter)
i.category = issues_category_map[ticket.component] unless ticket.component.blank? i.category = issues_category_map[ticket.component] unless ticket.component.blank?
i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank? i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
i.id = ticket.id unless Issue.exists?(ticket.id) i.custom_values << CustomValue.new(:custom_field => custom_field_map['resolution'], :value => ticket.resolution) unless ticket.resolution.blank?
next unless Time.fake(ticket.changetime) { i.save } i.id = ticket.id unless Issue.exists?(ticket.id)
TICKET_MAP[ticket.id] = i.id next unless Time.fake(ticket.changetime) { i.save }
migrated_tickets += 1 TICKET_MAP[ticket.id] = i.id
migrated_tickets += 1
# Owner
# Owner
unless ticket.owner.blank? unless ticket.owner.blank?
i.assigned_to = find_or_create_user(ticket.owner, true) i.assigned_to = find_or_create_user(ticket.owner, true)
Time.fake(ticket.changetime) { i.save } Time.fake(ticket.changetime) { i.save }
end end
# Comments and status/resolution changes # Comments and status/resolution changes
ticket.changes.group_by(&:time).each do |time, changeset| ticket.changes.group_by(&:time).each do |time, changeset|
status_change = changeset.select {|change| change.field == 'status'}.first status_change = changeset.select {|change| change.field == 'status'}.first
resolution_change = changeset.select {|change| change.field == 'resolution'}.first resolution_change = changeset.select {|change| change.field == 'resolution'}.first
comment_change = changeset.select {|change| change.field == 'comment'}.first comment_change = changeset.select {|change| change.field == 'comment'}.first
n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''), n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''),
:created_on => time :created_on => time
n.user = find_or_create_user(changeset.first.author) n.user = find_or_create_user(changeset.first.author)
n.journalized = i n.journalized = i
if status_change && if status_change &&
STATUS_MAPPING[status_change.oldvalue] && STATUS_MAPPING[status_change.oldvalue] &&
STATUS_MAPPING[status_change.newvalue] && STATUS_MAPPING[status_change.newvalue] &&
(STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue]) (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue])
@@ -501,39 +502,35 @@ namespace :redmine do
:value => resolution_change.newvalue) :value => resolution_change.newvalue)
end end
n.save unless n.details.empty? && n.notes.blank? n.save unless n.details.empty? && n.notes.blank?
end end
# Attachments # Attachments
ticket.attachments.each do |attachment| ticket.attachments.each do |attachment|
next unless attachment.exist? next unless attachment.exist?
a = Attachment.new :created_on => attachment.time a = Attachment.new :created_on => attachment.time
a.file = attachment a.file = attachment
a.author = find_or_create_user(attachment.author) a.author = find_or_create_user(attachment.author)
a.container = i a.container = i
a.description = attachment.description a.description = attachment.description
migrated_ticket_attachments += 1 if a.save migrated_ticket_attachments += 1 if a.save
end end
# Custom fields # Custom fields
custom_values = ticket.customs.inject({}) do |h, custom| ticket.customs.each do |custom|
if custom_field = custom_field_map[custom.name] next if custom_field_map[custom.name].nil?
h[custom_field.id] = custom.value v = CustomValue.new :custom_field => custom_field_map[custom.name],
:value => custom.value
v.customized = i
next unless v.save
migrated_custom_values += 1 migrated_custom_values += 1
end end
h
end
if custom_field_map['resolution'] && !ticket.resolution.blank?
custom_values[custom_field_map['resolution'].id] = ticket.resolution
end
i.custom_field_values = custom_values
i.save_custom_field_values
end end
# update issue id sequence if needed (postgresql) # update issue id sequence if needed (postgresql)
Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!') Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
puts puts
# Wiki # Wiki
print "Migrating wiki" print "Migrating wiki"
if wiki.save if wiki.save
TracWikiPage.find(:all, :order => 'name, version').each do |page| TracWikiPage.find(:all, :order => 'name, version').each do |page|
@@ -548,10 +545,10 @@ namespace :redmine do
p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac' p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac'
p.content.comments = page.comment p.content.comments = page.comment
Time.fake(page.time) { p.new_record? ? p.save : p.content.save } Time.fake(page.time) { p.new_record? ? p.save : p.content.save }
next if p.content.new_record? next if p.content.new_record?
migrated_wiki_edits += 1 migrated_wiki_edits += 1
# Attachments # Attachments
page.attachments.each do |attachment| page.attachments.each do |attachment|
next unless attachment.exist? next unless attachment.exist?
@@ -564,7 +561,7 @@ namespace :redmine do
migrated_wiki_attachments += 1 if a.save migrated_wiki_attachments += 1 if a.save
end end
end end
wiki.reload wiki.reload
wiki.pages.each do |page| wiki.pages.each do |page|
page.content.text = convert_wiki_text(page.content.text) page.content.text = convert_wiki_text(page.content.text)
@@ -572,7 +569,7 @@ namespace :redmine do
end end
end end
puts puts
puts puts
puts "Components: #{migrated_components}/#{TracComponent.count}" puts "Components: #{migrated_components}/#{TracComponent.count}"
puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}" puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}"
@@ -582,18 +579,18 @@ namespace :redmine do
puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}" puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}"
puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s
end end
def self.limit_for(klass, attribute) def self.limit_for(klass, attribute)
klass.columns_hash[attribute.to_s].limit klass.columns_hash[attribute.to_s].limit
end end
def self.encoding(charset) def self.encoding(charset)
@ic = Iconv.new('UTF-8', charset) @ic = Iconv.new('UTF-8', charset)
rescue Iconv::InvalidEncoding rescue Iconv::InvalidEncoding
puts "Invalid encoding!" puts "Invalid encoding!"
return false return false
end end
def self.set_trac_directory(path) def self.set_trac_directory(path)
@@trac_directory = path @@trac_directory = path
raise "This directory doesn't exist!" unless File.directory?(path) raise "This directory doesn't exist!" unless File.directory?(path)
@@ -618,7 +615,7 @@ namespace :redmine do
puts e puts e
return false return false
end end
def self.set_trac_db_host(host) def self.set_trac_db_host(host)
return nil if host.blank? return nil if host.blank?
@@trac_db_host = host @@trac_db_host = host
@@ -628,7 +625,7 @@ namespace :redmine do
return nil if port.to_i == 0 return nil if port.to_i == 0
@@trac_db_port = port.to_i @@trac_db_port = port.to_i
end end
def self.set_trac_db_name(name) def self.set_trac_db_name(name)
return nil if name.blank? return nil if name.blank?
@@trac_db_name = name @@trac_db_name = name
@@ -637,22 +634,22 @@ namespace :redmine do
def self.set_trac_db_username(username) def self.set_trac_db_username(username)
@@trac_db_username = username @@trac_db_username = username
end end
def self.set_trac_db_password(password) def self.set_trac_db_password(password)
@@trac_db_password = password @@trac_db_password = password
end end
def self.set_trac_db_schema(schema) def self.set_trac_db_schema(schema)
@@trac_db_schema = schema @@trac_db_schema = schema
end end
mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password
def self.trac_db_path; "#{trac_directory}/db/trac.db" end def self.trac_db_path; "#{trac_directory}/db/trac.db" end
def self.trac_attachments_directory; "#{trac_directory}/attachments" end def self.trac_attachments_directory; "#{trac_directory}/attachments" end
def self.target_project_identifier(identifier) def self.target_project_identifier(identifier)
project = Project.find_by_identifier(identifier) project = Project.find_by_identifier(identifier)
if !project if !project
# create the target project # create the target project
project = Project.new :name => identifier.humanize, project = Project.new :name => identifier.humanize,
@@ -665,16 +662,16 @@ namespace :redmine do
puts puts
puts "This project already exists in your Redmine database." puts "This project already exists in your Redmine database."
print "Are you sure you want to append data to this project ? [Y/n] " print "Are you sure you want to append data to this project ? [Y/n] "
exit if STDIN.gets.match(/^n$/i) exit if STDIN.gets.match(/^n$/i)
end end
project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG) project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE) project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
@target_project = project.new_record? ? nil : project @target_project = project.new_record? ? nil : project
end end
def self.connection_params def self.connection_params
if %w(sqlite sqlite3).include?(trac_adapter) if %w(sqlite sqlite3).include?(trac_adapter)
{:adapter => trac_adapter, {:adapter => trac_adapter,
:database => trac_db_path} :database => trac_db_path}
else else
{:adapter => trac_adapter, {:adapter => trac_adapter,
@@ -687,7 +684,7 @@ namespace :redmine do
} }
end end
end end
def self.establish_connection def self.establish_connection
constants.each do |const| constants.each do |const|
klass = const_get(const) klass = const_get(const)
@@ -695,7 +692,7 @@ namespace :redmine do
klass.establish_connection connection_params klass.establish_connection connection_params
end end
end end
private private
def self.encode(text) def self.encode(text)
@ic.iconv text @ic.iconv text
@@ -703,7 +700,7 @@ namespace :redmine do
text text
end end
end end
puts puts
if Redmine::DefaultData::Loader.no_data? if Redmine::DefaultData::Loader.no_data?
puts "Redmine configuration need to be loaded before importing data." puts "Redmine configuration need to be loaded before importing data."
@@ -712,10 +709,10 @@ namespace :redmine do
puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\"" puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
exit exit
end end
puts "WARNING: a new project will be added to Redmine during this process." puts "WARNING: a new project will be added to Redmine during this process."
print "Are you sure you want to continue ? [y/N] " print "Are you sure you want to continue ? [y/N] "
break unless STDIN.gets.match(/^y$/i) break unless STDIN.gets.match(/^y$/i)
puts puts
def prompt(text, options = {}, &block) def prompt(text, options = {}, &block)
@@ -727,9 +724,9 @@ namespace :redmine do
break if yield value break if yield value
end end
end end
DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432} DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432}
prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip} prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip}
prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite') {|adapter| TracMigrate.set_trac_adapter adapter} prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite') {|adapter| TracMigrate.set_trac_adapter adapter}
unless %w(sqlite sqlite3).include?(TracMigrate.trac_adapter) unless %w(sqlite sqlite3).include?(TracMigrate.trac_adapter)
@@ -743,7 +740,7 @@ namespace :redmine do
prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding} prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding}
prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier} prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier}
puts puts
TracMigrate.migrate TracMigrate.migrate
end end
end end
+1 -1
View File
@@ -61,7 +61,7 @@
<ul> <ul>
<li><strong>[[sandbox:some page]]</strong> displays a link to the page named 'Some page' of the Sandbox wiki</li> <li><strong>[[sandbox:some page]]</strong> displays a link to the page named 'Some page' of the Sandbox wiki</li>
<li><strong>[[sandbox:]]</strong> displays a link to the Sandbox wiki main page</li> <li><strong>[[sandbox]]</strong> displays a link to the Sandbox wiki main page</li>
</ul> </ul>
<p>Wiki links are displayed in red if the page doesn't exist yet, eg: <a href="Nonexistent_page.html" class="wiki-page new">Nonexistent page</a>.</p> <p>Wiki links are displayed in red if the page doesn't exist yet, eg: <a href="Nonexistent_page.html" class="wiki-page new">Nonexistent page</a>.</p>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 B

After

Width:  |  Height:  |  Size: 535 B

-8
View File
@@ -314,14 +314,6 @@ div.flash.notice {
color: #005f00; color: #005f00;
} }
div.flash.warning {
background: url(../images/warning.png) 8px 5px no-repeat;
background-color: #FFEBC1;
border-color: #FDBF3B;
color: #A6750C;
text-align: left;
}
.nodata, .warning { .nodata, .warning {
text-align: center; text-align: center;
background-color: #FFEBC1; background-color: #FFEBC1;
-24
View File
@@ -85,28 +85,4 @@ attachments_007:
filename: archive.zip filename: archive.zip
author_id: 1 author_id: 1
content_type: application/octet-stream content_type: application/octet-stream
attachments_008:
created_on: 2006-07-19 21:07:27 +02:00
container_type: Project
container_id: 1
downloads: 0
disk_filename: 060719210727_project_file.zip
digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
id: 8
filesize: 320
filename: project_file.zip
author_id: 2
content_type: application/octet-stream
attachments_009:
created_on: 2006-07-19 21:07:27 +02:00
container_type: Version
container_id: 1
downloads: 0
disk_filename: 060719210727_version_file.zip
digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
id: 9
filesize: 452
filename: version_file.zip
author_id: 2
content_type: application/octet-stream

Some files were not shown because too many files have changed in this diff Show More