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