##// END OF EJS Templates
Basic plugin support....
Jean-Philippe Lang -
r741:e4f0864e3a7f
parent child
Show More
@@ -0,0 +1,8
1 <h2><%= l(:label_settings) %>: <%=h @plugin.name %></h2>
2
3 <% form_tag({:action => 'plugin'}) do %>
4 <div class="box tabular">
5 <%= render :partial => @partial, :locals => {:settings => @settings}%>
6 </div>
7 <%= submit_tag l(:button_apply) %>
8 <% end %>
@@ -0,0 +1,124
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 module Redmine #:nodoc:
19
20 # Base class for Redmine plugins.
21 # Plugins are registered using the <tt>register</tt> class method that acts as the public constructor.
22 #
23 # Redmine::Plugin.register :example do
24 # name 'Example plugin'
25 # author 'John Smith'
26 # description 'This is an example plugin for Redmine'
27 # version '0.0.1'
28 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
29 # end
30 #
31 # === Plugin attributes
32 #
33 # +settings+ is an optional attribute that let the plugin be configurable.
34 # It must be a hash with the following keys:
35 # * <tt>:default</tt>: default value for the plugin settings
36 # * <tt>:partial</tt>: path of the configuration partial view, relative to the plugin <tt>app/views</tt> directory
37 # Example:
38 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
39 # In this example, the settings partial will be found here in the plugin directory: <tt>app/views/settings/_settings.rhtml</tt>.
40 #
41 # When rendered, the plugin settings value is available as the local variable +settings+
42 class Plugin
43 @registered_plugins = {}
44 class << self
45 attr_reader :registered_plugins
46 private :new
47
48 def def_field(*names)
49 class_eval do
50 names.each do |name|
51 define_method(name) do |*args|
52 args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args)
53 end
54 end
55 end
56 end
57 end
58 def_field :name, :description, :author, :version, :settings
59
60 # Plugin constructor
61 def self.register(name, &block)
62 p = new
63 p.instance_eval(&block)
64 Plugin.registered_plugins[name] = p
65 end
66
67 # Adds an item to the given +menu+.
68 # The +id+ parameter (equals to the project id) is automatically added to the url.
69 # menu :project_menu, :label_plugin_example, :controller => 'example', :action => 'say_hello'
70 #
71 # Currently, only the project menu can be extended. Thus, the +name+ parameter must be +:project_menu+
72 def menu(name, label, url)
73 Redmine::MenuManager.map(name) {|menu| menu.push label, url}
74 end
75
76 # Defines a permission called +name+ for the given +actions+.
77 #
78 # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array):
79 # permission :destroy_contacts, { :contacts => :destroy }
80 # permission :view_contacts, { :contacts => [:index, :show] }
81 #
82 # The +options+ argument can be used to make the permission public (implicitly given to any user)
83 # or to restrict users the permission can be given to.
84 #
85 # Examples
86 # # A permission that is implicitly given to any user
87 # # This permission won't appear on the Roles & Permissions setup screen
88 # permission :say_hello, { :example => :say_hello }, :public => true
89 #
90 # # A permission that can be given to any user
91 # permission :say_hello, { :example => :say_hello }
92 #
93 # # A permission that can be given to registered users only
94 # permission :say_hello, { :example => :say_hello }, :require => loggedin
95 #
96 # # A permission that can be given to project members only
97 # permission :say_hello, { :example => :say_hello }, :require => member
98 def permission(name, actions, options = {})
99 if @project_module
100 Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}}
101 else
102 Redmine::AccessControl.map {|map| map.permission(name, actions, options)}
103 end
104 end
105
106 # Defines a project module, that can be enabled/disabled for each project.
107 # Permissions defined inside +block+ will be bind to the module.
108 #
109 # project_module :things do
110 # permission :view_contacts, { :contacts => [:list, :show] }, :public => true
111 # permission :destroy_contacts, { :contacts => :destroy }
112 # end
113 def project_module(name, &block)
114 @project_module = name
115 self.instance_eval(&block)
116 @project_module = nil
117 end
118
119 # Returns +true+ if the plugin can be configured.
120 def configurable?
121 settings && settings.is_a?(Hash) && !settings[:partial].blank?
122 end
123 end
124 end
@@ -0,0 +1,15
1 namespace :db do
2 desc 'Migrates installed plugins.'
3 task :migrate_plugins => :environment do
4 if Rails.respond_to?('plugins')
5 Rails.plugins.each do |plugin|
6 next unless plugin.respond_to?('migrate')
7 puts "Migrating #{plugin.name}..."
8 plugin.migrate
9 end
10 else
11 puts "Undefined method plugins for Rails!"
12 puts "Make sure engines plugin is installed."
13 end
14 end
15 end
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -1,79 +1,80
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class AdminController < ApplicationController
18 class AdminController < ApplicationController
19 layout 'base'
19 layout 'base'
20 before_filter :require_admin
20 before_filter :require_admin
21
21
22 helper :sort
22 helper :sort
23 include SortHelper
23 include SortHelper
24
24
25 def index
25 def index
26 end
26 end
27
27
28 def projects
28 def projects
29 sort_init 'name', 'asc'
29 sort_init 'name', 'asc'
30 sort_update
30 sort_update
31
31
32 @status = params[:status] ? params[:status].to_i : 0
32 @status = params[:status] ? params[:status].to_i : 0
33 conditions = nil
33 conditions = nil
34 conditions = ["status=?", @status] unless @status == 0
34 conditions = ["status=?", @status] unless @status == 0
35
35
36 @project_count = Project.count(:conditions => conditions)
36 @project_count = Project.count(:conditions => conditions)
37 @project_pages = Paginator.new self, @project_count,
37 @project_pages = Paginator.new self, @project_count,
38 25,
38 25,
39 params['page']
39 params['page']
40 @projects = Project.find :all, :order => sort_clause,
40 @projects = Project.find :all, :order => sort_clause,
41 :conditions => conditions,
41 :conditions => conditions,
42 :limit => @project_pages.items_per_page,
42 :limit => @project_pages.items_per_page,
43 :offset => @project_pages.current.offset
43 :offset => @project_pages.current.offset
44
44
45 render :action => "projects", :layout => false if request.xhr?
45 render :action => "projects", :layout => false if request.xhr?
46 end
46 end
47
47
48 def mail_options
48 def mail_options
49 @notifiables = %w(issue_added issue_updated news_added document_added file_added message_posted)
49 @notifiables = %w(issue_added issue_updated news_added document_added file_added message_posted)
50 if request.post?
50 if request.post?
51 Setting.notified_events = (params[:notified_events] || [])
51 Setting.notified_events = (params[:notified_events] || [])
52 flash[:notice] = l(:notice_successful_update)
52 flash[:notice] = l(:notice_successful_update)
53 redirect_to :controller => 'admin', :action => 'mail_options'
53 redirect_to :controller => 'admin', :action => 'mail_options'
54 end
54 end
55 end
55 end
56
56
57 def test_email
57 def test_email
58 raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
58 raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
59 # Force ActionMailer to raise delivery errors so we can catch it
59 # Force ActionMailer to raise delivery errors so we can catch it
60 ActionMailer::Base.raise_delivery_errors = true
60 ActionMailer::Base.raise_delivery_errors = true
61 begin
61 begin
62 @test = Mailer.deliver_test(User.current)
62 @test = Mailer.deliver_test(User.current)
63 flash[:notice] = l(:notice_email_sent, User.current.mail)
63 flash[:notice] = l(:notice_email_sent, User.current.mail)
64 rescue Exception => e
64 rescue Exception => e
65 flash[:error] = l(:notice_email_error, e.message)
65 flash[:error] = l(:notice_email_error, e.message)
66 end
66 end
67 ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
67 ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
68 redirect_to :action => 'mail_options'
68 redirect_to :action => 'mail_options'
69 end
69 end
70
70
71 def info
71 def info
72 @db_adapter_name = ActiveRecord::Base.connection.adapter_name
72 @db_adapter_name = ActiveRecord::Base.connection.adapter_name
73 @flags = {
73 @flags = {
74 :default_admin_changed => User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?,
74 :default_admin_changed => User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?,
75 :file_repository_writable => File.writable?(Attachment.storage_path),
75 :file_repository_writable => File.writable?(Attachment.storage_path),
76 :rmagick_available => Object.const_defined?(:Magick)
76 :rmagick_available => Object.const_defined?(:Magick)
77 }
77 }
78 @plugins = Redmine::Plugin.registered_plugins
78 end
79 end
79 end
80 end
@@ -1,33 +1,45
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class SettingsController < ApplicationController
18 class SettingsController < ApplicationController
19 layout 'base'
19 layout 'base'
20 before_filter :require_admin
20 before_filter :require_admin
21
21
22 def index
22 def index
23 edit
23 edit
24 render :action => 'edit'
24 render :action => 'edit'
25 end
25 end
26
26
27 def edit
27 def edit
28 if request.post? and params[:settings] and params[:settings].is_a? Hash
28 if request.post? and params[:settings] and params[:settings].is_a? Hash
29 params[:settings].each { |name, value| Setting[name] = value }
29 params[:settings].each { |name, value| Setting[name] = value }
30 redirect_to :action => 'edit' and return
30 redirect_to :action => 'edit' and return
31 end
31 end
32 end
32 end
33
34 def plugin
35 plugin_id = params[:id].to_sym
36 @plugin = Redmine::Plugin.registered_plugins[plugin_id]
37 if request.post?
38 Setting["plugin_#{plugin_id}"] = params[:settings]
39 flash[:notice] = l(:notice_successful_update)
40 redirect_to :action => 'plugin', :id => params[:id]
41 end
42 @partial = "../../vendor/plugins/#{plugin_id}/app/views/" + @plugin.settings[:partial]
43 @settings = Setting["plugin_#{plugin_id}"]
44 end
33 end
45 end
@@ -1,98 +1,102
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Setting < ActiveRecord::Base
18 class Setting < ActiveRecord::Base
19
19
20 cattr_accessor :available_settings
20 cattr_accessor :available_settings
21 @@available_settings = YAML::load(File.open("#{RAILS_ROOT}/config/settings.yml"))
21 @@available_settings = YAML::load(File.open("#{RAILS_ROOT}/config/settings.yml"))
22 Redmine::Plugin.registered_plugins.each do |id, plugin|
23 next unless plugin.settings
24 @@available_settings["plugin_#{id}"] = {'default' => plugin.settings[:default], 'serialized' => true}
25 end
22
26
23 validates_uniqueness_of :name
27 validates_uniqueness_of :name
24 validates_inclusion_of :name, :in => @@available_settings.keys
28 validates_inclusion_of :name, :in => @@available_settings.keys
25 validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting| @@available_settings[setting.name]['format'] == 'int' }
29 validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting| @@available_settings[setting.name]['format'] == 'int' }
26
30
27 # Hash used to cache setting values
31 # Hash used to cache setting values
28 @cached_settings = {}
32 @cached_settings = {}
29 @cached_cleared_on = Time.now
33 @cached_cleared_on = Time.now
30
34
31 def value
35 def value
32 v = read_attribute(:value)
36 v = read_attribute(:value)
33 # Unserialize serialized settings
37 # Unserialize serialized settings
34 v = YAML::load(v) if @@available_settings[name]['serialized'] && v.is_a?(String)
38 v = YAML::load(v) if @@available_settings[name]['serialized'] && v.is_a?(String)
35 v
39 v
36 end
40 end
37
41
38 def value=(v)
42 def value=(v)
39 v = v.to_yaml if v && @@available_settings[name]['serialized']
43 v = v.to_yaml if v && @@available_settings[name]['serialized']
40 write_attribute(:value, v)
44 write_attribute(:value, v)
41 end
45 end
42
46
43 # Returns the value of the setting named name
47 # Returns the value of the setting named name
44 def self.[](name)
48 def self.[](name)
45 v = @cached_settings[name]
49 v = @cached_settings[name]
46 v ? v : (@cached_settings[name] = find_or_default(name).value)
50 v ? v : (@cached_settings[name] = find_or_default(name).value)
47 end
51 end
48
52
49 def self.[]=(name, v)
53 def self.[]=(name, v)
50 setting = find_or_default(name)
54 setting = find_or_default(name)
51 setting.value = (v ? v : "")
55 setting.value = (v ? v : "")
52 @cached_settings[name] = nil
56 @cached_settings[name] = nil
53 setting.save
57 setting.save
54 setting.value
58 setting.value
55 end
59 end
56
60
57 # Defines getter and setter for each setting
61 # Defines getter and setter for each setting
58 # Then setting values can be read using: Setting.some_setting_name
62 # Then setting values can be read using: Setting.some_setting_name
59 # or set using Setting.some_setting_name = "some value"
63 # or set using Setting.some_setting_name = "some value"
60 @@available_settings.each do |name, params|
64 @@available_settings.each do |name, params|
61 src = <<-END_SRC
65 src = <<-END_SRC
62 def self.#{name}
66 def self.#{name}
63 self[:#{name}]
67 self[:#{name}]
64 end
68 end
65
69
66 def self.#{name}?
70 def self.#{name}?
67 self[:#{name}].to_i > 0
71 self[:#{name}].to_i > 0
68 end
72 end
69
73
70 def self.#{name}=(value)
74 def self.#{name}=(value)
71 self[:#{name}] = value
75 self[:#{name}] = value
72 end
76 end
73 END_SRC
77 END_SRC
74 class_eval src, __FILE__, __LINE__
78 class_eval src, __FILE__, __LINE__
75 end
79 end
76
80
77 # Checks if settings have changed since the values were read
81 # Checks if settings have changed since the values were read
78 # and clears the cache hash if it's the case
82 # and clears the cache hash if it's the case
79 # Called once per request
83 # Called once per request
80 def self.check_cache
84 def self.check_cache
81 settings_updated_on = Setting.maximum(:updated_on)
85 settings_updated_on = Setting.maximum(:updated_on)
82 if settings_updated_on && @cached_cleared_on <= settings_updated_on
86 if settings_updated_on && @cached_cleared_on <= settings_updated_on
83 @cached_settings.clear
87 @cached_settings.clear
84 @cached_cleared_on = Time.now
88 @cached_cleared_on = Time.now
85 logger.info "Settings cache cleared." if logger
89 logger.info "Settings cache cleared." if logger
86 end
90 end
87 end
91 end
88
92
89 private
93 private
90 # Returns the Setting instance for the setting named name
94 # Returns the Setting instance for the setting named name
91 # (record found in database or new record with default value)
95 # (record found in database or new record with default value)
92 def self.find_or_default(name)
96 def self.find_or_default(name)
93 name = name.to_s
97 name = name.to_s
94 raise "There's no setting named #{name}" unless @@available_settings.has_key?(name)
98 raise "There's no setting named #{name}" unless @@available_settings.has_key?(name)
95 setting = find_by_name(name)
99 setting = find_by_name(name)
96 setting ||= new(:name => name, :value => @@available_settings[name]['default']) if @@available_settings.has_key? name
100 setting ||= new(:name => name, :value => @@available_settings[name]['default']) if @@available_settings.has_key? name
97 end
101 end
98 end
102 end
@@ -1,9 +1,25
1 <h2><%=l(:label_information_plural)%></h2>
1 <h2><%=l(:label_information_plural)%></h2>
2
2
3 <p><%=l(:field_version)%>: <strong><%= Redmine::Info.versioned_name %></strong> (<%= @db_adapter_name %>)</p>
3 <p><%=l(:field_version)%>: <strong><%= Redmine::Info.versioned_name %></strong> (<%= @db_adapter_name %>)</p>
4
4
5 <table class="list">
5 <table class="list">
6 <tr class="odd"><td>Default administrator account changed</td><td><%= image_tag (@flags[:default_admin_changed] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
6 <tr class="odd"><td>Default administrator account changed</td><td><%= image_tag (@flags[:default_admin_changed] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
7 <tr class="even"><td>File repository writable</td><td><%= image_tag (@flags[:file_repository_writable] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
7 <tr class="even"><td>File repository writable</td><td><%= image_tag (@flags[:file_repository_writable] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
8 <tr class="odd"><td>RMagick available</td><td><%= image_tag (@flags[:rmagick_available] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
8 <tr class="odd"><td>RMagick available</td><td><%= image_tag (@flags[:rmagick_available] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
9 </table>
9 </table>
10
11 <% if @plugins.any? %>
12 &nbsp;
13 <h3 class="icon22 icon22-plugin">Plugins</h3>
14 <table class="list">
15 <% @plugins.keys.sort.each do |plugin| %>
16 <tr class="<%= cycle('odd', 'even') %>">
17 <td><%=h @plugins[plugin].name %></td>
18 <td><%=h @plugins[plugin].description %></td>
19 <td><%=h @plugins[plugin].author %></td>
20 <td><%=h @plugins[plugin].version %></td>
21 <td><%= link_to('Configure', :controller => 'settings', :action => 'plugin', :id => plugin.to_s) if @plugins[plugin].configurable? %></td>
22 </tr>
23 <% end %>
24 </table>
25 <% end %>
@@ -1,87 +1,91
1 # Be sure to restart your web server when you modify this file.
1 # Be sure to restart your web server when you modify this file.
2
2
3 # Uncomment below to force Rails into production mode when
3 # Uncomment below to force Rails into production mode when
4 # you don't control web/app server and can't set it the proper way
4 # you don't control web/app server and can't set it the proper way
5 # ENV['RAILS_ENV'] ||= 'production'
5 # ENV['RAILS_ENV'] ||= 'production'
6
6
7 # Bootstrap the Rails environment, frameworks, and default configuration
7 # Bootstrap the Rails environment, frameworks, and default configuration
8 require File.join(File.dirname(__FILE__), 'boot')
8 require File.join(File.dirname(__FILE__), 'boot')
9
9
10 Rails::Initializer.run do |config|
10 Rails::Initializer.run do |config|
11 # Settings in config/environments/* take precedence those specified here
11 # Settings in config/environments/* take precedence those specified here
12
12
13 # Skip frameworks you're not going to use
13 # Skip frameworks you're not going to use
14 # config.frameworks -= [ :action_web_service, :action_mailer ]
14 # config.frameworks -= [ :action_web_service, :action_mailer ]
15
15
16 # Add additional load paths for sweepers
16 # Add additional load paths for sweepers
17 config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
17 config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
18
18
19 # Force all environments to use the same logger level
19 # Force all environments to use the same logger level
20 # (by default production uses :info, the others :debug)
20 # (by default production uses :info, the others :debug)
21 # config.log_level = :debug
21 # config.log_level = :debug
22
22
23 # Use the database for sessions instead of the file system
23 # Use the database for sessions instead of the file system
24 # (create the session table with 'rake create_sessions_table')
24 # (create the session table with 'rake create_sessions_table')
25 # config.action_controller.session_store = :active_record_store
25 # config.action_controller.session_store = :active_record_store
26
26
27 # Enable page/fragment caching by setting a file-based store
27 # Enable page/fragment caching by setting a file-based store
28 # (remember to create the caching directory and make it readable to the application)
28 # (remember to create the caching directory and make it readable to the application)
29 # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
29 # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
30
30
31 # Activate observers that should always be running
31 # Activate observers that should always be running
32 # config.active_record.observers = :cacher, :garbage_collector
32 # config.active_record.observers = :cacher, :garbage_collector
33 config.active_record.observers = :message_observer
33 config.active_record.observers = :message_observer
34
34
35 # Make Active Record use UTC-base instead of local time
35 # Make Active Record use UTC-base instead of local time
36 # config.active_record.default_timezone = :utc
36 # config.active_record.default_timezone = :utc
37
37
38 # Use Active Record's schema dumper instead of SQL when creating the test database
38 # Use Active Record's schema dumper instead of SQL when creating the test database
39 # (enables use of different database adapters for development and test environments)
39 # (enables use of different database adapters for development and test environments)
40 # config.active_record.schema_format = :ruby
40 # config.active_record.schema_format = :ruby
41
41
42 # See Rails::Configuration for more options
42 # See Rails::Configuration for more options
43
43
44 # SMTP server configuration
44 # SMTP server configuration
45 config.action_mailer.smtp_settings = {
45 config.action_mailer.smtp_settings = {
46 :address => "127.0.0.1",
46 :address => "127.0.0.1",
47 :port => 25,
47 :port => 25,
48 :domain => "somenet.foo",
48 :domain => "somenet.foo",
49 :authentication => :login,
49 :authentication => :login,
50 :user_name => "redmine",
50 :user_name => "redmine",
51 :password => "redmine",
51 :password => "redmine",
52 }
52 }
53
53
54 config.action_mailer.perform_deliveries = true
54 config.action_mailer.perform_deliveries = true
55
55
56 # Tell ActionMailer not to deliver emails to the real world.
56 # Tell ActionMailer not to deliver emails to the real world.
57 # The :test delivery method accumulates sent emails in the
57 # The :test delivery method accumulates sent emails in the
58 # ActionMailer::Base.deliveries array.
58 # ActionMailer::Base.deliveries array.
59 #config.action_mailer.delivery_method = :test
59 #config.action_mailer.delivery_method = :test
60 config.action_mailer.delivery_method = :smtp
60 config.action_mailer.delivery_method = :smtp
61
62 # Uncomment this line if the engines plugin is installed.
63 # This will ensure that engines is loaded first.
64 # config.plugins = ["engines", "*"]
61 end
65 end
62
66
63 ActiveRecord::Errors.default_error_messages = {
67 ActiveRecord::Errors.default_error_messages = {
64 :inclusion => "activerecord_error_inclusion",
68 :inclusion => "activerecord_error_inclusion",
65 :exclusion => "activerecord_error_exclusion",
69 :exclusion => "activerecord_error_exclusion",
66 :invalid => "activerecord_error_invalid",
70 :invalid => "activerecord_error_invalid",
67 :confirmation => "activerecord_error_confirmation",
71 :confirmation => "activerecord_error_confirmation",
68 :accepted => "activerecord_error_accepted",
72 :accepted => "activerecord_error_accepted",
69 :empty => "activerecord_error_empty",
73 :empty => "activerecord_error_empty",
70 :blank => "activerecord_error_blank",
74 :blank => "activerecord_error_blank",
71 :too_long => "activerecord_error_too_long",
75 :too_long => "activerecord_error_too_long",
72 :too_short => "activerecord_error_too_short",
76 :too_short => "activerecord_error_too_short",
73 :wrong_length => "activerecord_error_wrong_length",
77 :wrong_length => "activerecord_error_wrong_length",
74 :taken => "activerecord_error_taken",
78 :taken => "activerecord_error_taken",
75 :not_a_number => "activerecord_error_not_a_number"
79 :not_a_number => "activerecord_error_not_a_number"
76 }
80 }
77
81
78 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" }
82 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" }
79
83
80 GLoc.set_config :default_language => :en
84 GLoc.set_config :default_language => :en
81 GLoc.clear_strings
85 GLoc.clear_strings
82 GLoc.set_kcode
86 GLoc.set_kcode
83 GLoc.load_localized_strings
87 GLoc.load_localized_strings
84 GLoc.set_config(:raise_string_not_found_errors => false)
88 GLoc.set_config(:raise_string_not_found_errors => false)
85
89
86 require 'redmine'
90 require 'redmine'
87
91
@@ -1,102 +1,103
1 require 'redmine/access_control'
1 require 'redmine/access_control'
2 require 'redmine/menu_manager'
2 require 'redmine/menu_manager'
3 require 'redmine/mime_type'
3 require 'redmine/mime_type'
4 require 'redmine/acts_as_watchable/init'
4 require 'redmine/acts_as_watchable/init'
5 require 'redmine/acts_as_event/init'
5 require 'redmine/acts_as_event/init'
6 require 'redmine/plugin'
6
7
7 begin
8 begin
8 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
9 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
9 rescue LoadError
10 rescue LoadError
10 # RMagick is not available
11 # RMagick is not available
11 end
12 end
12
13
13 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs )
14 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs )
14
15
15 # Permissions
16 # Permissions
16 Redmine::AccessControl.map do |map|
17 Redmine::AccessControl.map do |map|
17 map.permission :view_project, {:projects => [:show, :activity, :feeds]}, :public => true
18 map.permission :view_project, {:projects => [:show, :activity, :feeds]}, :public => true
18 map.permission :search_project, {:search => :index}, :public => true
19 map.permission :search_project, {:search => :index}, :public => true
19 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
20 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
20 map.permission :select_project_modules, {:projects => :modules}, :require => :member
21 map.permission :select_project_modules, {:projects => :modules}, :require => :member
21 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
22 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
22 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
23 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
23
24
24 map.project_module :issue_tracking do |map|
25 map.project_module :issue_tracking do |map|
25 # Issue categories
26 # Issue categories
26 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
27 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
27 # Issues
28 # Issues
28 map.permission :view_issues, {:projects => [:list_issues, :export_issues_csv, :export_issues_pdf, :changelog, :roadmap],
29 map.permission :view_issues, {:projects => [:list_issues, :export_issues_csv, :export_issues_pdf, :changelog, :roadmap],
29 :issues => [:show, :export_pdf],
30 :issues => [:show, :export_pdf],
30 :queries => :index,
31 :queries => :index,
31 :reports => :issue_report}, :public => true
32 :reports => :issue_report}, :public => true
32 map.permission :add_issues, {:projects => :add_issue}, :require => :loggedin
33 map.permission :add_issues, {:projects => :add_issue}, :require => :loggedin
33 map.permission :edit_issues, {:issues => [:edit, :destroy_attachment]}, :require => :loggedin
34 map.permission :edit_issues, {:issues => [:edit, :destroy_attachment]}, :require => :loggedin
34 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}, :require => :loggedin
35 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}, :require => :loggedin
35 map.permission :add_issue_notes, {:issues => :add_note}, :require => :loggedin
36 map.permission :add_issue_notes, {:issues => :add_note}, :require => :loggedin
36 map.permission :change_issue_status, {:issues => :change_status}, :require => :loggedin
37 map.permission :change_issue_status, {:issues => :change_status}, :require => :loggedin
37 map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin
38 map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin
38 map.permission :delete_issues, {:issues => :destroy}, :require => :member
39 map.permission :delete_issues, {:issues => :destroy}, :require => :member
39 # Queries
40 # Queries
40 map.permission :manage_pulic_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
41 map.permission :manage_pulic_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
41 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
42 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
42 # Gantt & calendar
43 # Gantt & calendar
43 map.permission :view_gantt, :projects => :gantt
44 map.permission :view_gantt, :projects => :gantt
44 map.permission :view_calendar, :projects => :calendar
45 map.permission :view_calendar, :projects => :calendar
45 end
46 end
46
47
47 map.project_module :time_tracking do |map|
48 map.project_module :time_tracking do |map|
48 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
49 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
49 map.permission :view_time_entries, :timelog => [:details, :report]
50 map.permission :view_time_entries, :timelog => [:details, :report]
50 end
51 end
51
52
52 map.project_module :news do |map|
53 map.project_module :news do |map|
53 map.permission :manage_news, {:projects => :add_news, :news => [:edit, :destroy, :destroy_comment]}, :require => :member
54 map.permission :manage_news, {:projects => :add_news, :news => [:edit, :destroy, :destroy_comment]}, :require => :member
54 map.permission :view_news, {:projects => :list_news, :news => :show}, :public => true
55 map.permission :view_news, {:projects => :list_news, :news => :show}, :public => true
55 map.permission :comment_news, {:news => :add_comment}, :require => :loggedin
56 map.permission :comment_news, {:news => :add_comment}, :require => :loggedin
56 end
57 end
57
58
58 map.project_module :documents do |map|
59 map.project_module :documents do |map|
59 map.permission :manage_documents, {:projects => :add_document, :documents => [:edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
60 map.permission :manage_documents, {:projects => :add_document, :documents => [:edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
60 map.permission :view_documents, :projects => :list_documents, :documents => [:show, :download]
61 map.permission :view_documents, :projects => :list_documents, :documents => [:show, :download]
61 end
62 end
62
63
63 map.project_module :files do |map|
64 map.project_module :files do |map|
64 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
65 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
65 map.permission :view_files, :projects => :list_files, :versions => :download
66 map.permission :view_files, :projects => :list_files, :versions => :download
66 end
67 end
67
68
68 map.project_module :wiki do |map|
69 map.project_module :wiki do |map|
69 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
70 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
70 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
71 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
71 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
72 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
72 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :special]
73 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :special]
73 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
74 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
74 end
75 end
75
76
76 map.project_module :repository do |map|
77 map.project_module :repository do |map|
77 map.permission :manage_repository, :repositories => [:edit, :destroy]
78 map.permission :manage_repository, :repositories => [:edit, :destroy]
78 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :changes, :diff, :stats, :graph]
79 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :changes, :diff, :stats, :graph]
79 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
80 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
80 end
81 end
81
82
82 map.project_module :boards do |map|
83 map.project_module :boards do |map|
83 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
84 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
84 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
85 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
85 map.permission :add_messages, {:messages => [:new, :reply]}, :require => :loggedin
86 map.permission :add_messages, {:messages => [:new, :reply]}, :require => :loggedin
86 end
87 end
87 end
88 end
88
89
89 # Project menu configuration
90 # Project menu configuration
90 Redmine::MenuManager.map :project_menu do |menu|
91 Redmine::MenuManager.map :project_menu do |menu|
91 menu.push :label_overview, :controller => 'projects', :action => 'show'
92 menu.push :label_overview, :controller => 'projects', :action => 'show'
92 menu.push :label_activity, :controller => 'projects', :action => 'activity'
93 menu.push :label_activity, :controller => 'projects', :action => 'activity'
93 menu.push :label_roadmap, :controller => 'projects', :action => 'roadmap'
94 menu.push :label_roadmap, :controller => 'projects', :action => 'roadmap'
94 menu.push :label_issue_plural, :controller => 'projects', :action => 'list_issues'
95 menu.push :label_issue_plural, :controller => 'projects', :action => 'list_issues'
95 menu.push :label_news_plural, :controller => 'projects', :action => 'list_news'
96 menu.push :label_news_plural, :controller => 'projects', :action => 'list_news'
96 menu.push :label_document_plural, :controller => 'projects', :action => 'list_documents'
97 menu.push :label_document_plural, :controller => 'projects', :action => 'list_documents'
97 menu.push :label_wiki, { :controller => 'wiki', :action => 'index', :page => nil }, :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
98 menu.push :label_wiki, { :controller => 'wiki', :action => 'index', :page => nil }, :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
98 menu.push :label_board_plural, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id, :if => Proc.new { |p| p.boards.any? }
99 menu.push :label_board_plural, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id, :if => Proc.new { |p| p.boards.any? }
99 menu.push :label_attachment_plural, :controller => 'projects', :action => 'list_files'
100 menu.push :label_attachment_plural, :controller => 'projects', :action => 'list_files'
100 menu.push :label_repository, { :controller => 'repositories', :action => 'show' }, :if => Proc.new { |p| p.repository && !p.repository.new_record? }
101 menu.push :label_repository, { :controller => 'repositories', :action => 'show' }, :if => Proc.new { |p| p.repository && !p.repository.new_record? }
101 menu.push :label_settings, :controller => 'projects', :action => 'settings'
102 menu.push :label_settings, :controller => 'projects', :action => 'settings'
102 end
103 end
@@ -1,448 +1,449
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2
2
3 h1, h2, h3 { font-family: "Trebuchet MS", Verdana, sans-serif;}
3 h1, h2, h3 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 h1 {margin:0; padding:0; font-size: 24px;}
4 h1 {margin:0; padding:0; font-size: 24px;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 11px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 11px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8
8
9 /***** Layout *****/
9 /***** Layout *****/
10 #top-menu {background: #22344A;color: #fff;height:1.5em; padding: 2px 6px 0px 6px;}
10 #top-menu {background: #22344A;color: #fff;height:1.5em; padding: 2px 6px 0px 6px;}
11 #top-menu a {color: #fff; padding-right: 4px;}
11 #top-menu a {color: #fff; padding-right: 4px;}
12 #account {float:right;}
12 #account {float:right;}
13
13
14 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 6px 0px 6px;}
14 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 6px 0px 6px;}
15 #header a {color:#f8f8f8;}
15 #header a {color:#f8f8f8;}
16 #quick-search {float:right;}
16 #quick-search {float:right;}
17
17
18 #main-menu {position: absolute; top: 5.5em; left:8px;}
18 #main-menu {position: absolute; top: 5.5em; left:8px;}
19 #main-menu ul {margin: 0; padding: 0;}
19 #main-menu ul {margin: 0; padding: 0;}
20 #main-menu li {
20 #main-menu li {
21 float:left;
21 float:left;
22 list-style-type:none;
22 list-style-type:none;
23 margin: 0px 10px 0px 0px;
23 margin: 0px 10px 0px 0px;
24 padding: 0px 0px 0px 0px;
24 padding: 0px 0px 0px 0px;
25 white-space:nowrap;
25 white-space:nowrap;
26 }
26 }
27 #main-menu li a {
27 #main-menu li a {
28 display: block;
28 display: block;
29 color: #fff;
29 color: #fff;
30 text-decoration: none;
30 text-decoration: none;
31 margin: 0;
31 margin: 0;
32 padding: 4px 4px 4px 4px;
32 padding: 4px 4px 4px 4px;
33 background: #22344A;
33 background: #22344A;
34 }
34 }
35 #main-menu li a:hover {background:#759FCF;}
35 #main-menu li a:hover {background:#759FCF;}
36
36
37 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
37 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
38
38
39 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
39 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
40 * html #sidebar{ width: 17%; }
40 * html #sidebar{ width: 17%; }
41 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
41 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
42 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
42 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
43 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
43 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
44
44
45 #content { width: 80%; background: url(../images/contentbg.png) repeat-x; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; position: relative; z-index: 10; height:600px; min-height: 600px;}
45 #content { width: 80%; background: url(../images/contentbg.png) repeat-x; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; position: relative; z-index: 10; height:600px; min-height: 600px;}
46 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
46 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
47 html>body #content {
47 html>body #content {
48 height: auto;
48 height: auto;
49 min-height: 600px;
49 min-height: 600px;
50 }
50 }
51
51
52 #main.nosidebar #sidebar{ display: none; }
52 #main.nosidebar #sidebar{ display: none; }
53 #main.nosidebar #content{ width: auto; border-right: 0; }
53 #main.nosidebar #content{ width: auto; border-right: 0; }
54
54
55 #footer {clear: both; border-top: 1px solid #bbb; font-size: 11px; padding: 5px; text-align:center; background:#fff;}
55 #footer {clear: both; border-top: 1px solid #bbb; font-size: 11px; padding: 5px; text-align:center; background:#fff;}
56
56
57 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
57 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
58 #login-form table td {padding: 6px;}
58 #login-form table td {padding: 6px;}
59 #login-form label {font-weight: bold;}
59 #login-form label {font-weight: bold;}
60
60
61 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
61 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
62
62
63 /***** Links *****/
63 /***** Links *****/
64 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
64 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
65 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
65 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
66 a img{ border: 0; }
66 a img{ border: 0; }
67
67
68 /***** Tables *****/
68 /***** Tables *****/
69 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
69 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
70 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
70 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
71 table.list tbody th { text-align: left; padding: 0px; }
71 table.list tbody th { text-align: left; padding: 0px; }
72 table.list td { overflow: hidden; text-overflow: ellipsis; vertical-align: top;}
72 table.list td { overflow: hidden; text-overflow: ellipsis; vertical-align: top;}
73 table.list tbody tr:hover { background-color:#ffffdd; }
73 table.list tbody tr:hover { background-color:#ffffdd; }
74 table td {padding:2px;}
74 table td {padding:2px;}
75 table p {margin:0;}
75 table p {margin:0;}
76 .odd {background-color:#f6f7f8;}
76 .odd {background-color:#f6f7f8;}
77 .even {background-color: #fff;}
77 .even {background-color: #fff;}
78
78
79 .highlight { background-color: #FCFD8D;}
79 .highlight { background-color: #FCFD8D;}
80
80
81 .box{
81 .box{
82 padding:6px;
82 padding:6px;
83 margin-bottom: 10px;
83 margin-bottom: 10px;
84 background-color:#f6f6f6;
84 background-color:#f6f6f6;
85 color:#505050;
85 color:#505050;
86 line-height:1.5em;
86 line-height:1.5em;
87 border: 1px solid #e4e4e4;
87 border: 1px solid #e4e4e4;
88 }
88 }
89
89
90 div.square {
90 div.square {
91 border: 1px solid #999;
91 border: 1px solid #999;
92 float: left;
92 float: left;
93 margin: .3em .4em 0 .4em;
93 margin: .3em .4em 0 .4em;
94 overflow: hidden;
94 overflow: hidden;
95 width: .6em; height: .6em;
95 width: .6em; height: .6em;
96 }
96 }
97
97
98 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px;font-size:0.9em;}
98 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px;font-size:0.9em;}
99 .splitcontentleft{float:left; width:49%;}
99 .splitcontentleft{float:left; width:49%;}
100 .splitcontentright{float:right; width:49%;}
100 .splitcontentright{float:right; width:49%;}
101 form {display: inline;}
101 form {display: inline;}
102 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
102 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
103 fieldset {border: 1px solid #e4e4e4; margin:0;}
103 fieldset {border: 1px solid #e4e4e4; margin:0;}
104 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
104 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
105 textarea.wiki-edit { width: 99%; }
105 textarea.wiki-edit { width: 99%; }
106 li p {margin-top: 0;}
106 li p {margin-top: 0;}
107 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
107 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
108 .autoscroll {overflow-x: auto; padding:1px; width:100%;}
108 .autoscroll {overflow-x: auto; padding:1px; width:100%;}
109
109
110 /***** Tabular forms ******/
110 /***** Tabular forms ******/
111 .tabular p{
111 .tabular p{
112 margin: 0;
112 margin: 0;
113 padding: 5px 0 8px 0;
113 padding: 5px 0 8px 0;
114 padding-left: 180px; /*width of left column containing the label elements*/
114 padding-left: 180px; /*width of left column containing the label elements*/
115 height: 1%;
115 height: 1%;
116 clear:left;
116 clear:left;
117 }
117 }
118
118
119 .tabular label{
119 .tabular label{
120 font-weight: bold;
120 font-weight: bold;
121 float: left;
121 float: left;
122 text-align: right;
122 text-align: right;
123 margin-left: -180px; /*width of left column*/
123 margin-left: -180px; /*width of left column*/
124 width: 175px; /*width of labels. Should be smaller than left column to create some right
124 width: 175px; /*width of labels. Should be smaller than left column to create some right
125 margin*/
125 margin*/
126 }
126 }
127
127
128 #settings .tabular p{ padding-left: 300px; }
128 #settings .tabular p{ padding-left: 300px; }
129 #settings .tabular label{ margin-left: -300px; width: 295px; }
129 #settings .tabular label{ margin-left: -300px; width: 295px; }
130
130
131 .required {color: #bb0000;}
131 .required {color: #bb0000;}
132 .summary {font-style: italic;}
132 .summary {font-style: italic;}
133
133
134 /***** Flash & error messages ****/
134 /***** Flash & error messages ****/
135 #flash div, #errorExplanation, .nodata {
135 #flash div, #errorExplanation, .nodata {
136 padding: 4px 4px 4px 30px;
136 padding: 4px 4px 4px 30px;
137 margin-bottom: 12px;
137 margin-bottom: 12px;
138 font-size: 1.1em;
138 font-size: 1.1em;
139 border: 2px solid;
139 border: 2px solid;
140 }
140 }
141
141
142 #flash div {margin-top: 6px;}
142 #flash div {margin-top: 6px;}
143
143
144 #flash div.error, #errorExplanation {
144 #flash div.error, #errorExplanation {
145 background: url(../images/false.png) 8px 5px no-repeat;
145 background: url(../images/false.png) 8px 5px no-repeat;
146 background-color: #ffe3e3;
146 background-color: #ffe3e3;
147 border-color: #dd0000;
147 border-color: #dd0000;
148 color: #550000;
148 color: #550000;
149 }
149 }
150
150
151 #flash div.notice {
151 #flash div.notice {
152 background: url(../images/true.png) 8px 5px no-repeat;
152 background: url(../images/true.png) 8px 5px no-repeat;
153 background-color: #dfffdf;
153 background-color: #dfffdf;
154 border-color: #9fcf9f;
154 border-color: #9fcf9f;
155 color: #005f00;
155 color: #005f00;
156 }
156 }
157
157
158 .nodata {
158 .nodata {
159 text-align: center;
159 text-align: center;
160 background-color: #FFEBC1;
160 background-color: #FFEBC1;
161 border-color: #FDBF3B;
161 border-color: #FDBF3B;
162 color: #A6750C;
162 color: #A6750C;
163 }
163 }
164
164
165 #errorExplanation ul { font-size: 0.9em;}
165 #errorExplanation ul { font-size: 0.9em;}
166
166
167 /***** Ajax indicator ******/
167 /***** Ajax indicator ******/
168 #ajax-indicator {
168 #ajax-indicator {
169 position: absolute; /* fixed not supported by IE */
169 position: absolute; /* fixed not supported by IE */
170 background-color:#eee;
170 background-color:#eee;
171 border: 1px solid #bbb;
171 border: 1px solid #bbb;
172 top:35%;
172 top:35%;
173 left:40%;
173 left:40%;
174 width:20%;
174 width:20%;
175 font-weight:bold;
175 font-weight:bold;
176 text-align:center;
176 text-align:center;
177 padding:0.6em;
177 padding:0.6em;
178 z-index:100;
178 z-index:100;
179 filter:alpha(opacity=50);
179 filter:alpha(opacity=50);
180 -moz-opacity:0.5;
180 -moz-opacity:0.5;
181 opacity: 0.5;
181 opacity: 0.5;
182 -khtml-opacity: 0.5;
182 -khtml-opacity: 0.5;
183 }
183 }
184
184
185 html>body #ajax-indicator { position: fixed; }
185 html>body #ajax-indicator { position: fixed; }
186
186
187 #ajax-indicator span {
187 #ajax-indicator span {
188 background-position: 0% 40%;
188 background-position: 0% 40%;
189 background-repeat: no-repeat;
189 background-repeat: no-repeat;
190 background-image: url(../images/loading.gif);
190 background-image: url(../images/loading.gif);
191 padding-left: 26px;
191 padding-left: 26px;
192 vertical-align: bottom;
192 vertical-align: bottom;
193 }
193 }
194
194
195 /***** Calendar *****/
195 /***** Calendar *****/
196 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
196 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
197 table.cal th { background-color:#EEEEEE; padding: 4px; }
197 table.cal th { background-color:#EEEEEE; padding: 4px; }
198 table.cal td {border: 1px solid #d7d7d7;}
198 table.cal td {border: 1px solid #d7d7d7;}
199 table.cal td.today {background:#ffffdd;}
199 table.cal td.today {background:#ffffdd;}
200
200
201 /***** Tooltips ******/
201 /***** Tooltips ******/
202 .tooltip{position:relative;z-index:24;}
202 .tooltip{position:relative;z-index:24;}
203 .tooltip:hover{z-index:25;color:#000;}
203 .tooltip:hover{z-index:25;color:#000;}
204 .tooltip span.tip{display: none; text-align:left;}
204 .tooltip span.tip{display: none; text-align:left;}
205
205
206 div.tooltip:hover span.tip{
206 div.tooltip:hover span.tip{
207 display:block;
207 display:block;
208 position:absolute;
208 position:absolute;
209 top:12px; left:24px; width:270px;
209 top:12px; left:24px; width:270px;
210 border:1px solid #555;
210 border:1px solid #555;
211 background-color:#fff;
211 background-color:#fff;
212 padding: 4px;
212 padding: 4px;
213 font-size: 0.8em;
213 font-size: 0.8em;
214 color:#505050;
214 color:#505050;
215 }
215 }
216
216
217 /***** Progress bar *****/
217 /***** Progress bar *****/
218 .progress {
218 .progress {
219 border: 1px solid #D7D7D7;
219 border: 1px solid #D7D7D7;
220 border-collapse: collapse;
220 border-collapse: collapse;
221 border-spacing: 0pt;
221 border-spacing: 0pt;
222 empty-cells: show;
222 empty-cells: show;
223 padding: 3px;
223 padding: 3px;
224 width: 40em;
224 width: 40em;
225 text-align: center;
225 text-align: center;
226 }
226 }
227
227
228 .progress td { height: 1em; }
228 .progress td { height: 1em; }
229 .progress .closed { background: #BAE0BA none repeat scroll 0%; }
229 .progress .closed { background: #BAE0BA none repeat scroll 0%; }
230 .progress .open { background: #FFF none repeat scroll 0%; }
230 .progress .open { background: #FFF none repeat scroll 0%; }
231
231
232 /***** Tabs *****/
232 /***** Tabs *****/
233 #content .tabs{height: 2.6em;}
233 #content .tabs{height: 2.6em;}
234 #content .tabs ul{margin:0;}
234 #content .tabs ul{margin:0;}
235 #content .tabs ul li{
235 #content .tabs ul li{
236 float:left;
236 float:left;
237 list-style-type:none;
237 list-style-type:none;
238 white-space:nowrap;
238 white-space:nowrap;
239 margin-right:8px;
239 margin-right:8px;
240 background:#fff;
240 background:#fff;
241 }
241 }
242 #content .tabs ul li a{
242 #content .tabs ul li a{
243 display:block;
243 display:block;
244 font-size: 0.9em;
244 font-size: 0.9em;
245 text-decoration:none;
245 text-decoration:none;
246 line-height:1em;
246 line-height:1em;
247 padding:4px;
247 padding:4px;
248 border: 1px solid #c0c0c0;
248 border: 1px solid #c0c0c0;
249 }
249 }
250
250
251 #content .tabs ul li a.selected, #content .tabs ul li a:hover{
251 #content .tabs ul li a.selected, #content .tabs ul li a:hover{
252 background-color: #507AAA;
252 background-color: #507AAA;
253 border: 1px solid #507AAA;
253 border: 1px solid #507AAA;
254 color: #fff;
254 color: #fff;
255 text-decoration:none;
255 text-decoration:none;
256 }
256 }
257
257
258 /***** Diff *****/
258 /***** Diff *****/
259 .diff_out { background: #fcc; }
259 .diff_out { background: #fcc; }
260 .diff_in { background: #cfc; }
260 .diff_in { background: #cfc; }
261
261
262 /***** Wiki *****/
262 /***** Wiki *****/
263 div.wiki table {
263 div.wiki table {
264 border: 1px solid #505050;
264 border: 1px solid #505050;
265 border-collapse: collapse;
265 border-collapse: collapse;
266 }
266 }
267
267
268 div.wiki table, div.wiki td, div.wiki th {
268 div.wiki table, div.wiki td, div.wiki th {
269 border: 1px solid #bbb;
269 border: 1px solid #bbb;
270 padding: 4px;
270 padding: 4px;
271 }
271 }
272
272
273 div.wiki a {
273 div.wiki a {
274 background-position: 0% 60%;
274 background-position: 0% 60%;
275 background-repeat: no-repeat;
275 background-repeat: no-repeat;
276 padding-left: 12px;
276 padding-left: 12px;
277 background-image: url(../images/external.png);
277 background-image: url(../images/external.png);
278 }
278 }
279
279
280 div.wiki a.wiki-page, div.wiki a.issue, div.wiki a.changeset, div.wiki a.email, div.wiki div.toc a {
280 div.wiki a.wiki-page, div.wiki a.issue, div.wiki a.changeset, div.wiki a.email, div.wiki div.toc a {
281 padding-left: 0;
281 padding-left: 0;
282 background-image: none;
282 background-image: none;
283 }
283 }
284
284
285 div.wiki a.new {
285 div.wiki a.new {
286 color: #b73535;
286 color: #b73535;
287 }
287 }
288
288
289 div.wiki pre {
289 div.wiki pre {
290 margin: 1em 1em 1em 1.6em;
290 margin: 1em 1em 1em 1.6em;
291 padding: 2px;
291 padding: 2px;
292 background-color: #fafafa;
292 background-color: #fafafa;
293 border: 1px solid #dadada;
293 border: 1px solid #dadada;
294 width:95%;
294 width:95%;
295 overflow-x: auto;
295 overflow-x: auto;
296 }
296 }
297
297
298 div.wiki div.toc {
298 div.wiki div.toc {
299 background-color: #ffffdd;
299 background-color: #ffffdd;
300 border: 1px solid #e4e4e4;
300 border: 1px solid #e4e4e4;
301 padding: 4px;
301 padding: 4px;
302 line-height: 1.2em;
302 line-height: 1.2em;
303 margin-bottom: 12px;
303 margin-bottom: 12px;
304 margin-right: 12px;
304 margin-right: 12px;
305 float: left;
305 float: left;
306 }
306 }
307
307
308 div.wiki div.toc.right {
308 div.wiki div.toc.right {
309 float: right;
309 float: right;
310 margin-left: 12px;
310 margin-left: 12px;
311 margin-right: 0;
311 margin-right: 0;
312 }
312 }
313
313
314 div.wiki div.toc a {
314 div.wiki div.toc a {
315 display: block;
315 display: block;
316 font-size: 0.9em;
316 font-size: 0.9em;
317 font-weight: normal;
317 font-weight: normal;
318 text-decoration: none;
318 text-decoration: none;
319 color: #606060;
319 color: #606060;
320 }
320 }
321 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
321 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
322
322
323 div.wiki div.toc a.heading2 { margin-left: 6px; }
323 div.wiki div.toc a.heading2 { margin-left: 6px; }
324 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
324 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
325
325
326 /***** My page layout *****/
326 /***** My page layout *****/
327 .block-receiver {
327 .block-receiver {
328 border:1px dashed #c0c0c0;
328 border:1px dashed #c0c0c0;
329 margin-bottom: 20px;
329 margin-bottom: 20px;
330 padding: 15px 0 15px 0;
330 padding: 15px 0 15px 0;
331 }
331 }
332
332
333 .mypage-box {
333 .mypage-box {
334 margin:0 0 20px 0;
334 margin:0 0 20px 0;
335 color:#505050;
335 color:#505050;
336 line-height:1.5em;
336 line-height:1.5em;
337 }
337 }
338
338
339 .handle {
339 .handle {
340 cursor: move;
340 cursor: move;
341 }
341 }
342
342
343 a.close-icon {
343 a.close-icon {
344 display:block;
344 display:block;
345 margin-top:3px;
345 margin-top:3px;
346 overflow:hidden;
346 overflow:hidden;
347 width:12px;
347 width:12px;
348 height:12px;
348 height:12px;
349 background-repeat: no-repeat;
349 background-repeat: no-repeat;
350 cursor:pointer;
350 cursor:pointer;
351 background-image:url('../images/close.png');
351 background-image:url('../images/close.png');
352 }
352 }
353
353
354 a.close-icon:hover {
354 a.close-icon:hover {
355 background-image:url('../images/close_hl.png');
355 background-image:url('../images/close_hl.png');
356 }
356 }
357
357
358 /***** Gantt chart *****/
358 /***** Gantt chart *****/
359 .gantt_hdr {
359 .gantt_hdr {
360 position:absolute;
360 position:absolute;
361 top:0;
361 top:0;
362 height:16px;
362 height:16px;
363 border-top: 1px solid #c0c0c0;
363 border-top: 1px solid #c0c0c0;
364 border-bottom: 1px solid #c0c0c0;
364 border-bottom: 1px solid #c0c0c0;
365 border-right: 1px solid #c0c0c0;
365 border-right: 1px solid #c0c0c0;
366 text-align: center;
366 text-align: center;
367 overflow: hidden;
367 overflow: hidden;
368 }
368 }
369
369
370 .task {
370 .task {
371 position: absolute;
371 position: absolute;
372 height:8px;
372 height:8px;
373 font-size:0.8em;
373 font-size:0.8em;
374 color:#888;
374 color:#888;
375 padding:0;
375 padding:0;
376 margin:0;
376 margin:0;
377 line-height:0.8em;
377 line-height:0.8em;
378 }
378 }
379
379
380 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
380 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
381 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
381 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
382 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
382 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
383 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
383 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
384
384
385 /***** Icons *****/
385 /***** Icons *****/
386 .icon {
386 .icon {
387 background-position: 0% 40%;
387 background-position: 0% 40%;
388 background-repeat: no-repeat;
388 background-repeat: no-repeat;
389 padding-left: 20px;
389 padding-left: 20px;
390 padding-top: 2px;
390 padding-top: 2px;
391 padding-bottom: 3px;
391 padding-bottom: 3px;
392 }
392 }
393
393
394 .icon22 {
394 .icon22 {
395 background-position: 0% 40%;
395 background-position: 0% 40%;
396 background-repeat: no-repeat;
396 background-repeat: no-repeat;
397 padding-left: 26px;
397 padding-left: 26px;
398 line-height: 22px;
398 line-height: 22px;
399 vertical-align: middle;
399 vertical-align: middle;
400 }
400 }
401
401
402 .icon-add { background-image: url(../images/add.png); }
402 .icon-add { background-image: url(../images/add.png); }
403 .icon-edit { background-image: url(../images/edit.png); }
403 .icon-edit { background-image: url(../images/edit.png); }
404 .icon-del { background-image: url(../images/delete.png); }
404 .icon-del { background-image: url(../images/delete.png); }
405 .icon-move { background-image: url(../images/move.png); }
405 .icon-move { background-image: url(../images/move.png); }
406 .icon-save { background-image: url(../images/save.png); }
406 .icon-save { background-image: url(../images/save.png); }
407 .icon-cancel { background-image: url(../images/cancel.png); }
407 .icon-cancel { background-image: url(../images/cancel.png); }
408 .icon-pdf { background-image: url(../images/pdf.png); }
408 .icon-pdf { background-image: url(../images/pdf.png); }
409 .icon-csv { background-image: url(../images/csv.png); }
409 .icon-csv { background-image: url(../images/csv.png); }
410 .icon-html { background-image: url(../images/html.png); }
410 .icon-html { background-image: url(../images/html.png); }
411 .icon-image { background-image: url(../images/image.png); }
411 .icon-image { background-image: url(../images/image.png); }
412 .icon-txt { background-image: url(../images/txt.png); }
412 .icon-txt { background-image: url(../images/txt.png); }
413 .icon-file { background-image: url(../images/file.png); }
413 .icon-file { background-image: url(../images/file.png); }
414 .icon-folder { background-image: url(../images/folder.png); }
414 .icon-folder { background-image: url(../images/folder.png); }
415 .icon-package { background-image: url(../images/package.png); }
415 .icon-package { background-image: url(../images/package.png); }
416 .icon-home { background-image: url(../images/home.png); }
416 .icon-home { background-image: url(../images/home.png); }
417 .icon-user { background-image: url(../images/user.png); }
417 .icon-user { background-image: url(../images/user.png); }
418 .icon-mypage { background-image: url(../images/user_page.png); }
418 .icon-mypage { background-image: url(../images/user_page.png); }
419 .icon-admin { background-image: url(../images/admin.png); }
419 .icon-admin { background-image: url(../images/admin.png); }
420 .icon-projects { background-image: url(../images/projects.png); }
420 .icon-projects { background-image: url(../images/projects.png); }
421 .icon-logout { background-image: url(../images/logout.png); }
421 .icon-logout { background-image: url(../images/logout.png); }
422 .icon-help { background-image: url(../images/help.png); }
422 .icon-help { background-image: url(../images/help.png); }
423 .icon-attachment { background-image: url(../images/attachment.png); }
423 .icon-attachment { background-image: url(../images/attachment.png); }
424 .icon-index { background-image: url(../images/index.png); }
424 .icon-index { background-image: url(../images/index.png); }
425 .icon-history { background-image: url(../images/history.png); }
425 .icon-history { background-image: url(../images/history.png); }
426 .icon-feed { background-image: url(../images/feed.png); }
426 .icon-feed { background-image: url(../images/feed.png); }
427 .icon-time { background-image: url(../images/time.png); }
427 .icon-time { background-image: url(../images/time.png); }
428 .icon-stats { background-image: url(../images/stats.png); }
428 .icon-stats { background-image: url(../images/stats.png); }
429 .icon-warning { background-image: url(../images/warning.png); }
429 .icon-warning { background-image: url(../images/warning.png); }
430 .icon-fav { background-image: url(../images/fav.png); }
430 .icon-fav { background-image: url(../images/fav.png); }
431 .icon-fav-off { background-image: url(../images/fav_off.png); }
431 .icon-fav-off { background-image: url(../images/fav_off.png); }
432 .icon-reload { background-image: url(../images/reload.png); }
432 .icon-reload { background-image: url(../images/reload.png); }
433 .icon-lock { background-image: url(../images/locked.png); }
433 .icon-lock { background-image: url(../images/locked.png); }
434 .icon-unlock { background-image: url(../images/unlock.png); }
434 .icon-unlock { background-image: url(../images/unlock.png); }
435 .icon-note { background-image: url(../images/note.png); }
435 .icon-note { background-image: url(../images/note.png); }
436
436
437 .icon22-projects { background-image: url(../images/22x22/projects.png); }
437 .icon22-projects { background-image: url(../images/22x22/projects.png); }
438 .icon22-users { background-image: url(../images/22x22/users.png); }
438 .icon22-users { background-image: url(../images/22x22/users.png); }
439 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
439 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
440 .icon22-role { background-image: url(../images/22x22/role.png); }
440 .icon22-role { background-image: url(../images/22x22/role.png); }
441 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
441 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
442 .icon22-options { background-image: url(../images/22x22/options.png); }
442 .icon22-options { background-image: url(../images/22x22/options.png); }
443 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
443 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
444 .icon22-authent { background-image: url(../images/22x22/authent.png); }
444 .icon22-authent { background-image: url(../images/22x22/authent.png); }
445 .icon22-info { background-image: url(../images/22x22/info.png); }
445 .icon22-info { background-image: url(../images/22x22/info.png); }
446 .icon22-comment { background-image: url(../images/22x22/comment.png); }
446 .icon22-comment { background-image: url(../images/22x22/comment.png); }
447 .icon22-package { background-image: url(../images/22x22/package.png); }
447 .icon22-package { background-image: url(../images/22x22/package.png); }
448 .icon22-settings { background-image: url(../images/22x22/settings.png); }
448 .icon22-settings { background-image: url(../images/22x22/settings.png); }
449 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
@@ -1,134 +1,134
1 # Copyright (c) 2005-2006 David Barri
1 # Copyright (c) 2005-2006 David Barri
2
2
3 require 'iconv'
3 require 'iconv'
4 require 'gloc-version'
4 require 'gloc-version'
5
5
6 module GLoc
6 module GLoc
7 class GLocError < StandardError #:nodoc:
7 class GLocError < StandardError #:nodoc:
8 end
8 end
9 class InvalidArgumentsError < GLocError #:nodoc:
9 class InvalidArgumentsError < GLocError #:nodoc:
10 end
10 end
11 class InvalidKeyError < GLocError #:nodoc:
11 class InvalidKeyError < GLocError #:nodoc:
12 end
12 end
13 class RuleNotFoundError < GLocError #:nodoc:
13 class RuleNotFoundError < GLocError #:nodoc:
14 end
14 end
15 class StringNotFoundError < GLocError #:nodoc:
15 class StringNotFoundError < GLocError #:nodoc:
16 end
16 end
17
17
18 class << self
18 class << self
19 private
19 private
20
20
21 def _add_localized_data(lang, symbol_hash, override, target) #:nodoc:
21 def _add_localized_data(lang, symbol_hash, override, target) #:nodoc:
22 lang= lang.to_sym
22 lang= lang.to_sym
23 if override
23 if override
24 target[lang] ||= {}
24 target[lang] ||= {}
25 target[lang].merge!(symbol_hash)
25 target[lang].merge!(symbol_hash)
26 else
26 else
27 symbol_hash.merge!(target[lang]) if target[lang]
27 symbol_hash.merge!(target[lang]) if target[lang]
28 target[lang]= symbol_hash
28 target[lang]= symbol_hash
29 end
29 end
30 end
30 end
31
31
32 def _add_localized_strings(lang, symbol_hash, override=true, strings_charset=nil) #:nodoc:
32 def _add_localized_strings(lang, symbol_hash, override=true, strings_charset=nil) #:nodoc:
33 _charset_required
33 _charset_required
34
34
35 # Convert all incoming strings to the gloc charset
35 # Convert all incoming strings to the gloc charset
36 if strings_charset
36 if strings_charset
37 Iconv.open(get_charset(lang), strings_charset) do |i|
37 Iconv.open(get_charset(lang), strings_charset) do |i|
38 symbol_hash.each_pair {|k,v| symbol_hash[k]= i.iconv(v)}
38 symbol_hash.each_pair {|k,v| symbol_hash[k]= i.iconv(v)}
39 end
39 end
40 end
40 end
41
41
42 # Convert rules
42 # Convert rules
43 rules= {}
43 rules= {}
44 old_kcode= $KCODE
44 old_kcode= $KCODE
45 begin
45 begin
46 $KCODE= 'u'
46 $KCODE= 'u'
47 Iconv.open(UTF_8, get_charset(lang)) do |i|
47 Iconv.open(UTF_8, get_charset(lang)) do |i|
48 symbol_hash.each {|k,v|
48 symbol_hash.each {|k,v|
49 if /^_gloc_rule_(.+)$/ =~ k.to_s
49 if /^_gloc_rule_(.+)$/ =~ k.to_s
50 v= i.iconv(v) if v
50 v= i.iconv(v) if v
51 v= '""' if v.nil?
51 v= '""' if v.nil?
52 rules[$1.to_sym]= eval "Proc.new do #{v} end"
52 rules[$1.to_sym]= eval "Proc.new do #{v} end"
53 end
53 end
54 }
54 }
55 end
55 end
56 ensure
56 ensure
57 $KCODE= old_kcode
57 $KCODE= old_kcode
58 end
58 end
59 rules.keys.each {|k| symbol_hash.delete "_gloc_rule_#{k}".to_sym}
59 rules.keys.each {|k| symbol_hash.delete "_gloc_rule_#{k}".to_sym}
60
60
61 # Add new localized data
61 # Add new localized data
62 LOWERCASE_LANGUAGES[lang.to_s.downcase]= lang
62 LOWERCASE_LANGUAGES[lang.to_s.downcase]= lang
63 _add_localized_data(lang, symbol_hash, override, LOCALIZED_STRINGS)
63 _add_localized_data(lang, symbol_hash, override, LOCALIZED_STRINGS)
64 _add_localized_data(lang, rules, override, RULES)
64 _add_localized_data(lang, rules, override, RULES)
65 end
65 end
66
66
67 def _charset_required #:nodoc:
67 def _charset_required #:nodoc:
68 set_charset UTF_8 unless CONFIG[:internal_charset]
68 set_charset UTF_8 unless CONFIG[:internal_charset]
69 end
69 end
70
70
71 def _get_internal_state_vars
71 def _get_internal_state_vars
72 [ CONFIG, LOCALIZED_STRINGS, RULES, LOWERCASE_LANGUAGES ]
72 [ CONFIG, LOCALIZED_STRINGS, RULES, LOWERCASE_LANGUAGES ]
73 end
73 end
74
74
75 def _get_lang_file_list(dir) #:nodoc:
75 def _get_lang_file_list(dir) #:nodoc:
76 dir= File.join(RAILS_ROOT,'lang') if dir.nil?
76 dir= File.join(RAILS_ROOT,'{.,vendor/plugins/*}','lang') if dir.nil?
77 Dir[File.join(dir,'*.{yaml,yml}')]
77 Dir[File.join(dir,'*.{yaml,yml}')]
78 end
78 end
79
79
80 def _l(symbol, language, *arguments) #:nodoc:
80 def _l(symbol, language, *arguments) #:nodoc:
81 symbol= symbol.to_sym if symbol.is_a?(String)
81 symbol= symbol.to_sym if symbol.is_a?(String)
82 raise InvalidKeyError.new("Symbol or String expected as key.") unless symbol.kind_of?(Symbol)
82 raise InvalidKeyError.new("Symbol or String expected as key.") unless symbol.kind_of?(Symbol)
83
83
84 translation= LOCALIZED_STRINGS[language][symbol] rescue nil
84 translation= LOCALIZED_STRINGS[language][symbol] rescue nil
85 if translation.nil?
85 if translation.nil?
86 raise StringNotFoundError.new("There is no key called '#{symbol}' in the #{language} strings.") if CONFIG[:raise_string_not_found_errors]
86 raise StringNotFoundError.new("There is no key called '#{symbol}' in the #{language} strings.") if CONFIG[:raise_string_not_found_errors]
87 translation= symbol.to_s
87 translation= symbol.to_s
88 end
88 end
89
89
90 begin
90 begin
91 return translation % arguments
91 return translation % arguments
92 rescue => e
92 rescue => e
93 raise InvalidArgumentsError.new("Translation value #{translation.inspect} with arguments #{arguments.inspect} caused error '#{e.message}'")
93 raise InvalidArgumentsError.new("Translation value #{translation.inspect} with arguments #{arguments.inspect} caused error '#{e.message}'")
94 end
94 end
95 end
95 end
96
96
97 def _l_has_string?(symbol,lang) #:nodoc:
97 def _l_has_string?(symbol,lang) #:nodoc:
98 symbol= symbol.to_sym if symbol.is_a?(String)
98 symbol= symbol.to_sym if symbol.is_a?(String)
99 LOCALIZED_STRINGS[lang].has_key?(symbol.to_sym) rescue false
99 LOCALIZED_STRINGS[lang].has_key?(symbol.to_sym) rescue false
100 end
100 end
101
101
102 def _l_rule(symbol,lang) #:nodoc:
102 def _l_rule(symbol,lang) #:nodoc:
103 symbol= symbol.to_sym if symbol.is_a?(String)
103 symbol= symbol.to_sym if symbol.is_a?(String)
104 raise InvalidKeyError.new("Symbol or String expected as key.") unless symbol.kind_of?(Symbol)
104 raise InvalidKeyError.new("Symbol or String expected as key.") unless symbol.kind_of?(Symbol)
105
105
106 r= RULES[lang][symbol] rescue nil
106 r= RULES[lang][symbol] rescue nil
107 raise RuleNotFoundError.new("There is no rule called '#{symbol}' in the #{lang} rules.") if r.nil?
107 raise RuleNotFoundError.new("There is no rule called '#{symbol}' in the #{lang} rules.") if r.nil?
108 r
108 r
109 end
109 end
110
110
111 def _verbose_msg(type=nil)
111 def _verbose_msg(type=nil)
112 return unless CONFIG[:verbose]
112 return unless CONFIG[:verbose]
113 x= case type
113 x= case type
114 when :stats
114 when :stats
115 x= valid_languages.map{|l| ":#{l}(#{LOCALIZED_STRINGS[l].size}/#{RULES[l].size})"}.sort.join(', ')
115 x= valid_languages.map{|l| ":#{l}(#{LOCALIZED_STRINGS[l].size}/#{RULES[l].size})"}.sort.join(', ')
116 "Current stats -- #{x}"
116 "Current stats -- #{x}"
117 else
117 else
118 yield
118 yield
119 end
119 end
120 puts "[GLoc] #{x}"
120 puts "[GLoc] #{x}"
121 end
121 end
122
122
123 public :_l, :_l_has_string?, :_l_rule
123 public :_l, :_l_has_string?, :_l_rule
124 end
124 end
125
125
126 private
126 private
127
127
128 unless const_defined?(:LOCALIZED_STRINGS)
128 unless const_defined?(:LOCALIZED_STRINGS)
129 LOCALIZED_STRINGS= {}
129 LOCALIZED_STRINGS= {}
130 RULES= {}
130 RULES= {}
131 LOWERCASE_LANGUAGES= {}
131 LOWERCASE_LANGUAGES= {}
132 end
132 end
133
133
134 end
134 end
General Comments 0
You need to be logged in to leave comments. Login now