##// END OF EJS Templates
Initial commit...
Jean-Philippe Lang -
r2:6b7650e2f031
parent child
Show More
@@ -0,0 +1,10
1 # Add your own tasks in files placed in lib/tasks ending in .rake,
2 # for example lib/tasks/switchtower.rake, and they will automatically be available to Rake.
3
4 require(File.join(File.dirname(__FILE__), 'config', 'boot'))
5
6 require 'rake'
7 require 'rake/testtask'
8 require 'rake/rdoctask'
9
10 require 'tasks/rails' No newline at end of file
@@ -0,0 +1,83
1 # redMine - project management software
2 # Copyright (C) 2006 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 class AccountController < ApplicationController
19 layout 'base'
20 # prevents login action to be filtered by check_if_login_required application scope filter
21 skip_before_filter :check_if_login_required, :only => :login
22 before_filter :require_login, :except => [:show, :login]
23
24 def show
25 @user = User.find(params[:id])
26 end
27
28 # Login request and validation
29 def login
30 if request.get?
31 session[:user] = nil
32 @user = User.new
33 else
34 @user = User.new(params[:user])
35 logged_in_user = @user.try_to_login
36 if logged_in_user
37 session[:user] = logged_in_user
38 redirect_back_or_default :controller => 'account', :action => 'my_page'
39 else
40 flash[:notice] = _('Invalid user/password')
41 end
42 end
43 end
44
45 # Log out current user and redirect to welcome page
46 def logout
47 session[:user] = nil
48 redirect_to(:controller => '')
49 end
50
51 def my_page
52 @user = session[:user]
53 @reported_issues = Issue.find(:all, :conditions => ["author_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')
54 @assigned_issues = Issue.find(:all, :conditions => ["assigned_to_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')
55 end
56
57 # Edit current user's account
58 def my_account
59 @user = User.find(session[:user].id)
60 if request.post? and @user.update_attributes(@params[:user])
61 flash[:notice] = 'Account was successfully updated.'
62 session[:user] = @user
63 set_localization
64 end
65 end
66
67 # Change current user's password
68 def change_password
69 @user = User.find(session[:user].id)
70 if @user.check_password?(@params[:old_password])
71 if @params[:new_password] == @params[:new_password_confirmation]
72 if @user.change_password(@params[:old_password], @params[:new_password])
73 flash[:notice] = 'Password was successfully updated.'
74 end
75 else
76 flash[:notice] = 'Password confirmation doesn\'t match!'
77 end
78 else
79 flash[:notice] = 'Wrong password'
80 end
81 render :action => 'my_account'
82 end
83 end
@@ -0,0 +1,49
1 # redMine - project management software
2 # Copyright (C) 2006 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 class AdminController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
21
22 helper :sort
23 include SortHelper
24
25 def index
26 end
27
28 def projects
29 sort_init 'projects.name', 'asc'
30 sort_update
31 @project_pages, @projects = paginate :projects, :per_page => 15, :order => sort_clause
32 end
33
34 def mail_options
35 @actions = Permission.find(:all, :conditions => ["mail_option=?", true]) || []
36 if request.post?
37 @actions.each { |a|
38 a.mail_enabled = params[:action_ids].include? a.id.to_s
39 a.save
40 }
41 flash[:notice] = "Mail options were successfully updated."
42 end
43 end
44
45 def info
46 @adapter_name = ActiveRecord::Base.connection.adapter_name
47 end
48
49 end
@@ -0,0 +1,86
1 # redMine - project management software
2 # Copyright (C) 2006 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 class ApplicationController < ActionController::Base
19 before_filter :check_if_login_required, :set_localization
20
21 # check if login is globally required to access the application
22 def check_if_login_required
23 require_login if RDM_LOGIN_REQUIRED
24 end
25
26 def set_localization
27 Localization.lang = session[:user].nil? ? RDM_DEFAULT_LANG : (session[:user].language || RDM_DEFAULT_LANG)
28 end
29
30 def require_login
31 unless session[:user]
32 store_location
33 redirect_to(:controller => "account", :action => "login")
34 end
35 end
36
37 def require_admin
38 if session[:user].nil?
39 store_location
40 redirect_to(:controller => "account", :action => "login")
41 else
42 unless session[:user].admin?
43 flash[:notice] = "Acces not allowed"
44 redirect_to(:controller => "projects", :action => "list")
45 end
46 end
47 end
48
49 # authorizes the user for the requested action.
50 def authorize
51 # check if action is allowed on public projects
52 if @project.public? and Permission.allowed_to_public "%s/%s" % [ @params[:controller], @params[:action] ]
53 return true
54 end
55 # if user is not logged in, he is redirect to login form
56 unless session[:user]
57 store_location
58 redirect_to(:controller => "account", :action => "login")
59 return false
60 end
61 # check if user is authorized
62 if session[:user].admin? or Permission.allowed_to_role( "%s/%s" % [ @params[:controller], @params[:action] ], session[:user].role_for_project(@project.id) )
63 return true
64 end
65 flash[:notice] = "Acces denied"
66 redirect_to(:controller => "")
67 return false
68 end
69
70 # store current uri in the session.
71 # we can return to this location by calling redirect_back_or_default
72 def store_location
73 session[:return_to] = @request.request_uri
74 end
75
76 # move to the last store_location call or to the passed default one
77 def redirect_back_or_default(default)
78 if session[:return_to].nil?
79 redirect_to default
80 else
81 redirect_to_url session[:return_to]
82 session[:return_to] = nil
83 end
84 end
85
86 end No newline at end of file
@@ -0,0 +1,58
1 # redMine - project management software
2 # Copyright (C) 2006 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 class CustomFieldsController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
21
22 def index
23 list
24 render :action => 'list'
25 end
26
27 def list
28 @custom_field_pages, @custom_fields = paginate :custom_fields, :per_page => 10
29 end
30
31 def new
32 if request.get?
33 @custom_field = CustomField.new
34 else
35 @custom_field = CustomField.new(params[:custom_field])
36 if @custom_field.save
37 flash[:notice] = 'CustomField was successfully created.'
38 redirect_to :action => 'list'
39 end
40 end
41 end
42
43 def edit
44 @custom_field = CustomField.find(params[:id])
45 if request.post? and @custom_field.update_attributes(params[:custom_field])
46 flash[:notice] = 'CustomField was successfully updated.'
47 redirect_to :action => 'list'
48 end
49 end
50
51 def destroy
52 CustomField.find(params[:id]).destroy
53 redirect_to :action => 'list'
54 rescue
55 flash[:notice] = "Unable to delete custom field"
56 redirect_to :action => 'list'
57 end
58 end
@@ -0,0 +1,65
1 # redMine - project management software
2 # Copyright (C) 2006 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 class DocumentsController < ApplicationController
19 layout 'base'
20 before_filter :find_project, :authorize
21
22 def show
23 end
24
25 def edit
26 @categories = Enumeration::get_values('DCAT')
27 if request.post? and @document.update_attributes(params[:document])
28 flash[:notice] = 'Document was successfully updated.'
29 redirect_to :action => 'show', :id => @document
30 end
31 end
32
33 def destroy
34 @document.destroy
35 redirect_to :controller => 'projects', :action => 'list_documents', :id => @project
36 end
37
38 def download
39 @attachment = @document.attachments.find(params[:attachment_id])
40 @attachment.increment_download
41 send_file @attachment.diskfile, :filename => @attachment.filename
42 end
43
44 def add_attachment
45 # Save the attachment
46 if params[:attachment][:file].size > 0
47 @attachment = @document.attachments.build(params[:attachment])
48 @attachment.author_id = session[:user].id unless session[:user].nil?
49 @attachment.save
50 end
51 render :action => 'show'
52 end
53
54 def destroy_attachment
55 @document.attachments.find(params[:attachment_id]).destroy
56 render :action => 'show'
57 end
58
59 private
60 def find_project
61 @document = Document.find(params[:id])
62 @project = @document.project
63 end
64
65 end
@@ -0,0 +1,69
1 # redMine - project management software
2 # Copyright (C) 2006 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 class EnumerationsController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
21
22 def index
23 list
24 render :action => 'list'
25 end
26
27 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
28 verify :method => :post, :only => [ :destroy, :create, :update ],
29 :redirect_to => { :action => :list }
30
31 def list
32 end
33
34 def new
35 @enumeration = Enumeration.new(:opt => params[:opt])
36 end
37
38 def create
39 @enumeration = Enumeration.new(params[:enumeration])
40 if @enumeration.save
41 flash[:notice] = 'Enumeration was successfully created.'
42 redirect_to :action => 'list', :opt => @enumeration.opt
43 else
44 render :action => 'new'
45 end
46 end
47
48 def edit
49 @enumeration = Enumeration.find(params[:id])
50 end
51
52 def update
53 @enumeration = Enumeration.find(params[:id])
54 if @enumeration.update_attributes(params[:enumeration])
55 flash[:notice] = 'Enumeration was successfully updated.'
56 redirect_to :action => 'list', :opt => @enumeration.opt
57 else
58 render :action => 'edit'
59 end
60 end
61
62 def destroy
63 Enumeration.find(params[:id]).destroy
64 redirect_to :action => 'list'
65 rescue
66 flash[:notice] = "Unable to delete enumeration"
67 redirect_to :action => 'list'
68 end
69 end
@@ -0,0 +1,43
1 # redMine - project management software
2 # Copyright (C) 2006 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 class HelpController < ApplicationController
19
20 skip_before_filter :check_if_login_required
21 before_filter :load_help_config
22
23 def index
24 if @params[:ctrl] and @help_config[@params[:ctrl]]
25 if @params[:page] and @help_config[@params[:ctrl]][@params[:page]]
26 template = @help_config[@params[:ctrl]][@params[:page]]
27 else
28 template = @help_config[@params[:ctrl]]['index']
29 end
30 end
31
32 if template
33 redirect_to "/manual/#{template}"
34 else
35 redirect_to "/manual/"
36 end
37 end
38
39 private
40 def load_help_config
41 @help_config = YAML::load(File.open("#{RAILS_ROOT}/config/help.yml"))
42 end
43 end
@@ -0,0 +1,42
1 # redMine - project management software
2 # Copyright (C) 2006 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 class IssueCategoriesController < ApplicationController
19 layout 'base'
20 before_filter :find_project, :authorize
21
22 def edit
23 if request.post? and @category.update_attributes(params[:category])
24 flash[:notice] = 'Issue category was successfully updated.'
25 redirect_to :controller => 'projects', :action => 'settings', :id => @project
26 end
27 end
28
29 def destroy
30 @category.destroy
31 redirect_to :controller => 'projects', :action => 'settings', :id => @project
32 rescue
33 flash[:notice] = "Categorie can't be deleted"
34 redirect_to :controller => 'projects', :action => 'settings', :id => @project
35 end
36
37 private
38 def find_project
39 @category = IssueCategory.find(params[:id])
40 @project = @category.project
41 end
42 end
@@ -0,0 +1,68
1 # redMine - project management software
2 # Copyright (C) 2006 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 class IssueStatusesController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
21
22 def index
23 list
24 render :action => 'list'
25 end
26
27 def list
28 @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 10
29 end
30
31 def new
32 @issue_status = IssueStatus.new
33 end
34
35 def create
36 @issue_status = IssueStatus.new(params[:issue_status])
37 if @issue_status.save
38 flash[:notice] = 'IssueStatus was successfully created.'
39 redirect_to :action => 'list'
40 else
41 render :action => 'new'
42 end
43 end
44
45 def edit
46 @issue_status = IssueStatus.find(params[:id])
47 end
48
49 def update
50 @issue_status = IssueStatus.find(params[:id])
51 if @issue_status.update_attributes(params[:issue_status])
52 flash[:notice] = 'IssueStatus was successfully updated.'
53 redirect_to :action => 'list'
54 else
55 render :action => 'edit'
56 end
57 end
58
59 def destroy
60 IssueStatus.find(params[:id]).destroy
61 redirect_to :action => 'list'
62 rescue
63 flash[:notice] = "Unable to delete issue status"
64 redirect_to :action => 'list'
65 end
66
67
68 end
@@ -0,0 +1,102
1 # redMine - project management software
2 # Copyright (C) 2006 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 class IssuesController < ApplicationController
19 layout 'base'
20 before_filter :find_project, :authorize
21
22 helper :custom_fields
23 include CustomFieldsHelper
24
25 def show
26 @status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", session[:user].role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if session[:user]
27 end
28
29 def edit
30 @trackers = Tracker.find(:all)
31 @priorities = Enumeration::get_values('IPRI')
32
33 if request.get?
34 @custom_values = @project.custom_fields_for_issues.collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
35 else
36 # Retrieve custom fields and values
37 @custom_values = @project.custom_fields_for_issues.collect { |x| CustomValue.new(:custom_field => x, :value => params["custom_fields"][x.id.to_s]) }
38
39 @issue.custom_values = @custom_values
40 if @issue.update_attributes(params[:issue])
41 flash[:notice] = 'Issue was successfully updated.'
42 redirect_to :action => 'show', :id => @issue
43 end
44 end
45 end
46
47 def change_status
48 @history = @issue.histories.build(params[:history])
49 @status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", session[:user].role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if session[:user]
50
51 if params[:confirm]
52 unless session[:user].nil?
53 @history.author = session[:user]
54 end
55 if @history.save
56 @issue.status = @history.status
57 @issue.fixed_version_id = (params[:issue][:fixed_version_id])
58 @issue.assigned_to_id = (params[:issue][:assigned_to_id])
59 if @issue.save
60 flash[:notice] = 'Issue was successfully updated.'
61 Mailer.deliver_issue_change_status(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
62 redirect_to :action => 'show', :id => @issue
63 end
64 end
65 end
66 @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
67
68 end
69
70 def destroy
71 @issue.destroy
72 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project
73 end
74
75 def add_attachment
76 # Save the attachment
77 if params[:attachment][:file].size > 0
78 @attachment = @issue.attachments.build(params[:attachment])
79 @attachment.author_id = session[:user].id unless session[:user].nil?
80 @attachment.save
81 end
82 redirect_to :action => 'show', :id => @issue
83 end
84
85 def destroy_attachment
86 @issue.attachments.find(params[:attachment_id]).destroy
87 redirect_to :action => 'show', :id => @issue
88 end
89
90 # Send the file in stream mode
91 def download
92 @attachment = @issue.attachments.find(params[:attachment_id])
93 send_file @attachment.diskfile, :filename => @attachment.filename
94 end
95
96 private
97 def find_project
98 @issue = Issue.find(params[:id])
99 @project = @issue.project
100 end
101
102 end
@@ -0,0 +1,41
1 # redMine - project management software
2 # Copyright (C) 2006 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 class MembersController < ApplicationController
19 layout 'base'
20 before_filter :find_project, :authorize
21
22 def edit
23 if request.post? and @member.update_attributes(params[:member])
24 flash[:notice] = 'Member was successfully updated.'
25 redirect_to :controller => 'projects', :action => 'settings', :id => @project
26 end
27 end
28
29 def destroy
30 @member.destroy
31 flash[:notice] = 'Member was successfully removed.'
32 redirect_to :controller => 'projects', :action => 'settings', :id => @project
33 end
34
35 private
36 def find_project
37 @member = Member.find(params[:id])
38 @project = @member.project
39 end
40
41 end
@@ -0,0 +1,42
1 # redMine - project management software
2 # Copyright (C) 2006 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 class NewsController < ApplicationController
19 layout 'base'
20 before_filter :find_project, :authorize
21
22 def show
23 end
24
25 def edit
26 if request.post? and @news.update_attributes(params[:news])
27 flash[:notice] = 'News was successfully updated.'
28 redirect_to :action => 'show', :id => @news
29 end
30 end
31
32 def destroy
33 @news.destroy
34 redirect_to :controller => 'projects', :action => 'list_news', :id => @project
35 end
36
37 private
38 def find_project
39 @news = News.find(params[:id])
40 @project = @news.project
41 end
42 end
@@ -0,0 +1,260
1 # redMine - project management software
2 # Copyright (C) 2006 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 class ProjectsController < ApplicationController
19 layout 'base'
20 before_filter :find_project, :authorize, :except => [ :index, :list, :add ]
21 before_filter :require_admin, :only => [ :add, :destroy ]
22
23 helper :sort
24 include SortHelper
25 helper :search_filter
26 include SearchFilterHelper
27 helper :custom_fields
28 include CustomFieldsHelper
29
30 def index
31 list
32 render :action => 'list'
33 end
34
35 # Lists public projects
36 def list
37 sort_init 'projects.name', 'asc'
38 sort_update
39 @project_count = Project.count(["public=?", true])
40 @project_pages = Paginator.new self, @project_count,
41 15,
42 @params['page']
43 @projects = Project.find :all, :order => sort_clause,
44 :conditions => ["public=?", true],
45 :limit => @project_pages.items_per_page,
46 :offset => @project_pages.current.offset
47 end
48
49 # Add a new project
50 def add
51 @custom_fields = CustomField::find_all
52 @project = Project.new(params[:project])
53 if request.post?
54 @project.custom_fields = CustomField.find(@params[:custom_field_ids]) if @params[:custom_field_ids]
55 if @project.save
56 flash[:notice] = 'Project was successfully created.'
57 redirect_to :controller => 'admin', :action => 'projects'
58 end
59 end
60 end
61
62 # Show @project
63 def show
64 @members = @project.members.find(:all, :include => [:user, :role])
65 end
66
67 def settings
68 @custom_fields = CustomField::find_all
69 @issue_category ||= IssueCategory.new
70 @member ||= @project.members.new
71 @roles = Role.find_all
72 @users = User.find_all - @project.members.find(:all, :include => :user).collect{|m| m.user }
73 end
74
75 # Edit @project
76 def edit
77 if request.post?
78 @project.custom_fields = CustomField.find(@params[:custom_field_ids]) if @params[:custom_field_ids]
79 if @project.update_attributes(params[:project])
80 flash[:notice] = 'Project was successfully updated.'
81 redirect_to :action => 'settings', :id => @project
82 else
83 settings
84 render :action => 'settings'
85 end
86 end
87 end
88
89 # Delete @project
90 def destroy
91 if request.post? and params[:confirm]
92 @project.destroy
93 redirect_to :controller => 'admin', :action => 'projects'
94 end
95 end
96
97 # Add a new issue category to @project
98 def add_issue_category
99 if request.post?
100 @issue_category = @project.issue_categories.build(params[:issue_category])
101 if @issue_category.save
102 redirect_to :action => 'settings', :id => @project
103 else
104 settings
105 render :action => 'settings'
106 end
107 end
108 end
109
110 # Add a new version to @project
111 def add_version
112 @version = @project.versions.build(params[:version])
113 if request.post? and @version.save
114 redirect_to :action => 'settings', :id => @project
115 end
116 end
117
118 # Add a new member to @project
119 def add_member
120 @member = @project.members.build(params[:member])
121 if request.post?
122 if @member.save
123 flash[:notice] = 'Member was successfully added.'
124 redirect_to :action => 'settings', :id => @project
125 else
126 settings
127 render :action => 'settings'
128 end
129 end
130 end
131
132 # Show members list of @project
133 def list_members
134 @members = @project.members
135 end
136
137 # Add a new document to @project
138 def add_document
139 @categories = Enumeration::get_values('DCAT')
140 @document = @project.documents.build(params[:document])
141 if request.post?
142 # Save the attachment
143 if params[:attachment][:file].size > 0
144 @attachment = @document.attachments.build(params[:attachment])
145 @attachment.author_id = session[:user].id unless session[:user].nil?
146 end
147 if @document.save
148 redirect_to :action => 'list_documents', :id => @project
149 end
150 end
151 end
152
153 # Show documents list of @project
154 def list_documents
155 @documents = @project.documents
156 end
157
158 # Add a new issue to @project
159 def add_issue
160 @trackers = Tracker.find(:all)
161 @priorities = Enumeration::get_values('IPRI')
162 if request.get?
163 @issue = @project.issues.build
164 @custom_values = @project.custom_fields_for_issues.collect { |x| CustomValue.new(:custom_field => x) }
165 else
166 # Create the issue and set the author
167 @issue = @project.issues.build(params[:issue])
168 @issue.author = session[:user] unless session[:user].nil?
169 # Create the document if a file was sent
170 if params[:attachment][:file].size > 0
171 @attachment = @issue.attachments.build(params[:attachment])
172 @attachment.author_id = session[:user].id unless session[:user].nil?
173 end
174 @custom_values = @project.custom_fields_for_issues.collect { |x| CustomValue.new(:custom_field => x, :value => params["custom_fields"][x.id.to_s]) }
175 @issue.custom_values = @custom_values
176 if @issue.save
177 flash[:notice] = "Issue was successfully added."
178 Mailer.deliver_issue_add(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
179 redirect_to :action => 'list_issues', :id => @project
180 end
181 end
182 end
183
184 # Show issues list of @project
185 def list_issues
186 sort_init 'issues.id', 'desc'
187 sort_update
188
189 search_filter_criteria 'issues.tracker_id', :values => "Tracker.find(:all)"
190 search_filter_criteria 'issues.priority_id', :values => "Enumeration.find(:all, :conditions => ['opt=?','IPRI'])"
191 search_filter_criteria 'issues.category_id', :values => "@project.issue_categories"
192 search_filter_criteria 'issues.status_id', :values => "IssueStatus.find(:all)"
193 search_filter_criteria 'issues.author_id', :values => "User.find(:all)", :label => "display_name"
194 search_filter_update if params[:set_filter] or request.post?
195
196 @issue_count = @project.issues.count(search_filter_clause)
197 @issue_pages = Paginator.new self, @issue_count,
198 15,
199 @params['page']
200 @issues = @project.issues.find :all, :order => sort_clause,
201 :include => [ :author, :status, :tracker ],
202 :conditions => search_filter_clause,
203 :limit => @issue_pages.items_per_page,
204 :offset => @issue_pages.current.offset
205 end
206
207 # Add a news to @project
208 def add_news
209 @news = @project.news.build(params[:news])
210 if request.post?
211 @news.author = session[:user] unless session[:user].nil?
212 if @news.save
213 redirect_to :action => 'list_news', :id => @project
214 end
215 end
216 end
217
218 # Show news list of @project
219 def list_news
220 @news_pages, @news = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "news.created_on DESC"
221 end
222
223 def add_file
224 if request.post?
225 # Save the attachment
226 if params[:attachment][:file].size > 0
227 @attachment = @project.versions.find(params[:version_id]).attachments.build(params[:attachment])
228 @attachment.author_id = session[:user].id unless session[:user].nil?
229 if @attachment.save
230 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
231 end
232 end
233 end
234 @versions = @project.versions
235 end
236
237 def list_files
238 @versions = @project.versions
239 end
240
241 # Show changelog of @project
242 def changelog
243 @fixed_issues = @project.issues.find(:all,
244 :include => [ :fixed_version, :status, :tracker ],
245 :conditions => [ "issue_statuses.is_closed=? and trackers.is_in_chlog=? and issues.fixed_version_id is not null", true, true]
246 )
247 end
248
249 private
250 # Find project of id params[:id]
251 # if not found, redirect to project list
252 # used as a before_filter
253 def find_project
254 @project = Project.find(params[:id])
255 rescue
256 flash[:notice] = 'Project not found.'
257 redirect_to :action => 'list'
258 end
259
260 end
@@ -0,0 +1,71
1 # redMine - project management software
2 # Copyright (C) 2006 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 class ReportsController < ApplicationController
19 layout 'base'
20 before_filter :find_project, :authorize
21
22 def issue_report
23 @statuses = IssueStatus.find_all
24 @trackers = Tracker.find_all
25 @issues_by_tracker =
26 ActiveRecord::Base.connection.select_all("select s.id as status_id,
27 s.is_closed as closed,
28 t.id as tracker_id,
29 count(i.id) as total
30 from
31 issues i, issue_statuses s, trackers t
32 where
33 i.status_id=s.id
34 and i.tracker_id=t.id
35 and i.project_id=#{@project.id}
36 group by s.id, t.id")
37 @priorities = Enumeration::get_values('IPRI')
38 @issues_by_priority =
39 ActiveRecord::Base.connection.select_all("select s.id as status_id,
40 s.is_closed as closed,
41 p.id as priority_id,
42 count(i.id) as total
43 from
44 issues i, issue_statuses s, enumerations p
45 where
46 i.status_id=s.id
47 and i.priority_id=p.id
48 and i.project_id=#{@project.id}
49 group by s.id, p.id")
50 @categories = @project.issue_categories
51 @issues_by_category =
52 ActiveRecord::Base.connection.select_all("select s.id as status_id,
53 s.is_closed as closed,
54 c.id as category_id,
55 count(i.id) as total
56 from
57 issues i, issue_statuses s, issue_categories c
58 where
59 i.status_id=s.id
60 and i.category_id=c.id
61 and i.project_id=#{@project.id}
62 group by s.id, c.id")
63 end
64
65
66 private
67 # Find project of id params[:id]
68 def find_project
69 @project = Project.find(params[:id])
70 end
71 end
@@ -0,0 +1,84
1 # redMine - project management software
2 # Copyright (C) 2006 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 class RolesController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
21
22 def index
23 list
24 render :action => 'list'
25 end
26
27 def list
28 @role_pages, @roles = paginate :roles, :per_page => 10
29 end
30
31 def new
32 @role = Role.new(params[:role])
33 if request.post?
34 @role.permissions = Permission.find(@params[:permission_ids]) if @params[:permission_ids]
35 if @role.save
36 flash[:notice] = 'Role was successfully created.'
37 redirect_to :action => 'list'
38 end
39 end
40 @permissions = Permission.find(:all, :order => 'sort ASC')
41 end
42
43 def edit
44 @role = Role.find(params[:id])
45 if request.post? and @role.update_attributes(params[:role])
46 @role.permissions = Permission.find(@params[:permission_ids] || [])
47 Permission.allowed_to_role_expired
48 flash[:notice] = 'Role was successfully updated.'
49 redirect_to :action => 'list'
50 end
51 @permissions = Permission.find(:all, :order => 'sort ASC')
52 end
53
54 def destroy
55 @role = Role.find(params[:id])
56 unless @role.members.empty?
57 flash[:notice] = 'Some members have this role. Can\'t delete it.'
58 else
59 @role.destroy
60 end
61 redirect_to :action => 'list'
62 end
63
64 def workflow
65 @roles = Role.find_all
66 @trackers = Tracker.find_all
67 @statuses = IssueStatus.find_all
68
69 @role = Role.find_by_id(params[:role_id])
70 @tracker = Tracker.find_by_id(params[:tracker_id])
71
72 if request.post?
73 Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
74 (params[:issue_status] || []).each { |old, news|
75 news.each { |new|
76 @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new)
77 }
78 }
79 if @role.save
80 flash[:notice] = 'Workflow was successfully updated.'
81 end
82 end
83 end
84 end
@@ -0,0 +1,60
1 # redMine - project management software
2 # Copyright (C) 2006 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 class TrackersController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
21
22 def index
23 list
24 render :action => 'list'
25 end
26
27 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
28 verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :list }
29
30 def list
31 @tracker_pages, @trackers = paginate :trackers, :per_page => 10
32 end
33
34 def new
35 @tracker = Tracker.new(params[:tracker])
36 if request.post? and @tracker.save
37 flash[:notice] = 'Tracker was successfully created.'
38 redirect_to :action => 'list'
39 end
40 end
41
42 def edit
43 @tracker = Tracker.find(params[:id])
44 if request.post? and @tracker.update_attributes(params[:tracker])
45 flash[:notice] = 'Tracker was successfully updated.'
46 redirect_to :action => 'list'
47 end
48 end
49
50 def destroy
51 @tracker = Tracker.find(params[:id])
52 unless @tracker.issues.empty?
53 flash[:notice] = "This tracker contains issues and can\'t be deleted."
54 else
55 @tracker.destroy
56 end
57 redirect_to :action => 'list'
58 end
59
60 end
@@ -0,0 +1,73
1 # redMine - project management software
2 # Copyright (C) 2006 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 class UsersController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
21
22 helper :sort
23 include SortHelper
24
25 def index
26 list
27 render :action => 'list'
28 end
29
30 def list
31 sort_init 'users.login', 'asc'
32 sort_update
33 @user_count = User.count
34 @user_pages = Paginator.new self, @user_count,
35 15,
36 @params['page']
37 @users = User.find :all, :order => sort_clause,
38 :limit => @user_pages.items_per_page,
39 :offset => @user_pages.current.offset
40 end
41
42 def add
43 if request.get?
44 @user = User.new
45 else
46 @user = User.new(params[:user])
47 @user.admin = params[:user][:admin]
48 if @user.save
49 flash[:notice] = 'User was successfully created.'
50 redirect_to :action => 'list'
51 end
52 end
53 end
54
55 def edit
56 @user = User.find(params[:id])
57 if request.post?
58 @user.admin = params[:user][:admin] if params[:user][:admin]
59 if @user.update_attributes(params[:user])
60 flash[:notice] = 'User was successfully updated.'
61 redirect_to :action => 'list'
62 end
63 end
64 end
65
66 def destroy
67 User.find(params[:id]).destroy
68 redirect_to :action => 'list'
69 rescue
70 flash[:notice] = "Unable to delete user"
71 redirect_to :action => 'list'
72 end
73 end
@@ -0,0 +1,53
1 # redMine - project management software
2 # Copyright (C) 2006 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 class VersionsController < ApplicationController
19 layout 'base'
20 before_filter :find_project, :authorize
21
22 def edit
23 if request.post? and @version.update_attributes(params[:version])
24 flash[:notice] = 'Version was successfully updated.'
25 redirect_to :controller => 'projects', :action => 'settings', :id => @project
26 end
27 end
28
29 def destroy
30 @version.destroy
31 redirect_to :controller => 'projects', :action => 'settings', :id => @project
32 rescue
33 flash[:notice] = "Unable to delete version"
34 redirect_to :controller => 'projects', :action => 'settings', :id => @project
35 end
36
37 def download
38 @attachment = @version.attachments.find(params[:attachment_id])
39 @attachment.increment_download
40 send_file @attachment.diskfile, :filename => @attachment.filename
41 end
42
43 def destroy_file
44 @version.attachments.find(params[:attachment_id]).destroy
45 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
46 end
47
48 private
49 def find_project
50 @version = Version.find(params[:id])
51 @project = @version.project
52 end
53 end
@@ -0,0 +1,26
1 # redMine - project management software
2 # Copyright (C) 2006 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 class WelcomeController < ApplicationController
19 layout 'base'
20
21 def index
22 @news = News.latest
23 @projects = Project.latest
24 end
25
26 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 AccountHelper
19 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 AdminHelper
19 end
@@ -0,0 +1,65
1 # redMine - project management software
2 # Copyright (C) 2006 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 ApplicationHelper
19
20 def loggedin?
21 session[:user]
22 end
23
24 def admin_loggedin?
25 session[:user] && session[:user].admin
26 end
27
28 def authorize_for(controller, action)
29 # check if action is allowed on public projects
30 if @project.public? and Permission.allowed_to_public "%s/%s" % [ controller, action ]
31 return true
32 end
33 # check if user is authorized
34 if session[:user] and (session[:user].admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], session[:user].role_for_project(@project.id) ) )
35 return true
36 end
37 return false
38 end
39
40 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
41 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller], options[:action])
42 end
43
44 # Display a link to user's account page
45 def link_to_user(user)
46 link_to user.display_name, :controller => 'account', :action => 'show', :id => user
47 end
48
49 def format_date(date)
50 _('(date)', date) if date
51 end
52
53 def format_time(time)
54 _('(time)', time) if time
55 end
56
57 def pagination_links_full(paginator, options={}, html_options={})
58 html =''
59 html << link_to(('&#171; ' + _('Previous') ), { :page => paginator.current.previous }) + ' ' if paginator.current.previous
60 html << (pagination_links(paginator, options, html_options) || '')
61 html << ' ' + link_to((_('Next') + ' &#187;'), { :page => paginator.current.next }) if paginator.current.next
62 html
63 end
64
65 end
@@ -0,0 +1,36
1 # redMine - project management software
2 # Copyright (C) 2006 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 CustomFieldsHelper
19 def custom_field_tag(custom_value)
20
21 custom_field = custom_value.custom_field
22
23 field_name = "custom_fields[#{custom_field.id}]"
24
25 case custom_field.typ
26 when 0 .. 2
27 text_field_tag field_name, custom_value.value
28 when 3
29 check_box field_name
30 when 4
31 select_tag field_name,
32 options_for_select(custom_field.possible_values.split('|'),
33 custom_value.value)
34 end
35 end
36 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 DocumentsHelper
19 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 EnumerationsHelper
19 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 HelpHelper
19 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 IssueCategoriesHelper
19 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 IssueStatusesHelper
19 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 IssuesHelper
19 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 MembersHelper
19 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 NewsHelper
19 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 ProjectsHelper
19 end
@@ -0,0 +1,32
1 # redMine - project management software
2 # Copyright (C) 2006 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 ReportsHelper
19
20 def aggregate(data, criteria)
21 a = 0
22 data.each { |row|
23 match = 1
24 criteria.each { |k, v|
25 match = 0 unless row[k].to_s == v.to_s
26 } unless criteria.nil?
27 a = a + row["total"].to_i if match == 1
28 } unless data.nil?
29 a
30 end
31
32 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 RolesHelper
19 end
@@ -0,0 +1,55
1 # redMine - project management software
2 # Copyright (C) 2006 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 SearchFilterHelper
19
20 def search_filter_criteria(field, options = {})
21 session[:search_filter] ||= {}
22 session[:search_filter][field] ||= options
23 # session[:search_filter][field][:values] = options[:values] unless options[:values].nil?
24 # session[:search_filter][field][:label] = options[:label] unless options[:label].nil?
25 end
26
27 def search_filter_update
28 session[:search_filter].each_key {|field| session[:search_filter][field][:value] = params[field] }
29 #@search_filter[:value] = params[@search_filter[:field]]
30 end
31
32 def search_filter_clause
33 clause = "1=1"
34 session[:search_filter].each {|field, criteria| clause = clause + " AND " + field + "='" + session[:search_filter][field][:value] + "'" unless session[:search_filter][field][:value].nil? || session[:search_filter][field][:value].empty? }
35 clause
36 #@search_filter[:field] + "='" + @search_filter[:value] + "'" unless @search_filter[:value].nil? || @search_filter[:value].empty?
37 end
38
39 def search_filter_tag(field)
40 option_values = []
41 #values = eval @search_filter[:values_expr]
42 option_values = eval session[:search_filter][field][:values]
43
44 content_tag("select",
45 content_tag("option", "[All]", :value => "") +
46 options_from_collection_for_select(option_values,
47 "id",
48 session[:search_filter][field][:label] || "name",
49 session[:search_filter][field][:value].to_i
50 ),
51 :name => field
52 )
53 end
54
55 end No newline at end of file
@@ -0,0 +1,157
1 # Helpers to sort tables using clickable column headers.
2 #
3 # Author: Stuart Rackham <srackham@methods.co.nz>, March 2005.
4 # License: This source code is released under the MIT license.
5 #
6 # - Consecutive clicks toggle the column's sort order.
7 # - Sort state is maintained by a session hash entry.
8 # - Icon image identifies sort column and state.
9 # - Typically used in conjunction with the Pagination module.
10 #
11 # Example code snippets:
12 #
13 # Controller:
14 #
15 # helper :sort
16 # include SortHelper
17 #
18 # def list
19 # sort_init 'last_name'
20 # sort_update
21 # @items = Contact.find_all nil, sort_clause
22 # end
23 #
24 # Controller (using Pagination module):
25 #
26 # helper :sort
27 # include SortHelper
28 #
29 # def list
30 # sort_init 'last_name'
31 # sort_update
32 # @contact_pages, @items = paginate :contacts,
33 # :order_by => sort_clause,
34 # :per_page => 10
35 # end
36 #
37 # View (table header in list.rhtml):
38 #
39 # <thead>
40 # <tr>
41 # <%= sort_header_tag('id', :title => 'Sort by contact ID') %>
42 # <%= sort_header_tag('last_name', :caption => 'Name') %>
43 # <%= sort_header_tag('phone') %>
44 # <%= sort_header_tag('address', :width => 200) %>
45 # </tr>
46 # </thead>
47 #
48 # - The ascending and descending sort icon images are sort_asc.png and
49 # sort_desc.png and reside in the application's images directory.
50 # - Introduces instance variables: @sort_name, @sort_default.
51 # - Introduces params :sort_key and :sort_order.
52 #
53 module SortHelper
54
55 # Initializes the default sort column (default_key) and sort order
56 # (default_order).
57 #
58 # - default_key is a column attribute name.
59 # - default_order is 'asc' or 'desc'.
60 # - name is the name of the session hash entry that stores the sort state,
61 # defaults to '<controller_name>_sort'.
62 #
63 def sort_init(default_key, default_order='asc', name=nil)
64 @sort_name = name || @params[:controller] + @params[:action] + '_sort'
65 @sort_default = {:key => default_key, :order => default_order}
66 end
67
68 # Updates the sort state. Call this in the controller prior to calling
69 # sort_clause.
70 #
71 def sort_update()
72 if @params[:sort_key]
73 sort = {:key => @params[:sort_key], :order => @params[:sort_order]}
74 elsif @session[@sort_name]
75 sort = @session[@sort_name] # Previous sort.
76 else
77 sort = @sort_default
78 end
79 @session[@sort_name] = sort
80 end
81
82 # Returns an SQL sort clause corresponding to the current sort state.
83 # Use this to sort the controller's table items collection.
84 #
85 def sort_clause()
86 @session[@sort_name][:key] + ' ' + @session[@sort_name][:order]
87 end
88
89 # Returns a link which sorts by the named column.
90 #
91 # - column is the name of an attribute in the sorted record collection.
92 # - The optional caption explicitly specifies the displayed link text.
93 # - A sort icon image is positioned to the right of the sort link.
94 #
95 def sort_link(column, caption=nil)
96 key, order = @session[@sort_name][:key], @session[@sort_name][:order]
97 if key == column
98 if order.downcase == 'asc'
99 icon = 'sort_asc'
100 order = 'desc'
101 else
102 icon = 'sort_desc'
103 order = 'asc'
104 end
105 else
106 icon = nil
107 order = 'desc' # changed for desc order by default
108 end
109 caption = titleize(Inflector::humanize(column)) unless caption
110 params = {:params => {:sort_key => column, :sort_order => order}}
111 link_to(caption, params) + (icon ? nbsp(2) + image_tag(icon) : '')
112 end
113
114 # Returns a table header <th> tag with a sort link for the named column
115 # attribute.
116 #
117 # Options:
118 # :caption The displayed link name (defaults to titleized column name).
119 # :title The tag's 'title' attribute (defaults to 'Sort by :caption').
120 #
121 # Other options hash entries generate additional table header tag attributes.
122 #
123 # Example:
124 #
125 # <%= sort_header_tag('id', :title => 'Sort by contact ID', :width => 40) %>
126 #
127 # Renders:
128 #
129 # <th title="Sort by contact ID" width="40">
130 # <a href="/contact/list?sort_order=desc&amp;sort_key=id">Id</a>
131 # &nbsp;&nbsp;<img alt="Sort_asc" src="/images/sort_asc.png" />
132 # </th>
133 #
134 def sort_header_tag(column, options = {})
135 if options[:caption]
136 caption = options[:caption]
137 options.delete(:caption)
138 else
139 caption = titleize(Inflector::humanize(column))
140 end
141 options[:title]= "Sort by #{caption}" unless options[:title]
142 content_tag('th', sort_link(column, caption), options)
143 end
144
145 private
146
147 # Return n non-breaking spaces.
148 def nbsp(n)
149 '&nbsp;' * n
150 end
151
152 # Return capitalized title.
153 def titleize(title)
154 title.split.map {|w| w.capitalize }.join(' ')
155 end
156
157 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 TrackersHelper
19 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 UsersHelper
19 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 VersionsHelper
19 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 WelcomeHelper
19 end
@@ -0,0 +1,81
1 # redMine - project management software
2 # Copyright (C) 2006 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 require "digest/md5"
19
20 class Attachment < ActiveRecord::Base
21 belongs_to :container, :polymorphic => true
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
23
24 validates_presence_of :filename
25
26 def file=(incomming_file)
27 unless incomming_file.nil?
28 @temp_file = incomming_file
29 if @temp_file.size > 0
30 self.filename = sanitize_filename(@temp_file.original_filename)
31 self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
32 self.content_type = @temp_file.content_type
33 self.size = @temp_file.size
34 end
35 end
36 end
37
38 # Copy temp file to its final location
39 def before_save
40 if @temp_file && (@temp_file.size > 0)
41 logger.debug("saving '#{self.diskfile}'")
42 File.open(diskfile, "wb") do |f|
43 f.write(@temp_file.read)
44 end
45 self.digest = Digest::MD5.hexdigest(File.read(diskfile))
46 end
47 end
48
49 # Deletes file on the disk
50 def after_destroy
51 if self.filename?
52 File.delete(diskfile) if File.exist?(diskfile)
53 end
54 end
55
56 # Returns file's location on disk
57 def diskfile
58 "#{RDM_STORAGE_PATH}/#{self.disk_filename}"
59 end
60
61 def increment_download
62 increment!(:downloads)
63 end
64
65 # returns last created projects
66 def self.most_downloaded
67 find(:all, :limit => 5, :order => "downloads DESC")
68 end
69
70 private
71 def sanitize_filename(value)
72 # get only the filename, not the whole path
73 just_filename = value.gsub(/^.*(\\|\/)/, '')
74 # NOTE: File.basename doesn't work right with Windows paths on Unix
75 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
76
77 # Finally, replace all non alphanumeric, underscore or periods with underscore
78 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
79 end
80
81 end
@@ -0,0 +1,38
1 # redMine - project management software
2 # Copyright (C) 2006 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 class CustomField < ActiveRecord::Base
19
20 has_and_belongs_to_many :projects
21 has_many :custom_values, :dependent => true
22 has_many :issues, :through => :issue_custom_values
23
24 validates_presence_of :name, :typ
25 validates_uniqueness_of :name
26
27 TYPES = [
28 [ "Integer", 0 ],
29 [ "String", 1 ],
30 [ "Date", 2 ],
31 [ "Boolean", 3 ],
32 [ "List", 4 ]
33 ].freeze
34
35 def self.for_all
36 find(:all, :conditions => ["is_for_all=?", true])
37 end
38 end
@@ -0,0 +1,41
1 # redMine - project management software
2 # Copyright (C) 2006 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 class CustomValue < ActiveRecord::Base
19 belongs_to :issue
20 belongs_to :custom_field
21
22 protected
23 def validate
24 errors.add(custom_field.name, "can't be blank") if custom_field.is_required? and value.empty?
25 errors.add(custom_field.name, "is not valid") unless custom_field.regexp.empty? or value =~ Regexp.new(custom_field.regexp)
26
27 case custom_field.typ
28 when 0
29 errors.add(custom_field.name, "must be an integer") unless value =~ /^[0-9]*$/
30 when 1
31 errors.add(custom_field.name, "is too short") if custom_field.min_length > 0 and value.length < custom_field.min_length and value.length > 0
32 errors.add(custom_field.name, "is too long") if custom_field.max_length > 0 and value.length > custom_field.max_length
33 when 2
34 errors.add(custom_field.name, "must be a valid date") unless value =~ /^(\d+)\/(\d+)\/(\d+)$/ or value.empty?
35 when 3
36
37 when 4
38 errors.add(custom_field.name, "is not a valid value") unless custom_field.possible_values.split('|').include? value or value.empty?
39 end
40 end
41 end
@@ -0,0 +1,24
1 # redMine - project management software
2 # Copyright (C) 2006 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 class Document < ActiveRecord::Base
19 belongs_to :project
20 belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
21 has_many :attachments, :as => :container, :dependent => true
22
23 validates_presence_of :title
24 end
@@ -0,0 +1,45
1 # redMine - project management software
2 # Copyright (C) 2006 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 class Enumeration < ActiveRecord::Base
19 before_destroy :check_integrity
20
21 validates_presence_of :opt, :name
22
23 OPTIONS = [
24 ["Issue priorities", "IPRI"],
25 ["Document categories", "DCAT"]
26 ].freeze
27
28 def self.get_values(option)
29 find(:all, :conditions => ['opt=?', option])
30 end
31
32 def name
33 _ self.attributes['name']
34 end
35
36 private
37 def check_integrity
38 case self.opt
39 when "IPRI"
40 raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id])
41 when "DCAT"
42 raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id])
43 end
44 end
45 end
@@ -0,0 +1,55
1 # redMine - project management software
2 # Copyright (C) 2006 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 class Issue < ActiveRecord::Base
19
20 belongs_to :project
21 belongs_to :tracker
22 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
23 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
24 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
25 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
26 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
27 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
28
29 has_many :histories, :class_name => 'IssueHistory', :dependent => true, :order => "issue_histories.created_on DESC", :include => :status
30 has_many :attachments, :as => :container, :dependent => true
31
32 has_many :custom_values, :dependent => true
33 has_many :custom_fields, :through => :custom_values
34
35 validates_presence_of :subject, :descr, :priority, :tracker, :author
36
37 # set default status for new issues
38 def before_create
39 self.status = IssueStatus.default
40 build_history
41 end
42
43 def long_id
44 "%05d" % self.id
45 end
46
47 private
48 # Creates an history for the issue
49 def build_history
50 @history = self.histories.build
51 @history.status = self.status
52 @history.author = self.author
53 end
54
55 end
@@ -0,0 +1,28
1 # redMine - project management software
2 # Copyright (C) 2006 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 class IssueCategory < ActiveRecord::Base
19 before_destroy :check_integrity
20 belongs_to :project
21
22 validates_presence_of :name
23
24 private
25 def check_integrity
26 raise "Can't delete category" if Issue.find(:first, :conditions => ["category_id=?", self.id])
27 end
28 end
@@ -0,0 +1,23
1 # redMine - project management software
2 # Copyright (C) 2006 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 class IssueHistory < ActiveRecord::Base
19 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
20 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
21
22 validates_presence_of :status
23 end
@@ -0,0 +1,47
1 # redMine - project management software
2 # Copyright (C) 2006 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 class IssueStatus < ActiveRecord::Base
19 before_destroy :check_integrity
20 has_many :workflows, :foreign_key => "old_status_id"
21
22 validates_presence_of :name
23 validates_uniqueness_of :name
24
25 # Returns the default status for new issues
26 def self.default
27 find(:first, :conditions =>["is_default=?", true])
28 end
29
30 # Returns an array of all statuses the given role can switch to
31 def new_statuses_allowed_to(role, tracker)
32 statuses = []
33 for workflow in self.workflows.find(:all, :include => :new_status)
34 statuses << workflow.new_status if workflow.role_id == role.id and workflow.tracker_id == tracker.id
35 end unless role.nil?
36 statuses
37 end
38
39 def name
40 _ self.attributes['name']
41 end
42
43 private
44 def check_integrity
45 raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id]) or IssueHistory.find(:first, :conditions => ["status_id=?", self.id])
46 end
47 end
@@ -0,0 +1,36
1 # redMine - project management software
2 # Copyright (C) 2006 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 class Mailer < ActionMailer::Base
19
20 def issue_change_status(issue)
21 # Sends to all project members
22 @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
23 @from = 'redmine@somenet.foo'
24 @subject = "Issue ##{issue.id} has been updated"
25 @body['issue'] = issue
26 end
27
28 def issue_add(issue)
29 # Sends to all project members
30 @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
31 @from = 'redmine@somenet.foo'
32 @subject = "Issue ##{issue.id} has been reported"
33 @body['issue'] = issue
34 end
35
36 end
@@ -0,0 +1,29
1 # redMine - project management software
2 # Copyright (C) 2006 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 class Member < ActiveRecord::Base
19 belongs_to :user
20 belongs_to :role
21 belongs_to :project
22
23 validates_presence_of :role, :user, :project
24 validates_uniqueness_of :user_id, :scope => :project_id
25
26 def name
27 self.user.display_name
28 end
29 end
@@ -0,0 +1,28
1 # redMine - project management software
2 # Copyright (C) 2006 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 class News < ActiveRecord::Base
19 belongs_to :project
20 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
21
22 validates_presence_of :title, :shortdescr, :descr
23
24 # returns last created news
25 def self.latest
26 find(:all, :limit => 5, :include => [ :author, :project ], :order => "news.created_on DESC")
27 end
28 end
@@ -0,0 +1,63
1 # redMine - project management software
2 # Copyright (C) 2006 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 class Permission < ActiveRecord::Base
19 has_and_belongs_to_many :roles
20
21 validates_presence_of :controller, :action, :descr
22
23 GROUPS = {
24 100 => "Project",
25 200 => "Membres",
26 300 => "Versions",
27 400 => "Issue categories",
28 1000 => "Issues",
29 1100 => "News",
30 1200 => "Documents",
31 1300 => "Files",
32 }.freeze
33
34 @@cached_perms_for_public = nil
35 @@cached_perms_for_roles = nil
36
37 def name
38 self.controller + "/" + self.action
39 end
40
41 def group_id
42 (self.sort / 100)*100
43 end
44
45 def self.allowed_to_public(action)
46 @@cached_perms_for_public ||= find(:all, :conditions => ["public=?", true]).collect {|p| "#{p.controller}/#{p.action}"}
47 @@cached_perms_for_public.include? action
48 end
49
50 def self.allowed_to_role(action, role)
51 @@cached_perms_for_roles ||=
52 begin
53 perms = {}
54 find(:all, :include => :roles).each {|p| perms.store "#{p.controller}/#{p.action}", p.roles.collect {|r| r.id } }
55 perms
56 end
57 @@cached_perms_for_roles[action] and @@cached_perms_for_roles[action].include? role
58 end
59
60 def self.allowed_to_role_expired
61 @@cached_perms_for_roles = nil
62 end
63 end
@@ -0,0 +1,44
1 # redMine - project management software
2 # Copyright (C) 2006 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 class Project < ActiveRecord::Base
19 has_many :versions, :dependent => true, :order => "versions.date DESC"
20 has_many :members, :dependent => true
21 has_many :issues, :dependent => true, :order => "issues.created_on DESC", :include => :status
22 has_many :documents, :dependent => true
23 has_many :news, :dependent => true, :order => "news.created_on DESC", :include => :author
24 has_many :issue_categories, :dependent => true
25 has_and_belongs_to_many :custom_fields
26
27 validates_presence_of :name, :descr
28
29 # returns 5 last created projects
30 def self.latest
31 find(:all, :limit => 5, :order => "created_on DESC")
32 end
33
34 # Returns current version of the project
35 def current_version
36 versions.find(:first, :conditions => [ "date <= ?", Date.today ], :order => "date DESC, id DESC")
37 end
38
39 # Returns an array of all custom fields enabled for project issues
40 # (explictly associated custom fields and custom fields enabled for all projects)
41 def custom_fields_for_issues
42 (CustomField.for_all + custom_fields).uniq
43 end
44 end
@@ -0,0 +1,31
1 # redMine - project management software
2 # Copyright (C) 2006 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 class Role < ActiveRecord::Base
19 before_destroy :check_integrity
20 has_and_belongs_to_many :permissions
21 has_many :workflows, :dependent => true
22 has_many :members
23
24 validates_presence_of :name
25 validates_uniqueness_of :name
26
27 private
28 def check_integrity
29 raise "Can't delete role" if Member.find(:first, :conditions =>["role_id=?", self.id])
30 end
31 end
@@ -0,0 +1,31
1 # redMine - project management software
2 # Copyright (C) 2006 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 class Tracker < ActiveRecord::Base
19 before_destroy :check_integrity
20 has_many :issues
21 has_many :workflows, :dependent => true
22
23 def name
24 _ self.attributes['name']
25 end
26
27 private
28 def check_integrity
29 raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id])
30 end
31 end
@@ -0,0 +1,89
1 # redMine - project management software
2 # Copyright (C) 2006 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 require "digest/sha1"
19
20 class User < ActiveRecord::Base
21 has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :dependent => true
22
23 attr_accessor :password
24 attr_accessor :last_before_login_on
25 # Prevents unauthorized assignments
26 attr_protected :admin
27
28 validates_presence_of :login, :firstname, :lastname, :mail
29 validates_uniqueness_of :login, :mail
30
31 # Login must contain lettres, numbers, underscores only
32 validates_format_of :login, :with => /^[a-z0-9_]+$/i
33 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
34
35 def before_create
36 self.hashed_password = User.hash_password(self.password)
37 end
38
39 def after_create
40 @password = nil
41 end
42
43 # Returns the user that matches user's login and password
44 def try_to_login
45 @user = User.login(self.login, self.password)
46 unless @user.nil?
47 @user.last_before_login_on = @user.last_login_on
48 @user.update_attribute(:last_login_on, DateTime.now)
49 end
50 @user
51 end
52
53 # Return user's full name for display
54 def display_name
55 firstname + " " + lastname #+ (self.admin ? " (Admin)" : "" )
56 end
57
58 # Returns the user that matches the given login and password
59 def self.login(login, password)
60 hashed_password = hash_password(password || "")
61 find(:first,
62 :conditions => ["login = ? and hashed_password = ? and locked = ?", login, hashed_password, false])
63 end
64
65 def check_password?(clear_password)
66 User.hash_password(clear_password) == self.hashed_password
67 end
68
69 def change_password(current_password, new_password)
70 self.hashed_password = User.hash_password(new_password)
71 save
72 end
73
74 def role_for_project(project_id)
75 @role_for_projects ||=
76 begin
77 roles = {}
78 self.memberships.each { |m| roles.store m.project_id, m.role_id }
79 roles
80 end
81 @role_for_projects[project_id]
82 end
83
84 private
85 # Return password digest
86 def self.hash_password(clear_password)
87 Digest::SHA1.hexdigest(clear_password)
88 end
89 end
@@ -0,0 +1,30
1 # redMine - project management software
2 # Copyright (C) 2006 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 class Version < ActiveRecord::Base
19 before_destroy :check_integrity
20 belongs_to :project
21 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
22 has_many :attachments, :as => :container, :dependent => true
23
24 validates_presence_of :name, :descr
25
26 private
27 def check_integrity
28 raise "Can't delete version" if self.fixed_issues.find(:first)
29 end
30 end
@@ -0,0 +1,25
1 # redMine - project management software
2 # Copyright (C) 2006 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 class Workflow < ActiveRecord::Base
19
20 belongs_to :role
21 belongs_to :old_status, :class_name => 'IssueStatus', :foreign_key => 'old_status_id'
22 belongs_to :new_status, :class_name => 'IssueStatus', :foreign_key => 'new_status_id'
23
24 validates_presence_of :role, :old_status, :new_status
25 end
@@ -0,0 +1,13
1 <div class="box">
2 <h2><%=_ 'Please login' %></h2>
3
4 <%= start_form_tag :action=> "login" %>
5 <p><label for="user_login"><%=_ 'Login' %>:</label><br/>
6 <input type="text" name="user[login]" id="user_login" size="30" /></p>
7
8 <p><label for="user_password"><%=_ 'Password' %>:</label><br/>
9 <input type="password" name="user[password]" id="user_password" size="30"/></p>
10
11 <p><input type="submit" name="login" value="<%=_ 'Log in' %> &#187;" class="primary" /></p>
12 <%= end_form_tag %>
13 </div> No newline at end of file
@@ -0,0 +1,54
1 <h2><%=_('My account')%></h2>
2
3 <p><%=_('Login')%>: <strong><%= @user.login %></strong><br />
4 <%=_('Created on')%>: <%= format_time(@user.created_on) %>,
5 <%=_('Last update')%>: <%= format_time(@user.updated_on) %></p>
6
7 <div class="splitcontentleft">
8 <div class="box">
9 <h3><%=_('Information')%></h3>
10 &nbsp;
11 <%= start_form_tag :action => 'my_account' %>
12 <%= error_messages_for 'user' %>
13
14 <!--[form:user]-->
15 <p><label for="user_firstname"><%=_('Firstname')%> <span class="required">*</span></label><br/>
16 <%= text_field 'user', 'firstname' %></p>
17
18 <p><label for="user_lastname"><%=_('Lastname')%> <span class="required">*</span></label><br/>
19 <%= text_field 'user', 'lastname' %></p>
20
21 <p><label for="user_mail"><%=_('Mail')%> <span class="required">*</span></label><br/>
22 <%= text_field 'user', 'mail' %></p>
23
24 <p><label for="user_language"><%=_('Language')%></label><br/>
25 <%= select("user", "language", Localization.langs) %></p>
26 <!--[eoform:user]-->
27
28 <p><%= check_box 'user', 'mail_notification' %> <label for="user_mail_notification"><%=_('Mail notifications')%></label></p>
29
30 <center><%= submit_tag _('Save') %></center>
31 <%= end_form_tag %>
32 </div>
33 </div>
34
35
36 <div class="splitcontentright">
37 <div class="box">
38 <h3><%=_('Password')%></h3>
39 &nbsp;
40 <%= start_form_tag :action => 'change_password' %>
41
42 <p><label for="old_password"><%=_('Password')%> <span class="required">*</span></label><br/>
43 <%= password_field_tag 'old_password' %></p>
44
45 <p><label for="new_password"><%=_('New password')%> <span class="required">*</span></label><br/>
46 <%= password_field_tag 'new_password' %></p>
47
48 <p><label for="new_password_confirmation"><%=_('Confirmation')%> <span class="required">*</span></label><br/>
49 <%= password_field_tag 'new_password_confirmation' %></p>
50
51 <center><%= submit_tag _('Save') %></center>
52 <%= end_form_tag %>
53 </div>
54 </div> No newline at end of file
@@ -0,0 +1,19
1 <h2><%=_('My page') %></h2>
2
3 <p>
4 <%=_('Welcome')%> <b><%= @user.firstname %></b><br />
5 <% unless @user.last_before_login_on.nil? %>
6 <%=_('Last login')%>: <%= format_time(@user.last_before_login_on) %>
7 <% end %>
8 </p>
9
10 <div class="splitcontentleft">
11 <h3><%=_('Reported issues')%></h3>
12 <%= render :partial => 'issues/list_simple', :locals => { :issues => @reported_issues } %>
13 <%= "<p>(Last #{@reported_issues.length} updated)</p>" if @reported_issues.length > 0 %>
14 </div>
15 <div class="splitcontentright">
16 <h3><%=_('Assigned to me')%></h3>
17 <%= render :partial => 'issues/list_simple', :locals => { :issues => @assigned_issues } %>
18 <%= "<p>(Last #{@assigned_issues.length} updated)</p>" if @assigned_issues.length > 0 %>
19 </div> No newline at end of file
@@ -0,0 +1,19
1 <h2><%= @user.display_name %></h2>
2
3 <p>
4 <%= mail_to @user.mail %><br />
5 <%=_('Registered on')%>: <%= format_date(@user.created_on) %>
6 </p>
7
8 <h3><%=_('Projects')%></h3>
9 <p>
10 <% for membership in @user.memberships %>
11 <%= membership.project.name %> (<%= membership.role.name %>, <%= format_date(membership.created_on) %>)
12 <br />
13 <% end %>
14 </p>
15
16 <h3><%=_('Activity')%></h3>
17 <p>
18 <%=_('Reported issues')%>: <%= Issue.count( [ "author_id=?", @user.id]) %>
19 </p> No newline at end of file
@@ -0,0 +1,45
1 <h2><%=_('Administration')%></h2>
2
3 <p>
4 <%= image_tag "projects" %>
5 <%= link_to _('Projects'), :controller => 'admin', :action => 'projects' %> |
6 <%= link_to _('New'), :controller => 'projects', :action => 'add' %>
7 </p>
8
9 <p>
10 <%= image_tag "users" %>
11 <%= link_to _('Users'), :controller => 'users' %> |
12 <%= link_to _('New'), :controller => 'users', :action => 'add' %>
13 </p>
14
15 <p>
16 <%= image_tag "role" %>
17 <%= link_to _('Roles and permissions'), :controller => 'roles' %>
18 </p>
19
20 <p>
21 <%= image_tag "tracker" %>
22 <%= link_to _('Trackers'), :controller => 'trackers' %> |
23 <%= link_to _('Custom fields'), :controller => 'custom_fields' %>
24 </p>
25
26 <p>
27 <%= image_tag "workflow" %>
28 <%= link_to _('Issue Statuses'), :controller => 'issue_statuses' %> |
29 <%= link_to _('Workflow'), :controller => 'roles', :action => 'workflow' %>
30 </p>
31
32 <p>
33 <%= image_tag "options" %>
34 <%= link_to _('Enumerations'), :controller => 'enumerations' %>
35 </p>
36
37 <p>
38 <%= image_tag "mailer" %>
39 <%= link_to _('Mail notifications'), :controller => 'admin', :action => 'mail_options' %>
40 </p>
41
42 <p>
43 <%= image_tag "help" %>
44 <%= link_to _('Information'), :controller => 'admin', :action => 'info' %>
45 </p> No newline at end of file
@@ -0,0 +1,4
1 <h2><%=_('Information')%></h2>
2
3 <%=_('Version')%>: <%= RDM_APP_NAME %> <%= RDM_APP_VERSION %><br />
4 <%=_('Database')%>: <%= @adapter_name %> No newline at end of file
@@ -0,0 +1,16
1 <h2><%=_('Mail notifications')%></h2>
2
3 <p><%=_('Select actions for which mail notification should be enabled.')%></p>
4
5 <%= start_form_tag ({}, :id => 'mail_options_form')%>
6 <% for action in @actions %>
7 <%= check_box_tag "action_ids[]", action.id, action.mail_enabled? %>
8 <%= action.descr %><br />
9 <% end %>
10 <br />
11 <p>
12 <a href="javascript:checkAll('mail_options_form', true)"><%=_('Check all')%></a> |
13 <a href="javascript:checkAll('mail_options_form', false)"><%=_('Uncheck all')%></a>
14 </p>
15 <%= submit_tag _('Save') %>
16 <%= end_form_tag %> No newline at end of file
@@ -0,0 +1,35
1 <h2><%=_('Projects')%></h2>
2
3 <table width="100%" cellspacing="1" cellpadding="2" class="listTableContent">
4 <tr class="ListHead">
5 <%= sort_header_tag('projects.name', :caption => _('Project')) %>
6 <th><%=_('Description')%></th>
7 <th><%=_('Public')%></th>
8 <%= sort_header_tag('projects.created_on', :caption => _('Created on')) %>
9 <th></th>
10 </tr>
11
12 <% odd_or_even = 1
13 for project in @projects
14 odd_or_even = 1 - odd_or_even %>
15 <tr class="ListLine<%= odd_or_even %>">
16 <td><%= link_to project.name, :controller => 'projects', :action => 'settings', :id => project %>
17 <td><%= project.descr %>
18 <td align="center"><%= image_tag 'true' if project.public? %>
19 <td align="center"><%= format_date(project.created_on) %>
20 <td align="center">
21 <%= start_form_tag({:controller => 'projects', :action => 'destroy', :id => project}) %>
22 <%= submit_tag _('Delete'), :class => "button-small" %>
23 <%= end_form_tag %>
24 </td>
25 </tr>
26 <% end %>
27 </table>
28
29 <%= link_to ('&#171; ' + _('Previous')), { :page => @project_pages.current.previous } if @project_pages.current.previous %>
30 <%= pagination_links(@project_pages) %>
31 <%= link_to (_('Next') + ' &#187;'), { :page => @project_pages.current.next } if @project_pages.current.next %>
32
33 <br />
34
35 <%= link_to ('&#187; ' + _('New project')), :controller => 'projects', :action => 'add' %> No newline at end of file
@@ -0,0 +1,26
1 <%= error_messages_for 'custom_field' %>
2
3 <!--[form:custom_field]-->
4 <p><label for="custom_field_name"><%=_('Name')%></label><br/>
5 <%= text_field 'custom_field', 'name' %></p>
6
7 <p><label for="custom_field_typ"><%=_('Type')%></label><br/>
8 <%= select("custom_field", "typ", CustomField::TYPES) %></p>
9
10 <p><%= check_box 'custom_field', 'is_required' %>
11 <label for="custom_field_is_required"><%=_('Required')%></label></p>
12
13 <p><%= check_box 'custom_field', 'is_for_all' %>
14 <label for="custom_field_is_for_all"><%=_('For all projects')%></label></p>
15
16 <p><label for="custom_field_min_length"><%=_('Min - max length')%></label> (<%=_('0 means no restriction')%>)<br/>
17 <%= text_field 'custom_field', 'min_length', :size => 5 %> -
18 <%= text_field 'custom_field', 'max_length', :size => 5 %></p>
19
20 <p><label for="custom_field_regexp"><%=_('Regular expression pattern')%></label> (eg. ^[A-Z0-9]+$)<br/>
21 <%= text_field 'custom_field', 'regexp', :size => 50 %></p>
22
23 <p><label for="custom_field_possible_values"><%=_('Possible values')%></label> (separator: |)<br/>
24 <%= text_area 'custom_field', 'possible_values', :rows => 5, :cols => 60 %></p>
25 <!--[eoform:custom_field]-->
26
@@ -0,0 +1,6
1 <h2><%=_('Custom field')%></h2>
2
3 <%= start_form_tag :action => 'edit', :id => @custom_field %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Save') %>
6 <%= end_form_tag %>
@@ -0,0 +1,32
1 <h2><%=_('Custom fields')%></h2>
2
3 <table border="0" cellspacing="1" cellpadding="2" class="listTableContent">
4 <tr class="ListHead">
5 <th><%=_('Name')%></th>
6 <th><%=_('Type')%></th>
7 <th><%=_('Required')%></th>
8 <th><%=_('For all projects')%></th>
9 <th><%=_('Used by')%></th>
10 <th></th>
11 </tr>
12 <% for custom_field in @custom_fields %>
13 <tr style="background-color:#CEE1ED">
14 <td><%= link_to custom_field.name, :action => 'edit', :id => custom_field %></td>
15 <td align="center"><%= CustomField::TYPES[custom_field.typ][0] %></td>
16 <td align="center"><%= image_tag 'true' if custom_field.is_required? %></td>
17 <td align="center"><%= image_tag 'true' if custom_field.is_for_all? %></td>
18 <td align="center"><%= custom_field.projects.count.to_s + ' ' + _('Project') + '(s)' unless custom_field.is_for_all? %></td>
19 <td align="center">
20 <%= start_form_tag :action => 'destroy', :id => custom_field %>
21 <%= submit_tag _('Delete'), :class => "button-small" %>
22 <%= end_form_tag %> </td>
23 </tr>
24 <% end %>
25 </table>
26
27 <%= link_to ('&#171; ' + _('Previous')), { :page => @custom_field_pages.current.previous } if @custom_field_pages.current.previous %>
28 <%= link_to (_('Next') + ' &#187;'), { :page => @custom_field_pages.current.next } if @custom_field_pages.current.next %>
29
30 <br />
31
32 <%= link_to ('&#187; ' + _('New custom field')), :action => 'new' %>
@@ -0,0 +1,7
1 <h2><%=_('New custom field')%></h2>
2
3 <%= start_form_tag :action => 'new' %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Create') %>
6 <%= end_form_tag %>
7
@@ -0,0 +1,15
1 <%= error_messages_for 'document' %>
2
3 <!--[form:document]-->
4 <p><label for="document_category_id"><%=_('Category')%></label><br />
5 <select name="document[category_id]">
6 <%= options_from_collection_for_select @categories, "id", "name",@document.category_id %>
7 </select></p>
8
9 <p><label for="document_title"><%=_('Title')%> <span class="required">*</span></label><br />
10 <%= text_field 'document', 'title', :size => 60 %></p>
11
12 <p><label for="document_descr"><%=_('Description')%> <span class="required">*</span></label><br />
13 <%= text_area 'document', 'descr', :cols => 60, :rows => 5 %></p>
14 <!--[eoform:document]-->
15
@@ -0,0 +1,8
1 <h2><%=_('Document')%></h2>
2
3 <%= start_form_tag :action => 'edit', :id => @document %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Save') %>
6 <%= end_form_tag %>
7
8
@@ -0,0 +1,45
1 <h2><%= @document.title %></h2>
2
3 <strong><%=_('Description')%>:</strong> <%= @document.descr %><br />
4 <strong><%=_('Category')%>:</strong> <%= @document.category.name %><br />
5 <br />
6
7 <% if authorize_for('documents', 'edit') %>
8 <%= start_form_tag ({ :controller => 'documents', :action => 'edit', :id => @document }, :method => 'get' ) %>
9 <%= submit_tag _('Edit') %>
10 <%= end_form_tag %>
11 <% end %>
12
13 <% if authorize_for('documents', 'destroy') %>
14 <%= start_form_tag ({ :controller => 'documents', :action => 'destroy', :id => @document } ) %>
15 <%= submit_tag _('Delete') %>
16 <%= end_form_tag %>
17 <% end %>
18
19 <br /><br />
20
21 <table border="0" cellspacing="1" cellpadding="2" width="100%">
22 <% for attachment in @document.attachments %>
23 <tr style="background-color:#CEE1ED">
24 <td><%= link_to attachment.filename, :action => 'download', :id => @document, :attachment_id => attachment %></td>
25 <td align="center"><%= format_date(attachment.created_on) %></td>
26 <td align="center"><%= attachment.author.display_name %></td>
27 <td><%= human_size(attachment.size) %><br /><%= attachment.downloads %> <%=_('download')%>(s)</td>
28 <% if authorize_for('documents', 'destroy_attachment') %>
29 <td align="center">
30 <%= start_form_tag :action => 'destroy_attachment', :id => @document, :attachment_id => attachment %>
31 <%= submit_tag _('Delete'), :class => "button-small" %>
32 <%= end_form_tag %>
33 </tr>
34 <% end %>
35 <% end %>
36 </table>
37 <br />
38
39 <% if authorize_for('documents', 'add_attachment') %>
40 <%= start_form_tag ({ :controller => 'documents', :action => 'add_attachment', :id => @document }, :multipart => true) %>
41 <%=_('Add file')%><br /><%= file_field 'attachment', 'file' %>
42 <%= submit_tag _('Add') %>
43 <%= end_form_tag %>
44 <% end %>
45
@@ -0,0 +1,9
1 <%= error_messages_for 'enumeration' %>
2
3 <!--[form:optvalue]-->
4 <%= hidden_field 'enumeration', 'opt' %>
5
6 <p><label for="enumeration_name"><%=_('Name')%></label><br/>
7 <%= text_field 'enumeration', 'name' %></p>
8 <!--[eoform:optvalue]-->
9
@@ -0,0 +1,10
1 <h2><%=_('Enumerations')%></h2>
2
3 <%= start_form_tag :action => 'update', :id => @enumeration %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Save') %>
6 <%= end_form_tag %>
7
8 <%= start_form_tag :action => 'destroy', :id => @enumeration %>
9 <%= submit_tag _('Delete') %>
10 <%= end_form_tag %> No newline at end of file
@@ -0,0 +1,22
1 <h2><%=_('Enumerations')%></h2>
2
3 <% for option in Enumeration::OPTIONS %>
4
5 <% if @params[:opt]==option[1] %>
6
7 <p><%= image_tag 'dir_open' %> <b><%=_ option[0] %></b></p>
8 <ul>
9 <% for value in Enumeration::find(:all, :conditions => [ "opt = ?", option[1]]) %>
10 <li><%= link_to value.name, :action => 'edit', :id => value %></li>
11 <% end %>
12 </ul>
13 <ul>
14 <li><%= link_to ('&#187; ' + _('New')), :action => 'new', :opt => option[1] %></li>
15 </ul>
16
17 <% else %>
18 <p><%= image_tag 'dir' %> <%= link_to _(option[0]), :opt => option[1] %></p>
19 <% end %>
20
21 <% end %>
22
@@ -0,0 +1,6
1 <h2><%=_('New enumeration')%></h2>
2
3 <%= start_form_tag :action => 'create' %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Create') %>
6 <%= end_form_tag %>
@@ -0,0 +1,7
1 <%= error_messages_for 'issue_category' %>
2
3 <!--[form:issue_category]-->
4 <p><label for="issue_category_name"><%=_('Name')%></label><br/>
5 <%= text_field 'issue_category', 'name' %></p>
6 <!--[eoform:issue_category]-->
7
@@ -0,0 +1,6
1 <h2>Editing issue category</h2>
2
3 <%= start_form_tag :action => 'edit', :id => @category %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Save') %>
6 <%= end_form_tag %>
@@ -0,0 +1,17
1 <%= error_messages_for 'issue_status' %>
2
3 <!--[form:issue_status]-->
4 <p><label for="issue_status_name"><%=_('Name')%></label><br/>
5 <%= text_field 'issue_status', 'name' %></p>
6
7 <p><%= check_box 'issue_status', 'is_closed' %>
8 <label for="issue_status_is_closed"><%=_('Issue closed')%></label></p>
9
10 <p><%= check_box 'issue_status', 'is_default' %>
11 <label for="issue_status_is_default"><%=_('Default status')%></label></p>
12
13 <p><label for="issue_status_html_color"><%=_('Color')%></label>
14 #<%= text_field 'issue_status', 'html_color', :size => 6 %></p>
15
16 <!--[eoform:issue_status]-->
17
@@ -0,0 +1,6
1 <h2><%=_('Issue status')%></h2>
2
3 <%= start_form_tag :action => 'update', :id => @issue_status %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Save') %>
6 <%= end_form_tag %>
@@ -0,0 +1,30
1 <h2><%=_('Issue statuses')%></h2>
2
3 <table border="0" cellspacing="1" cellpadding="2" class="listTableContent">
4 <tr class="ListHead">
5 <th><%=_('Status')%></th>
6 <th><%=_('Default status')%></th>
7 <th><%=_('Issue closed')%></th>
8 <th><%=_('Color')%></th>
9 <th></th>
10 </tr>
11
12 <% for status in @issue_statuses %>
13 <tr style="background-color:#CEE1ED">
14 <td><%= link_to status.name, :action => 'edit', :id => status %></td>
15 <td align="center"><%= image_tag 'true' if status.is_default %></td>
16 <td align="center"><%= image_tag 'true' if status.is_closed %></td>
17 <td bgcolor="#<%= status.html_color %>">&nbsp</td>
18 <td align="center">
19 <%= start_form_tag :action => 'destroy', :id => status %>
20 <%= submit_tag _('Delete'), :class => "button-small" %>
21 <%= end_form_tag %>
22 </td>
23 </tr>
24 <% end %>
25 </table>
26
27 <%= pagination_links_full @issue_status_pages %>
28 <br />
29
30 <%= link_to '&#187; ' + _('New issue status'), :action => 'new' %>
@@ -0,0 +1,6
1 <h2><%=_('New issue status')%></h2>
2
3 <%= start_form_tag :action => 'create' %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Create') %>
6 <%= end_form_tag %>
@@ -0,0 +1,28
1 <% if issues.length > 0 %>
2 <table cellspacing="0" cellpadding="1" width="100%" border="0" class="listTable">
3 <tr><td>
4 <table width="100%" border="0" cellspacing="1" cellpadding="2" class="listTableContent">
5 <tr class="ListHead">
6 <th>#</th>
7 <th><%=_('Tracker')%></th>
8 <th><%=_('Subject')%></th>
9 </tr>
10 <% for issue in issues %>
11 <tr bgcolor="#<%= issue.status.html_color %>">
12 <td align="center">
13 <%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %><br />
14 </td>
15 <td><p class="small"><%= issue.project.name %> - <%= issue.tracker.name %><br />
16 <%= issue.status.name %> - <%= format_time(issue.updated_on) %></p></td>
17 <td>
18 <p class="small"><%= link_to issue.subject, :controller => 'issues', :action => 'show', :id => issue %></p>
19 </td>
20 </tr>
21 <% end %>
22 </table>
23 </td>
24 </tr>
25 </table>
26 <% else %>
27 <%=_('No issue')%>
28 <% end %> No newline at end of file
@@ -0,0 +1,29
1 <h2><%=_('Issue')%> #<%= @issue.id %>: <%= @issue.subject %></h2>
2
3 <%= error_messages_for 'history' %>
4 <%= start_form_tag :action => 'change_status', :id => @issue %>
5
6 <%= hidden_field_tag 'confirm', 1 %>
7 <%= hidden_field 'history', 'status_id' %>
8
9 <p><%=_('New status')%>: <b><%= @history.status.name %></b></p>
10
11 <div>
12 <p><label for="issue_assigned_to_id"><%=_('Assigned to')%></label><br/>
13 <select name="issue[assigned_to_id]">
14 <option value=""></option>
15 <%= options_from_collection_for_select @assignable_to, "id", "display_name", @issue.assigned_to_id %></p>
16 </select></p>
17 </div>
18
19 <p><label for="issue_fixed_version"><%=_('Fixed in version')%></label><br/>
20 <select name="issue[fixed_version_id]">
21 <option value="">--none--</option>
22 <%= options_from_collection_for_select @issue.project.versions, "id", "name", @issue.fixed_version_id %>
23 </select></p>
24
25 <p><label for="history_notes"><%=_('Notes')%></label><br />
26 <%= text_area 'history', 'notes', :cols => 60, :rows => 10 %></p>
27
28 <%= submit_tag _('Save') %>
29 <%= end_form_tag %>
@@ -0,0 +1,62
1 <h2><%=_('Issue')%> #<%= @issue.id %></h2>
2
3 <%= error_messages_for 'issue' %>
4 <%= start_form_tag :action => 'edit', :id => @issue %>
5
6 <!--[form:issue]-->
7 <p><%=_('Status')%>: <b><%= @issue.status.name %></b></p>
8
9 <div style="float:left;margin-right:10px;">
10 <p><label for="issue_tracker_id"><%=_('Tracker')%> <span class="required">*</span></label><br/>
11 <select name="issue[tracker_id]">
12 <%= options_from_collection_for_select @trackers, "id", "name", @issue.tracker_id %></p>
13 </select></p>
14 </div>
15
16 <div style="float:left;margin-right:10px;">
17 <p><label for="issue_priority_id"><%=_('Priority')%> <span class="required">*</span></label><br/>
18 <select name="issue[priority_id]">
19 <%= options_from_collection_for_select @priorities, "id", "name", @issue.priority_id %></p>
20 </select></p>
21 </div>
22
23 <div style="float:left;margin-right:10px;">
24 <p><label for="issue_assigned_to_id"><%=_('Assigned to')%></label><br/>
25 <select name="issue[assigned_to_id]">
26 <option value=""></option>
27 <%= options_from_collection_for_select @issue.project.members, "user_id", "name", @issue.assigned_to_id %></p>
28 </select></p>
29 </div>
30
31 <div>
32 <p><label for="issue_category_id"><%=_('Category')%></label><br/>
33 <select name="issue[category_id]">
34 <option value=""></option>
35 <%= options_from_collection_for_select @project.issue_categories, "id", "name", @issue.category_id %></p>
36 </select></p>
37 </div>
38
39 <p><label for="issue_subject"><%=_('Subject')%></label><span class="required">*</span><br/>
40 <%= text_field 'issue', 'subject', :size => 60 %></p>
41
42 <p><label for="issue_descr"><%=_('Description')%></label><span class="required">*</span><br/>
43 <%= text_area 'issue', 'descr', :cols => 60, :rows => 10 %></p>
44
45
46 <% for custom_value in @custom_values %>
47 <p><%= content_tag "label", custom_value.custom_field.name %>
48 <% if custom_value.custom_field.is_required? %><span class="required">*</span><% end %>
49 <br />
50 <%= custom_field_tag custom_value %></p>
51 <% end %>
52
53
54 <p><label for="issue_fixed_version"><%=_('Fixed in version')%></label><br/>
55 <select name="issue[fixed_version_id]">
56 <option value="">--none--</option>
57 <%= options_from_collection_for_select @project.versions, "id", "name", @issue.fixed_version_id %>
58 </select></p>
59 <!--[eoform:issue]-->
60
61 <center><%= submit_tag _('Save') %></center>
62 <%= end_form_tag %>
@@ -0,0 +1,90
1
2 <h2><%=_('Issue')%> #<%= @issue.id %> - <%= @issue.subject %></h2>
3
4 <div class="box">
5 <p><b><%=_('Tracker')%>:</b> <%= @issue.tracker.name %></p>
6 <p><b><%=_('Priority')%>:</b> <%= @issue.priority.name %></p>
7 <p><b><%=_('Category')%>:</b> <%= @issue.category.name unless @issue.category_id.nil? %></p>
8 <p><b><%=_('Status')%>:</b> <%= @issue.status.name %></p>
9 <p><b><%=_('Author')%>:</b> <%= @issue.author.display_name %></p>
10 <p><b><%=_('Assigned to')%>:</b> <%= @issue.assigned_to.display_name unless @issue.assigned_to.nil? %></p>
11
12 <p><b><%=_('Subject')%>:</b> <%= @issue.subject %></p>
13 <p><b><%=_('Description')%>:</b> <%= @issue.descr %></p>
14 <p><b><%=_('Created on')%>:</b> <%= format_date(@issue.created_on) %></p>
15
16 <% if authorize_for('issues', 'edit') %>
17 <%= start_form_tag ({:controller => 'issues', :action => 'edit', :id => @issue}, :method => "get" ) %>
18 <%= submit_tag _('Edit') %>
19 <%= end_form_tag %>
20 &nbsp;&nbsp;
21 <% end %>
22
23 <% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
24 <%= start_form_tag ({:controller => 'issues', :action => 'change_status', :id => @issue}) %>
25 <label for="history_status_id"><%=_('Change status')%>:</label>
26 <select name="history[status_id]">
27 <%= options_from_collection_for_select @status_options, "id", "name" %>
28 </select>
29 <%= submit_tag _ "Update..." %>
30 <%= end_form_tag %>
31 &nbsp;&nbsp;
32 <% end %>
33
34 <% if authorize_for('issues', 'destroy') %>
35 <%= start_form_tag ({:controller => 'issues', :action => 'destroy', :id => @issue} ) %>
36 <%= submit_tag _ "Delete" %>
37 <%= end_form_tag %>
38 &nbsp;&nbsp;
39 <% end %>
40
41 </div>
42
43
44 <div class="splitcontentleft">
45 <div class="box">
46 <h3><%=_('History')%></h3>
47 <table width="100%">
48 <% for history in @issue.histories.find(:all, :include => :author) %>
49 <tr>
50 <td><%= format_date(history.created_on) %></td>
51 <td><%= history.author.display_name %></td>
52 <td><b><%= history.status.name %></b></td>
53 </tr>
54 <% if history.notes? %>
55 <tr><td colspan=3><div class="notes"><%= history.notes %></td></tr>
56 <% end %>
57 <% end %>
58 </table>
59 </div>
60 </div>
61
62 <div class="splitcontentright">
63 <div class="box">
64 <h3><%=_('Attachments')%></h3>
65 <table width="100%">
66 <% for attachment in @issue.attachments %>
67 <tr>
68 <td><%= link_to attachment.filename, :action => 'download', :id => @issue, :attachment_id => attachment %> (<%= human_size(attachment.size) %>)</td>
69 <td><%= format_date(attachment.created_on) %></td>
70 <td><%= attachment.author.display_name %></td>
71 <% if authorize_for('issues', 'destroy_attachment') %>
72 <td>
73 <%= start_form_tag :action => 'destroy_attachment', :id => @issue, :attachment_id => attachment %>
74 <%= submit_tag _('Delete'), :class => "button-small" %>
75 <%= end_form_tag %>
76 </td>
77 <% end %>
78 </tr>
79 <% end %>
80 </table>
81 <br />
82 <% if authorize_for('issues', 'add_attachment') %>
83 <%= start_form_tag ({ :controller => 'issues', :action => 'add_attachment', :id => @issue }, :multipart => true) %>
84 <%=_('Add file')%>: <%= file_field 'attachment', 'file' %>
85 <%= submit_tag _('Add') %>
86 <%= end_form_tag %>
87 <% end %>
88 </div>
89 </div>
90
@@ -0,0 +1,89
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3 <head>
4 <title>redMine</title>
5 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
6 <meta name="description" content="redMine" />
7 <meta name="keywords" content="issue,bug,tracker" />
8 <%= stylesheet_link_tag "application" %>
9 <%= stylesheet_link_tag "rails" %>
10 <%= javascript_include_tag :defaults %>
11 </head>
12
13 <body>
14 <div id="container" >
15
16 <div id="header">
17 <div style="float: left;">
18 <h1><%= RDM_APP_NAME %></h1>
19 <h2>Project management</h2>
20 </div>
21 <div style="float: right; padding-right: 1em; padding-top: 0.2em;">
22 <% unless session[:user].nil? %><small><%=_('Logged as')%> <b><%= session[:user].login %></b></small><% end %>
23 </div>
24 </div>
25
26 <div id="navigation">
27 <ul>
28 <li class="selected"><%= link_to _('Home'), { :controller => '' }, :class => "picHome" %></li>
29 <li><%= link_to _('My page'), { :controller => 'account', :action => 'my_page'}, :class => "picUserPage" %></li>
30 <li><%= link_to _('Projects'), { :controller => 'projects' }, :class => "picProject" %></li>
31
32 <% unless session[:user].nil? %>
33 <li><%= link_to _('My account'), { :controller => 'account', :action => 'my_account' }, :class => "picUser" %></li>
34 <% end %>
35
36 <% if admin_loggedin? %>
37 <li><%= link_to _('Administration'), { :controller => 'admin' }, :class => "picAdmin" %></li>
38 <% end %>
39
40 <li class="right"><%= link_to _('Help'), { :controller => 'help', :ctrl => @params[:controller], :page => @params[:action] }, :target => "new", :class => "picHelp" %></li>
41 <% if session[:user].nil? %>
42 <li class="right"><%= link_to _('Log in'), { :controller => 'account', :action => 'login' }, :class => "picUser" %></li>
43 <% else %>
44 <li class="right"><%= link_to _('Logout'), { :controller => 'account', :action => 'logout' }, :class => "picUser" %></li>
45 <% end %>
46 </ul>
47
48 </div>
49
50 <div id="subcontent">
51
52 <% unless @project.nil? || @project.id.nil? %>
53 <h2><%= @project.name %></h2>
54 <ul class="menublock">
55 <li><%= link_to _('Overview'), :controller => 'projects', :action => 'show', :id => @project %></li>
56 <li><%= link_to _('Issues'), :controller => 'projects', :action => 'list_issues', :id => @project %></li>
57 <li><%= link_to _('Reports'), :controller => 'reports', :action => 'issue_report', :id => @project %></li>
58 <li><%= link_to _('News'), :controller => 'projects', :action => 'list_news', :id => @project %></li>
59 <li><%= link_to _('Change log'), :controller => 'projects', :action => 'changelog', :id => @project %></li>
60 <li><%= link_to _('Documents'), :controller => 'projects', :action => 'list_documents', :id => @project %></li>
61 <li><%= link_to _('Members'), :controller => 'projects', :action => 'list_members', :id => @project %></li>
62 <li><%= link_to _('Files'), :controller => 'projects', :action => 'list_files', :id => @project %></li>
63 <li><%= link_to_if_authorized _('Settings'), :controller => 'projects', :action => 'settings', :id => @project %></li>
64 </ul>
65 <% end %>
66
67 <% unless session[:user].nil? %>
68 <h2><%=_('My projects') %></h2>
69 <ul class="menublock">
70 <% for membership in session[:user].memberships %>
71 <li><%= link_to membership.project.name, :controller => 'projects', :action => 'show', :id => membership.project %></li>
72 <% end %>
73 </ul>
74 <% end %>
75
76 </div>
77
78 <div id="content">
79 <% if flash[:notice] %><p style="color: green"><%= flash[:notice] %></p><% end %>
80 <%= @content_for_layout %>
81 </div>
82
83 <div id="footer">
84 <p><a href="http://redmine.sourceforge.net/" target="_new"><%= RDM_APP_NAME %></a> <%= RDM_APP_VERSION %></p>
85 </div>
86
87 </div>
88 </body>
89 </html> No newline at end of file
@@ -0,0 +1,6
1 <%=_('Issue')%> #<%= issue.id %> - <%= issue.subject %>
2 <%=_('Author')%>: <%= issue.author.display_name %>
3
4 <%= issue.descr %>
5
6 http://<%= RDM_HOST_NAME %>/issues/show/<%= issue.id %> No newline at end of file
@@ -0,0 +1,3
1 Issue #<%= @issue.id %> has been reported.
2 ----------------------------------------
3 <%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %> No newline at end of file
@@ -0,0 +1,3
1 Issue #<%= @issue.id %> has been updated to "<%= @issue.status.name %>" status.
2 ----------------------------------------
3 <%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %> No newline at end of file
@@ -0,0 +1,13
1 <%= error_messages_for 'news' %>
2
3 <!--[form:news]-->
4 <p><label for="news_title"><%=_('Title')%></label> <span class="required">*</span><br/>
5 <%= text_field 'news', 'title', :size => 60 %></p>
6
7 <p><label for="news_shortdescr"><%=_('Summary')%> <span class="required">*</span></label><br/>
8 <%= text_area 'news', 'shortdescr', :cols => 60, :rows => 2 %></p>
9
10 <p><label for="news_descr"><%=_('Description')%> <span class="required">*</span></label><br/>
11 <%= text_area 'news', 'descr', :cols => 60, :rows => 10 %></p>
12 <!--[eoform:news]-->
13
@@ -0,0 +1,6
1 <h2><%=_('News')%></h2>
2
3 <%= start_form_tag :action => 'edit', :id => @news %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Save') %>
6 <%= end_form_tag %>
@@ -0,0 +1,10
1 <h2><%= @news.title %></h2>
2
3 <p>
4 <b><%=_('Summary')%></b>: <%= @news.shortdescr %><br />
5 <b><%=_('By')%></b>: <%= @news.author.display_name %><br />
6 <b><%=_('Date')%></b>: <%= format_time(@news.created_on) %>
7 </p>
8
9 <%= @news.descr %>
10
@@ -0,0 +1,28
1 <%= error_messages_for 'project' %>
2
3 <!--[form:project]-->
4 <p><label for="project_name"><%=_('Name')%> <span class="required">*</span></label><br/>
5 <%= text_field 'project', 'name' %></p>
6
7 <p><label for="project_descr"><%=_('Description')%> <span class="required">*</span></label><br/>
8 <%= text_field 'project', 'descr', :size => 60 %></p>
9
10 <p><label for="project_homepage"><%=_('Homepage')%></label><br/>
11 <%= text_field 'project', 'homepage', :size => 40 %></p>
12
13 <p><%= check_box 'project', 'public' %>
14 <label for="project_public"><%=_('Public')%></label></p>
15
16 <fieldset><legend><%=_('Custom fields')%></legend>
17 <% for custom_field in @custom_fields %>
18 <input type="checkbox"
19
20 name="custom_field_ids[]"
21 value="<%= custom_field.id %>"
22 <%if @project.custom_fields.include? custom_field%>checked="checked"<%end%>
23 > <%= custom_field.name %>
24
25 <% end %></fieldset>
26 <br />
27
28 <!--[eoform:project]-->
@@ -0,0 +1,7
1 <h2><%=_('New project')%></h2>
2
3 <%= start_form_tag :action => 'add' %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Create') %>
6 <%= end_form_tag %>
7
@@ -0,0 +1,26
1 <h2><%=_('New document')%></h2>
2
3 <%= error_messages_for 'document' %>
4 <%= start_form_tag( { :action => 'add_document', :id => @project }, :multipart => true) %>
5
6 <!--[form:document]-->
7 <p><label for="document_category_id"><%=_('Category')%></label><br />
8 <select name="document[category_id]">
9 <%= options_from_collection_for_select @categories, "id", "name",@document.category_id %>
10 </select></p>
11
12 <p><label for="document_title"><%=_('Title')%> <span class="required">*</span></label><br />
13 <%= text_field 'document', 'title', :size => 60 %></p>
14
15 <p><label for="document_descr"><%=_('Description')%> <span class="required">*</span></label><br />
16 <%= text_area 'document', 'descr', :cols => 60, :rows => 5 %></p>
17
18 <p><label for="attachment_file"><%=_('File')%></label><br/>
19 <%= file_field 'attachment', 'file' %></p>
20
21 <!--[eoform:document]-->
22
23 <%= submit_tag _('Create') %>
24 <%= end_form_tag %>
25
26
@@ -0,0 +1,13
1 <h2><%=_('New file')%></h2>
2
3 <%= start_form_tag ({ :action => 'add_file', :project => @project }, :multipart => true) %>
4
5 <p><label for="version_id"><%=_('Version')%></label><br />
6 <select name="version_id">
7 <%= options_from_collection_for_select @versions, "id", "name" %>
8 </select></p>
9
10 <p><b><%=_('File')%><b><br /><%= file_field 'attachment', 'file' %></p>
11 <br/>
12 <%= submit_tag _('Add') %>
13 <%= end_form_tag %> No newline at end of file
@@ -0,0 +1,62
1 <h2><%=_('New issue')%></h2>
2
3 <%= start_form_tag( { :action => 'add_issue', :id => @project }, :multipart => true) %>
4 <%= error_messages_for 'issue' %>
5
6 <!--[form:issue]-->
7
8 <div style="float:left;margin-right:10px;">
9 <p><label for="issue_tracker_id"><%=_('Tracker')%> <span class="required">*</span></label><br/>
10 <select name="issue[tracker_id]">
11 <%= options_from_collection_for_select @trackers, "id", "name", @issue.tracker_id %></p>
12 </select></p>
13 </div>
14
15 <div style="float:left;margin-right:10px;">
16 <p><label for="issue_priority_id"><%=_('Priority')%> <span class="required">*</span></label><br/>
17 <select name="issue[priority_id]">
18 <%= options_from_collection_for_select @priorities, "id", "name", @issue.priority_id %></p>
19 </select></p>
20 </div>
21
22 <div style="float:left;margin-right:10px;">
23 <p><label for="issue_assigned_to_id"><%=_('Assigned to')%></label><br/>
24 <select name="issue[assigned_to_id]">
25 <option value=""></option>
26 <%= options_from_collection_for_select @issue.project.members, "user_id", "name", @issue.assigned_to_id %></p>
27 </select></p>
28 </div>
29
30 <div>
31 <p><label for="issue_category_id"><%=_('Category')%></label><br/>
32 <select name="issue[category_id]">
33 <option value=""></option>
34 <%= options_from_collection_for_select @project.issue_categories, "id", "name", @issue.category_id %></p>
35 </select></p>
36 </div>
37
38 <p><label for="issue_subject"><%=_('Subject')%> <span class="required">*</span></label><br/>
39 <%= text_field 'issue', 'subject', :size => 80 %></p>
40
41 <p><label for="issue_descr"><%=_('Description')%> <span class="required">*</span></label><br/>
42 <%= text_area 'issue', 'descr', :cols => 60, :rows => 10 %></p>
43
44
45 <% for custom_value in @custom_values %>
46 <div style="float:left;margin-right:10px;">
47 <p><%= content_tag "label", custom_value.custom_field.name %>
48 <% if custom_value.custom_field.is_required? %><span class="required">*</span><% end %>
49 <br />
50 <%= custom_field_tag custom_value %></p>
51 </div>
52 <% end %>
53
54 <div style="clear: both;">
55 <p><label for="attachment_file"><%=_('Attachment')%></label><br/>
56 <%= file_field 'attachment', 'file' %></p>
57 </div>
58
59 <!--[eoform:issue]-->
60
61 <%= submit_tag _('Create') %>
62 <%= end_form_tag %> No newline at end of file
@@ -0,0 +1,7
1 <h2><%=('Add news')%></h2>
2
3 <%= start_form_tag :action => 'add_news', :id => @project %>
4 <%= render :partial => 'news/form' %>
5 <%= submit_tag _('Create') %>
6 <%= end_form_tag %>
7
@@ -0,0 +1,7
1 <h2><%=_('New version')%></h2>
2
3 <%= start_form_tag :action => 'add_version', :id => @project %>
4 <%= render :partial => 'versions/form' %>
5 <%= submit_tag _('Create') %>
6 <%= end_form_tag %>
7
@@ -0,0 +1,12
1 <h2><%=_('Change log')%></h2>
2
3 <% fixed_issues = @fixed_issues.group_by {|i| i.fixed_version } %>
4 <% fixed_issues.each do |version, issues| %>
5 <p><strong><%= version.name %></strong> - <%= format_date(version.date) %><br />
6 <%=h version.descr %></p>
7 <ul>
8 <% issues.each do |i| %>
9 <li><%= link_to i.long_id, :controller => 'issues', :action => 'show', :id => i %> [<%= i.tracker.name %>]: <%= i.subject %></li>
10 <% end %>
11 </ul>
12 <% end %>
@@ -0,0 +1,12
1 <h2><%=_('Confirmation')%></h2>
2 <div class="box">
3 <center>
4 <p><%=_('Are you sure you want to delete project')%> <strong><%= @project.name %></strong> ?</p>
5 <p>
6 <%= start_form_tag({:controller => 'projects', :action => 'destroy', :id => @project}) %>
7 <%= hidden_field_tag "confirm", 1 %>
8 <%= submit_tag _('Delete') %>
9 <%= end_form_tag %>
10 </p>
11 </center>
12 </div> No newline at end of file
@@ -0,0 +1,22
1 <h2><%=_('Public projects')%></h2>
2
3 <table width="100%" cellspacing="1" cellpadding="2" class="listTableContent">
4 <tr class="ListHead">
5 <%= sort_header_tag('projects.name', :caption => _('Project')) %>
6 <th>Description</th>
7 <%= sort_header_tag('projects.created_on', :caption => _('Created on')) %>
8 </tr>
9
10 <% odd_or_even = 1
11 for project in @projects
12 odd_or_even = 1 - odd_or_even %>
13 <tr class="ListLine<%= odd_or_even %>">
14 <td><%= link_to project.name, :action => 'show', :id => project %>
15 <td><%= project.descr %>
16 <td align="center"><%= format_date(project.created_on) %>
17 </tr>
18 <% end %>
19 </table>
20
21 <%= pagination_links_full @project_pages %>
22 [ <%= @project_pages.current.first_item %> - <%= @project_pages.current.last_item %> / <%= @project_count %> ] No newline at end of file
@@ -0,0 +1,21
1 <h2><%=_('Documents')%></h2>
2
3 <% documents = @documents.group_by {|d| d.category } %>
4 <% documents.each do |category, docs| %>
5 <h3><%= category.name %></h3>
6 <ul>
7 <% docs.each do |d| %>
8 <li>
9 <b><%= link_to d.title, :controller => 'documents', :action => 'show', :id => d %></b>
10 <br />
11 <%=_('Desciption')%>: <%= d.descr %><br />
12 <%= format_time(d.created_on) %>
13 </li>
14
15 <% end %>
16 </ul>
17 <% end %>
18
19 <p>
20 <%= link_to_if_authorized '&#187; ' + _('New'), :controller => 'projects', :action => 'add_document', :id => @project %>
21 </p>
@@ -0,0 +1,47
1 <h2><%=_('Files')%></h2>
2
3 <% delete_allowed = authorize_for('versions', 'destroy_file') %>
4
5 <table border="0" cellspacing="1" cellpadding="2" class="listTableContent">
6 <tr class="ListHead">
7 <th><%=_('Version')%></th>
8 <th><%=_('File')%></th>
9 <th><%=_('Date')%></th>
10 <th><%=_('Size')%></th>
11 <th>D/L</th>
12 <th>MD5</th>
13 <% if delete_allowed %><th></th><% end %>
14 </tr>
15
16 <% for version in @versions %>
17 <tr>
18 <td colspan="7"><%= image_tag 'package' %> <b><%= version.name %></b></td>
19 </tr>
20 <% odd_or_even = 1
21 for file in version.attachments
22 odd_or_even = 1 - odd_or_even %>
23 <tr class="ListLine<%= odd_or_even %>">
24 <td></td>
25 <td><%= link_to file.filename, :controller => 'versions', :action => 'download', :id => version, :attachment_id => file %></td>
26 <td align="center"><%= format_date(file.created_on) %></td>
27 <td align="center"><%= human_size(file.size) %></td>
28 <td align="center"><%= file.downloads %></td>
29 <td align="center"><small><%= file.digest %></small></td>
30 <% if delete_allowed %>
31 <td align="center">
32 <%= start_form_tag :controller => 'versions', :action => 'destroy_file', :id => version, :attachment_id => file %>
33 <%= submit_tag _('Delete'), :class => "button-small" %>
34 <%= end_form_tag %>
35 </td>
36 <% end %>
37 </tr>
38 <% end %>
39 <% end %>
40 </table>
41
42 <br />
43 <p>
44 <%= link_to_if_authorized '&#187; ' + _('New'), :controller => 'projects', :action => 'add_file', :id => @project %>
45 </p>
46
47
@@ -0,0 +1,56
1 <h2><%=_('Issues')%></h2>
2
3 <form method="post" class="noborder">
4 <table cellpadding=2>
5 <tr>
6 <td><%=_('Status')%>:<br /><%= search_filter_tag("issues.status_id") %></td>
7 <td><%=_('Tracker')%>:<br /><%= search_filter_tag("issues.tracker_id") %></td>
8 <td><%=_('Priority')%>:<br /><%= search_filter_tag("issues.priority_id") %></td>
9 <td><%=_('Category')%>:<br /><%= search_filter_tag("issues.category_id") %></td>
10 <td><%=_('Author')%>:<br /><%= search_filter_tag("issues.author_id") %></td>
11 <td valign="bottom">
12 <%= submit_tag _('Apply filter') %>
13 <%= end_form_tag %>
14
15 <%= start_form_tag %>
16 <%= submit_tag _('Reset') %>
17 <%= end_form_tag %>
18 </td>
19 </tr>
20 </table>
21
22 &nbsp;
23
24 <table border="0" cellspacing="1" cellpadding="2" class="listTableContent">
25
26 <tr class="ListHead">
27 <%= sort_header_tag('issues.id', :caption => '#') %>
28 <%= sort_header_tag('issue_statuses.name', :caption => _('Status')) %>
29 <%= sort_header_tag('issues.tracker_id', :caption => _('Tracker')) %>
30 <th><%=_('Subject')%></th>
31 <%= sort_header_tag('users.lastname', :caption => _('Author')) %>
32 <%= sort_header_tag('issues.created_on', :caption => _('Created on')) %>
33 <%= sort_header_tag('issues.updated_on', :caption => _('Last update')) %>
34 </tr>
35
36 <% for issue in @issues %>
37 <tr bgcolor="#<%= issue.status.html_color %>">
38 <td align="center"><%= link_to issue.long_id, :controller => 'issues', :action => 'show', :id => issue %></td>
39 <td align="center"><%= issue.status.name %></td>
40 <td align="center"><%= issue.tracker.name %></td>
41 <td><%= link_to issue.subject, :controller => 'issues', :action => 'show', :id => issue %></td>
42 <td align="center"><%= issue.author.display_name %></td>
43 <td align="center"><%= format_time(issue.created_on) %></td>
44 <td align="center"><%= format_time(issue.updated_on) %></td>
45 </tr>
46 <% end %>
47 </table>
48 <p>
49 <%= pagination_links_full @issue_pages %>
50 [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]
51 </p>
52
53
54 <p>
55 <%= link_to_if_authorized '&#187; ' + _('Report an issue'), :controller => 'projects', :action => 'add_issue', :id => @project %>
56 </p> No newline at end of file
@@ -0,0 +1,11
1 <h2><%=_('Project members')%></h2>
2
3 <% members = @members.group_by {|m| m.role } %>
4 <% members.each do |role, member| %>
5 <h3><%= role.name %></h3>
6 <ul>
7 <% member.each do |m| %>
8 <li><%= link_to m.user.display_name, :controller => 'account', :action => 'show', :id => m.user %> (<%= format_date m.created_on %>)</li>
9 <% end %>
10 </ul>
11 <% end %>
@@ -0,0 +1,17
1 <h2><%=_('News')%></h2>
2
3 <% for news in @news %>
4 <p>
5 <b><%= news.title %></b> <small>(<%= link_to_user news.author %> <%= format_time(news.created_on) %>)</small>
6 <%= link_to_if_authorized image_tag('edit_small'), :controller => 'news', :action => 'edit', :id => news %>
7 <%= link_to_if_authorized image_tag('delete'), { :controller => 'news', :action => 'destroy', :id => news }, :confirm => 'Are you sure?' %>
8 <br />
9 <%= news.shortdescr %>
10 <small>[<%= link_to _('Read...'), :controller => 'news', :action => 'show', :id => news %>]</small>
11 </p>
12 <% end %>
13
14 <%= pagination_links_full @news_pages %>
15 <p>
16 <%= link_to_if_authorized '&#187; ' + _('Add'), :controller => 'projects', :action => 'add_news', :id => @project %>
17 </p>
@@ -0,0 +1,105
1 <h2><%=_('Settings')%></h2>
2
3 <div class="box">
4 <%= start_form_tag :action => 'edit', :id => @project %>
5 <%= render :partial => 'form' %>
6 <center><%= submit_tag _('Save') %></center>
7 <%= end_form_tag %>
8 </div>
9
10 <div class="box">
11 <h3><%=_('Members')%></h3>
12 <%= error_messages_for 'member' %>
13 <table>
14 <% for member in @project.members.find(:all, :include => :user) %>
15 <% unless member.new_record? %>
16 <tr>
17 <td><%= member.user.display_name %></td>
18 <td>
19 <%= start_form_tag :controller => 'members', :action => 'edit', :id => member %>
20 <select name="member[role_id]">
21 <%= options_from_collection_for_select @roles, "id", "name", member.role_id %>
22 </select>
23 </td>
24 <td>
25 <%= submit_tag _('Save'), :class => "button-small" %>
26 <%= end_form_tag %>
27 </td>
28 <td>
29 <%= start_form_tag :controller => 'members', :action => 'destroy', :id => member %>
30 <%= submit_tag _('Delete'), :class => "button-small" %>
31 <%= end_form_tag %>
32 </td>
33 </tr>
34 <% end %>
35 <% end %>
36 </table>
37 <hr />
38 <label><%=_('New member')%></label><br/>
39 <%= start_form_tag :controller => 'projects', :action => 'add_member', :id => @project %>
40 <select name="member[user_id]">
41 <%= options_from_collection_for_select @users, "id", "display_name", @member.user_id %>
42 </select>
43 <select name="member[role_id]">
44 <%= options_from_collection_for_select @roles, "id", "name", @member.role_id %>
45 </select>
46 <%= submit_tag _('Add') %>
47 <%= end_form_tag %>
48 </div>
49
50 <div class="box">
51 <h3><%=_('Versions')%></h3>
52
53 <table>
54 <% for version in @project.versions %>
55 <tr>
56 <td><%= link_to version.name, :controller => 'versions', :action => 'edit', :id => version %></td>
57 <td><%=h version.descr %></td>
58 <td>
59 <%= start_form_tag :controller => 'versions', :action => 'destroy', :id => version %>
60 <%= submit_tag _('Delete'), :class => "button-small" %>
61 <%= end_form_tag %>
62 </td>
63 </tr>
64 <% end %>
65 </table>
66 <hr />
67 <%= start_form_tag ({ :controller => 'projects', :action => 'add_version', :id => @project }, :method => 'get' ) %>
68 <%= submit_tag _('New version...') %>
69 <%= end_form_tag %>
70 </div>
71
72
73 <div class="box">
74 <h3><%=_('Issue categories')%></h3>
75 <table>
76 <% for @category in @project.issue_categories %>
77 <% unless @category.new_record? %>
78 <tr>
79 <td>
80 <%= start_form_tag :controller => 'issue_categories', :action => 'edit', :id => @category %>
81 <%= text_field 'category', 'name', :size => 25 %>
82 </td>
83 <td>
84 <%= submit_tag _('Save'), :class => "button-small" %>
85 <%= end_form_tag %>
86 </td>
87 <td>
88 <%= start_form_tag :controller => 'issue_categories', :action => 'destroy', :id => @category %>
89 <%= submit_tag _('Delete'), :class => "button-small" %>
90 <%= end_form_tag %>
91 </td>
92 </tr>
93 <% end %>
94 <% end %>
95 </table>
96 <hr />
97
98 <%= start_form_tag :action => 'add_issue_category', :id => @project %>
99 <%= error_messages_for 'issue_category' %>
100 <label for="issue_category_name"><%=_('New category')%></label><br/>
101 <%= text_field 'issue_category', 'name', :size => 25 %>
102 <%= submit_tag _('Create') %>
103 <%= end_form_tag %>
104
105 </div>
@@ -0,0 +1,53
1 <h2><%=_('Overview')%></h2>
2
3 <div class="splitcontentleft">
4 <%= @project.descr %>
5 <ul>
6 <li><%=_('Homepage')%>: <%= link_to @project.homepage, @project.homepage %></li>
7 <li><%=_('Created on')%>: <%= format_date(@project.created_on) %></li>
8 </ul>
9
10 <div class="box">
11 <h3><%= image_tag "tracker" %> <%=_('Trackers')%></h3>
12 <ul>
13 <% for tracker in Tracker.find_all %>
14 <li><%= link_to tracker.name, :controller => 'projects', :action => 'list_issues', :id => @project,
15 :set_filter => 1,
16 "issues.tracker_id" => tracker.id %>:
17 <%= tracker.issues.count(["project_id=?", @project.id]) %> <%=_('open')%>
18 </li>
19 <% end %>
20 </ul>
21 <%= link_to_if_authorized '&#187; ' + _('Report an issue'), :controller => 'projects', :action => 'add_issue', :id => @project %>
22 <br />
23 <center><small>[ <%= link_to _('View all issues'), :controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1 %> ]</small></center>
24 </div>
25 </div>
26
27 <div class="splitcontentright">
28 <div class="box">
29 <h3><%= image_tag "users" %> <%=_('Members')%></h3>
30 <% for member in @members %>
31 <%= link_to_user member.user %> (<%= member.role.name %>)<br />
32 <% end %>
33 </div>
34
35 <div class="box">
36 <h3><%=_('Latest news')%></h3>
37 <% for news in @project.news %>
38 <p>
39 <b><%= news.title %></b> <small>(<%= link_to_user news.author %> <%= format_time(news.created_on) %>)</small><br />
40 <%= news.shortdescr %>
41 <small>[<%= link_to _('Read...'), :controller => 'news', :action => 'show', :id => news %>]</small>
42 </p>
43 <hr />
44 <% end %>
45 <center><small>[ <%= link_to _('View all news'), :controller => 'projects', :action => 'list_news', :id => @project %> ]</small></center>
46 </div>
47 </div>
48
49
50
51
52
53
@@ -0,0 +1,34
1 <% col_width = 70 / (@statuses.length+3) %>
2
3 <table border="0" cellspacing="1" cellpadding="2" width="100%">
4 <tr>
5 <td width="25%"></td>
6 <% for status in @statuses %>
7 <td align="center" width="<%= col_width %>%" bgcolor="#<%= status.html_color %>"><small><%= status.name %></small></td>
8 <% end %>
9 <td align="center" width="<%= col_width %>%"><strong><%=_('Open')%></strong></td>
10 <td align="center" width="<%= col_width %>%"><strong><%=_('Closed')%></strong></td>
11 <td align="center" width="<%= col_width %>%"><strong><%=_('Total')%></strong></td>
12 </tr>
13
14 <% for row in rows %>
15 <tr style="background-color:#CEE1ED">
16 <td><%= link_to row.name, :controller => 'projects', :action => 'list_issues', :id => @project,
17 :set_filter => 1,
18 "issues.#{field_name}" => row.id %></td>
19 <% for status in @statuses %>
20 <td align="center"><%= link_to (aggregate data, { field_name => row.id, "status_id" => status.id }),
21 :controller => 'projects', :action => 'list_issues', :id => @project,
22 :set_filter => 1,
23 "issues.status_id" => status.id,
24 "issues.#{field_name}" => row.id %></td>
25 <% end %>
26 <td align="center"><%= aggregate data, { field_name => row.id, "closed" => 0 } %></td>
27 <td align="center"><%= aggregate data, { field_name => row.id, "closed" => 1 } %></td>
28 <td align="center"><%= link_to (aggregate data, { field_name => row.id }),
29 :controller => 'projects', :action => 'list_issues', :id => @project,
30 :set_filter => 1,
31 "issues.#{field_name}" => row.id %></td>
32 <% end %>
33 </tr>
34 </table> No newline at end of file
@@ -0,0 +1,13
1 <h2><%=_('Reports')%></h2>
2
3 <strong><%=_('Issues by tracker')%></strong>
4 <%= render :partial => 'simple', :locals => { :data => @issues_by_tracker, :field_name => "tracker_id", :rows => @trackers } %>
5 <br />
6
7 <strong><%=_('Issues by priority')%></strong>
8 <%= render :partial => 'simple', :locals => { :data => @issues_by_priority, :field_name => "priority_id", :rows => @priorities } %>
9 <br />
10
11 <strong><%=_('Issues by category')%></strong>
12 <%= render :partial => 'simple', :locals => { :data => @issues_by_category, :field_name => "category_id", :rows => @categories } %>
13
@@ -0,0 +1,22
1 <%= error_messages_for 'role' %>
2
3 <!--[form:role]-->
4 <p><label for="role_name"><%=_('Name')%> <span class="required">*</span></label><br />
5 <%= text_field 'role', 'name' %></p>
6
7 <strong><%=_('Permissions')%></strong>
8 <% permissions = @permissions.group_by {|p| p.group_id } %>
9 <% permissions.keys.sort.each do |group_id| %>
10 <fieldset><legend><%= _(Permission::GROUPS[group_id]) %></legend>
11 <% permissions[group_id].each do |p| %>
12 <div style="width:200px;float:left;"><%= check_box_tag "permission_ids[]", p.id, (@role.permissions.include? p) %>
13 <%= _(p.descr) %>
14 </div>
15 <% end %>
16 </fieldset>
17 <% end %>
18 <br />
19 <a href="javascript:checkAll('role_form', true)"><%=_('Check all')%></a> |
20 <a href="javascript:checkAll('role_form', false)"><%=_('Uncheck all')%></a><br />
21 <!--[eoform:role]-->
22
@@ -0,0 +1,10
1 <h2><%=_('Role')%></h2>
2
3 <%= start_form_tag ({ :action => 'edit', :id => @role }, :id => 'role_form') %>
4 <%= render :partial => 'form' %>
5
6 <br />
7 <%= submit_tag _('Save') %>
8 <%= end_form_tag %>
9
10
@@ -0,0 +1,23
1 <h2><%=_('Roles')%></h2>
2
3 <table border="0" cellspacing="1" cellpadding="2" class="listTableContent">
4 <tr class="ListHead">
5 <th><%=_('Role')%></th>
6 <th></th>
7 </tr>
8
9 <% for role in @roles %>
10 <tr style="background-color:#CEE1ED">
11 <td><%= link_to role.name, :action => 'edit', :id => role %></td>
12 <td align="center">
13 <%= start_form_tag :action => 'destroy', :id => role %>
14 <%= submit_tag _('Delete'), :class => "button-small" %>
15 <%= end_form_tag %>
16 </tr>
17 <% end %>
18 </table>
19
20 <%= pagination_links_full @role_pages %>
21 <br />
22
23 <%= link_to '&#187; ' + _('New role'), :action => 'new' %>
@@ -0,0 +1,8
1 <h2><%=_('New role')%></h2>
2
3 <%= start_form_tag ({ :action => 'new' }, :id => 'role_form') %>
4 <%= render :partial => 'form' %>
5
6 <br /><%= submit_tag _('Create') %>
7 <%= end_form_tag %>
8
@@ -0,0 +1,70
1 <h2><%=_('Workflow setup')%></h2>
2
3 <p><%=_('Select a workflow to edit')%>:</p>
4
5 <%= start_form_tag ({:action => 'workflow'}, :method => 'get') %>
6 <div style="float:left;margin-right:10px;">
7 <p><label for="role_id"><%=_('Role')%></label><br/>
8 <select id="role_id" name="role_id">
9 <%= options_from_collection_for_select @roles, "id", "name", (@role.id unless @role.nil?) %>
10 </select></p>
11 </div>
12
13 <div>
14 <p><label for="tracker_id"><%=_('Tracker')%></label><br/>
15 <select id="tracker_id" name="tracker_id">
16 <%= options_from_collection_for_select @trackers, "id", "name", (@tracker.id unless @tracker.nil?) %>
17 </select>
18
19 <%= submit_tag _('Edit') %>
20 </p>
21 </div>
22 <%= end_form_tag %>
23
24
25
26 <% unless @tracker.nil? or @role.nil? %>
27 <div class="box">
28 <%= form_tag ({:action => 'workflow', :role_id => @role, :tracker_id => @tracker }, :id => 'workflow_form' ) %>
29 <table>
30 <tr>
31 <td align="center"><strong><%=_('Issue status')%></strong></td>
32 <td align="center" colspan="<%= @statuses.length %>"><strong><%=_('New statuses allowed')%></strong></td>
33 </tr>
34 <tr>
35 <td></td>
36 <% for new_status in @statuses %>
37 <td width="60" align="center"><%= new_status.name %></td>
38 <% end %>
39 </tr>
40
41 <% for old_status in @statuses %>
42 <tr>
43 <td><%= old_status.name %></td>
44
45 <% for new_status in @statuses %>
46 <td align="center">
47
48 <input type="checkbox"
49 name="issue_status[<%= old_status.id %>][]"
50 value="<%= new_status.id %>"
51 <%if old_status.new_statuses_allowed_to(@role, @tracker).include? new_status%>checked="checked"<%end%>
52 <%if old_status==new_status%>disabled<%end%>
53 >
54 </td>
55 <% end %>
56
57 </tr>
58 <% end %>
59 </table>
60 <br />
61 <p>
62 <a href="javascript:checkAll('workflow_form', true)"><%=_('Check all')%></a> |
63 <a href="javascript:checkAll('workflow_form', false)"><%=_('Uncheck all')%></a>
64 </p>
65 <br />
66 <%= submit_tag _('Save') %>
67 <%= end_form_tag %>
68
69 <% end %>
70 </div> No newline at end of file
@@ -0,0 +1,10
1 <%= error_messages_for 'tracker' %>
2
3 <!--[form:tracker]-->
4 <p><label for="tracker_name"><%=_('Name')%></label><br/>
5 <%= text_field 'tracker', 'name' %></p>
6
7 <p><%= check_box 'tracker', 'is_in_chlog' %>
8 <label for="tracker_is_in_chlog"><%=_('View issues in change log')%></label></p>
9 <!--[eoform:tracker]-->
10
@@ -0,0 +1,6
1 <h2><%=_('Tracker')%></h2>
2
3 <%= start_form_tag :action => 'edit', :id => @tracker %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Save') %>
6 <%= end_form_tag %>
@@ -0,0 +1,24
1 <h2><%=_('Trackers')%></h2>
2
3 <table border="0" cellspacing="1" cellpadding="2" class="listTableContent">
4 <tr class="ListHead">
5 <th><%=_('Tracker')%></th>
6 <th></th>
7 </tr>
8
9 <% for tracker in @trackers %>
10 <tr style="background-color:#CEE1ED">
11 <td><%= link_to tracker.name, :action => 'edit', :id => tracker %></td>
12 <td align="center">
13 <%= start_form_tag :action => 'destroy', :id => tracker %>
14 <%= submit_tag _('Delete'), :class => "button-small" %>
15 <%= end_form_tag %>
16 </td>
17 </tr>
18 <% end %>
19 </table>
20
21 <%= pagination_links_full @tracker_pages %>
22 <br />
23
24 <%= link_to '&#187; ' + _('New tracker'), :action => 'new' %>
@@ -0,0 +1,7
1 <h2><%=_('New tracker')%></h2>
2
3 <%= start_form_tag :action => 'new' %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Create') %>
6 <%= end_form_tag %>
7
@@ -0,0 +1,28
1 <%= error_messages_for 'user' %>
2
3 <!--[form:user]-->
4 <p><label for="user_login"><%=_('Login')%></label><br/>
5 <%= text_field 'user', 'login' %></p>
6
7 <p><label for="user_password"><%=_('Password')%></label><br/>
8 <%= password_field 'user', 'password' %></p>
9
10 <p><label for="user_firstname"><%=_('Firstname')%></label><br/>
11 <%= text_field 'user', 'firstname' %></p>
12
13 <p><label for="user_lastname"><%=_('Lastname')%></label><br/>
14 <%= text_field 'user', 'lastname' %></p>
15
16 <p><label for="user_mail"><%=_('Mail')%></label><br/>
17 <%= text_field 'user', 'mail' %></p>
18
19 <p><label for="user_language"><%=_('Language')%></label><br/>
20 <%= select("user", "language", Localization.langs) %></p>
21
22 <p><%= check_box 'user', 'admin' %> <label for="user_admin"><%=_('Administrator')%></label></p>
23
24 <p><%= check_box 'user', 'mail_notification' %> <label for="user_mail_notification"><%=_('Mail notifications')%></label></p>
25
26 <p><%= check_box 'user', 'locked' %> <label for="user_locked"><%=_('Locked')%></label></p>
27 <!--[eoform:user]-->
28
@@ -0,0 +1,6
1 <h2><%=_('New user')%></h2>
2
3 <%= start_form_tag :action => 'add' %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Create') %>
6 <%= end_form_tag %>
@@ -0,0 +1,7
1 <h2><%=_('User')%></h2>
2
3 <%= start_form_tag :action => 'edit', :id => @user %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Save') %>
6
7 <%= end_form_tag %>
@@ -0,0 +1,46
1 <h2><%=_('Users')%></h2>
2
3 <table border="0" cellspacing="1" cellpadding="2" class="listTableContent">
4 <tr class="ListHead">
5 <%= sort_header_tag('users.login', :caption => _('Login')) %>
6 <%= sort_header_tag('users.firstname', :caption => _('Firstname')) %>
7 <%= sort_header_tag('users.lastname', :caption => _('Lastname')) %>
8 <th><%=_('Mail')%></th>
9 <%= sort_header_tag('users.admin', :caption => _('Admin')) %>
10 <%= sort_header_tag('users.locked', :caption => _('Locked')) %>
11 <%= sort_header_tag('users.created_on', :caption => _('Created on')) %>
12 <%= sort_header_tag('users.last_login_on', :caption => _('Last login')) %>
13 <th></th>
14 </tr>
15 <% for user in @users %>
16 <tr style="background-color:#CEE1ED">
17 <td><%= link_to user.login, :action => 'edit', :id => user %></td>
18 <td><%= user.firstname %></td>
19 <td><%= user.lastname %></td>
20 <td><%= user.mail %></td>
21 <td align="center"><%= image_tag 'true' if user.admin? %></td>
22 <td align="center"><%= image_tag 'locked' if user.locked? %></td>
23 <td align="center"><%= format_time(user.created_on) %></td>
24 <td align="center"><%= format_time(user.last_login_on) unless user.last_login_on.nil? %></td>
25 <td align="center">
26 <%= start_form_tag :action => 'edit', :id => user %>
27 <% if user.locked? %>
28 <%= hidden_field_tag 'user[locked]', false %>
29 <%= submit_tag _('Unlock'), :class => "button-small" %>
30 <% else %>
31 <%= hidden_field_tag 'user[locked]', true %>
32 <%= submit_tag _('Lock'), :class => "button-small" %>
33 <% end %>
34 <%= end_form_tag %>
35 </td>
36 </tr>
37 <% end %>
38 </table>
39
40 <p><%= pagination_links_full @user_pages %>
41 [ <%= @user_pages.current.first_item %> - <%= @user_pages.current.last_item %> / <%= @user_count %> ]
42 </p>
43
44 <p>
45 <%= link_to '&#187; ' + _('New user'), :action => 'add' %>
46 </p> No newline at end of file
@@ -0,0 +1,13
1 <%= error_messages_for 'version' %>
2
3 <!--[form:version]-->
4 <p><label for="version_name"><%=_('Version')%></label><br/>
5 <%= text_field 'version', 'name', :size => 20 %></p>
6
7 <p><label for="version_descr"><%=_('Description')%></label><br/>
8 <%= text_field 'version', 'descr', :size => 60 %></p>
9
10 <p><label for="version_date"><%=_('Date')%></label><br/>
11 <%= date_select 'version', 'date' %></p>
12 <!--[eoform:version]-->
13
@@ -0,0 +1,8
1 <h2><%=_('Version')%></h2>
2
3 <%= start_form_tag :action => 'edit', :id => @version %>
4 <%= render :partial => 'form' %>
5 <%= submit_tag _('Save') %>
6 <%= end_form_tag %>
7
8
@@ -0,0 +1,30
1 <div class="splitcontentleft">
2 <h2><%=_('Welcome')%> !</h2>
3
4 <div class="box">
5 <h3>Latest news</h3>
6 <% for news in @news %>
7 <p>
8 <b><%= news.title %></b> (<%= link_to_user news.author %> <%= format_time(news.created_on) %> - <%= news.project.name %>)<br />
9 <%= news.shortdescr %><br />
10 [<%= link_to 'Read...', :controller => 'news', :action => 'show', :id => news %>]
11 </p>
12 <hr />
13 <% end %>
14 </div>
15 </div>
16
17 <div class="splitcontentright">
18 <div class="box">
19 <h3>Latest projects</h3>
20 <ul>
21 <% for project in @projects %>
22 <li>
23 <%= link_to project.name, :controller => 'projects', :action => 'show', :id => project %> (added <%= format_time(project.created_on) %>)<br />
24 <%= project.descr %>
25 </li>
26 <% end %>
27 </ul>
28 </div>
29
30 </div>
@@ -0,0 +1,19
1 # Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
2
3 unless defined?(RAILS_ROOT)
4 root_path = File.join(File.dirname(__FILE__), '..')
5 unless RUBY_PLATFORM =~ /mswin32/
6 require 'pathname'
7 root_path = Pathname.new(root_path).cleanpath(true).to_s
8 end
9 RAILS_ROOT = root_path
10 end
11
12 if File.directory?("#{RAILS_ROOT}/vendor/rails")
13 require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
14 else
15 require 'rubygems'
16 require 'initializer'
17 end
18
19 Rails::Initializer.run(:set_load_path)
@@ -0,0 +1,32
1 # MySQL (default setup). Versions 4.1 and 5.0 are recommended.
2 #
3 # Get the fast C bindings:
4 # gem install mysql
5 # (on OS X: gem install mysql -- --include=/usr/local/lib)
6 # And be sure to use new-style password hashing:
7 # http://dev.mysql.com/doc/refman/5.0/en/old-client.html
8 development:
9 adapter: mysql
10 database: redmine_development
11 host: localhost
12 username: root
13 password:
14
15 test:
16 adapter: mysql
17 database: redmine_test
18 host: localhost
19 username: root
20 password:
21
22 demo:
23 adapter: sqlite3
24 dbfile: db/redmine_demo.db
25
26 production:
27 adapter: mysql
28 database: redmine
29 host: localhost
30 username: root
31 password:
32 No newline at end of file
@@ -0,0 +1,85
1 # Be sure to restart your web server when you modify this file.
2
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
5 # ENV['RAILS_ENV'] ||= 'production'
6
7 # Bootstrap the Rails environment, frameworks, and default configuration
8 require File.join(File.dirname(__FILE__), 'boot')
9
10 Rails::Initializer.run do |config|
11 # Settings in config/environments/* take precedence those specified here
12
13 # Skip frameworks you're not going to use
14 # config.frameworks -= [ :action_web_service, :action_mailer ]
15
16 # Add additional load paths for your own custom dirs
17 # config.load_paths += %W( #{RAILS_ROOT}/extras )
18
19 # Force all environments to use the same logger level
20 # (by default production uses :info, the others :debug)
21 # config.log_level = :debug
22
23 # Use the database for sessions instead of the file system
24 # (create the session table with 'rake create_sessions_table')
25 # config.action_controller.session_store = :active_record_store
26
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)
29 # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
30
31 # Activate observers that should always be running
32 # config.active_record.observers = :cacher, :garbage_collector
33
34 # Make Active Record use UTC-base instead of local time
35 # config.active_record.default_timezone = :utc
36
37 # Use Active Record's schema dumper instead of SQL when creating the test database
38 # (enables use of different database adapters for development and test environments)
39 # config.active_record.schema_format = :ruby
40
41 # See Rails::Configuration for more options
42
43 # SMTP server configuration
44 config.action_mailer.server_settings = {
45 :address => "127.0.0.1",
46 :port => 25,
47 :domain => "somenet.foo",
48 :authentication => :login,
49 :user_name => "redmine",
50 :password => "redmine",
51 }
52
53 config.action_mailer.perform_deliveries = true
54
55 # Tell ActionMailer not to deliver emails to the real world.
56 # The :test delivery method accumulates sent emails in the
57 # ActionMailer::Base.deliveries array.
58 #config.action_mailer.delivery_method = :test
59 config.action_mailer.delivery_method = :smtp
60 end
61
62 # Add new inflection rules using the following format
63 # (all these examples are active by default):
64 # Inflector.inflections do |inflect|
65 # inflect.plural /^(ox)$/i, '\1en'
66 # inflect.singular /^(ox)en/i, '\1'
67 # inflect.irregular 'person', 'people'
68 # inflect.uncountable %w( fish sheep )
69 # end
70
71 # Include your application configuration below
72
73 # application name
74 RDM_APP_NAME = "redMine"
75 # application version
76 RDM_APP_VERSION = "0.1.0"
77 # application host name
78 RDM_HOST_NAME = "somenet.foo"
79 # file storage path
80 RDM_STORAGE_PATH = "#{RAILS_ROOT}/files"
81 # if RDM_LOGIN_REQUIRED is set to true, login is required to access the application
82 RDM_LOGIN_REQUIRED = false
83 # default langage
84 RDM_DEFAULT_LANG = 'en'
85
@@ -0,0 +1,21
1 # Settings specified here will take precedence over those in config/environment.rb
2
3 # The production environment is meant for finished, "live" apps.
4 # Code is not reloaded between requests
5 config.cache_classes = true
6
7 # Use a different logger for distributed setups
8 # config.logger = SyslogLogger.new
9 config.log_level = :info
10
11 # Full error reports are disabled and caching is turned on
12 config.action_controller.consider_all_requests_local = false
13 config.action_controller.perform_caching = true
14
15 # Enable serving of images, stylesheets, and javascripts from an asset server
16 # config.action_controller.asset_host = "http://assets.example.com"
17
18 # Disable mail delivery
19 config.action_mailer.perform_deliveries = false
20 config.action_mailer.raise_delivery_errors = false
21
@@ -0,0 +1,19
1 # Settings specified here will take precedence over those in config/environment.rb
2
3 # In the development environment your application's code is reloaded on
4 # every request. This slows down response time but is perfect for development
5 # since you don't have to restart the webserver when you make code changes.
6 config.cache_classes = false
7
8 # Log error messages when you accidentally call methods on nil.
9 config.whiny_nils = true
10
11 # Enable the breakpoint server that script/breakpointer connects to
12 config.breakpoint_server = true
13
14 # Show full error reports and disable caching
15 config.action_controller.consider_all_requests_local = true
16 config.action_controller.perform_caching = false
17
18 # Don't care if the mailer can't send
19 config.action_mailer.raise_delivery_errors = false
@@ -0,0 +1,20
1 # Settings specified here will take precedence over those in config/environment.rb
2
3 # The production environment is meant for finished, "live" apps.
4 # Code is not reloaded between requests
5 config.cache_classes = true
6
7 # Use a different logger for distributed setups
8 # config.logger = SyslogLogger.new
9
10
11 # Full error reports are disabled and caching is turned on
12 config.action_controller.consider_all_requests_local = false
13 config.action_controller.perform_caching = true
14
15 # Enable serving of images, stylesheets, and javascripts from an asset server
16 # config.action_controller.asset_host = "http://assets.example.com"
17
18 # Disable delivery errors if you bad email addresses should just be ignored
19 config.action_mailer.raise_delivery_errors = false
20
@@ -0,0 +1,15
1 # Settings specified here will take precedence over those in config/environment.rb
2
3 # The test environment is used exclusively to run your application's
4 # test suite. You never need to work with it otherwise. Remember that
5 # your test database is "scratch space" for the test suite and is wiped
6 # and recreated between test runs. Don't rely on the data there!
7 config.cache_classes = true
8
9 # Log error messages when you accidentally call methods on nil.
10 config.whiny_nils = true
11
12 # Show full error reports and disable caching
13 config.action_controller.consider_all_requests_local = true
14 config.action_controller.perform_caching = false
15
@@ -0,0 +1,21
1 # administration
2 admin:
3 index: administration.html
4 mail_options: administration.html#mail_notifications
5 info: administration.html#app_info
6 users:
7 index: administration.html#users
8 roles:
9 index: administration.html#roles
10 workflow: administration.html#workflow
11 trackers:
12 index: administration.html#trackers
13 issue_statuses:
14 index: administration.html#issue_statuses
15
16 # projects
17 projects:
18 add: projects.html#settings
19
20
21 # issues No newline at end of file
@@ -0,0 +1,24
1 ActionController::Routing::Routes.draw do |map|
2 # Add your own custom routes here.
3 # The priority is based upon order of creation: first created -> highest priority.
4
5 # Here's a sample route:
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 # Keep in mind you can assign values other than :controller and :action
8
9 # You can have the root of your site routed by hooking up ''
10 # -- just remember to delete public/index.html.
11 map.connect '', :controller => "welcome"
12
13 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
14 map.connect 'help/:ctrl/:page', :controller => 'help'
15 map.connect ':controller/:action/:id/:sort_key/:sort_order'
16
17 # Allow downloading Web Service WSDL as a file with an extension
18 # instead of a file named 'wsdl'
19 map.connect ':controller/service.wsdl', :action => 'wsdl'
20
21
22 # Install the default route as the lowest priority.
23 map.connect ':controller/:action/:id'
24 end
@@ -0,0 +1,254
1 class Setup < ActiveRecord::Migration
2 def self.up
3 create_table "attachments", :force => true do |t|
4 t.column "container_id", :integer, :default => 0, :null => false
5 t.column "container_type", :string, :limit => 30, :default => "", :null => false
6 t.column "filename", :string, :default => "", :null => false
7 t.column "disk_filename", :string, :default => "", :null => false
8 t.column "size", :integer, :default => 0, :null => false
9 t.column "content_type", :string, :limit => 60, :default => "", :null => false
10 t.column "digest", :string, :limit => 40, :default => "", :null => false
11 t.column "downloads", :integer, :default => 0, :null => false
12 t.column "author_id", :integer, :default => 0, :null => false
13 t.column "created_on", :timestamp
14 end
15
16 create_table "custom_fields", :force => true do |t|
17 t.column "name", :string, :limit => 30, :default => "", :null => false
18 t.column "typ", :integer, :limit => 6, :default => 0, :null => false
19 t.column "is_required", :boolean, :default => false, :null => false
20 t.column "is_for_all", :boolean, :default => false, :null => false
21 t.column "possible_values", :text, :default => "", :null => false
22 t.column "regexp", :string, :default => "", :null => false
23 t.column "min_length", :integer, :limit => 4, :default => 0, :null => false
24 t.column "max_length", :integer, :limit => 4, :default => 0, :null => false
25 end
26
27 create_table "custom_fields_projects", :id => false, :force => true do |t|
28 t.column "custom_field_id", :integer, :default => 0, :null => false
29 t.column "project_id", :integer, :default => 0, :null => false
30 end
31
32 create_table "custom_values", :force => true do |t|
33 t.column "issue_id", :integer, :default => 0, :null => false
34 t.column "custom_field_id", :integer, :default => 0, :null => false
35 t.column "value", :text, :default => "", :null => false
36 end
37
38 add_index "custom_values", ["issue_id"], :name => "custom_values_issue_id"
39
40 create_table "documents", :force => true do |t|
41 t.column "project_id", :integer, :default => 0, :null => false
42 t.column "category_id", :integer, :default => 0, :null => false
43 t.column "title", :string, :limit => 60, :default => "", :null => false
44 t.column "descr", :text, :default => "", :null => false
45 t.column "created_on", :timestamp
46 end
47
48 create_table "enumerations", :force => true do |t|
49 t.column "opt", :string, :limit => 4, :default => "", :null => false
50 t.column "name", :string, :limit => 30, :default => "", :null => false
51 end
52
53 create_table "issue_categories", :force => true do |t|
54 t.column "project_id", :integer, :default => 0, :null => false
55 t.column "name", :string, :limit => 30, :default => "", :null => false
56 end
57
58 create_table "issue_histories", :force => true do |t|
59 t.column "issue_id", :integer, :default => 0, :null => false
60 t.column "status_id", :integer, :default => 0, :null => false
61 t.column "author_id", :integer, :default => 0, :null => false
62 t.column "notes", :text, :default => "", :null => false
63 t.column "created_on", :timestamp
64 end
65
66 add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id"
67
68 create_table "issue_statuses", :force => true do |t|
69 t.column "name", :string, :limit => 30, :default => "", :null => false
70 t.column "is_closed", :boolean, :default => false, :null => false
71 t.column "is_default", :boolean, :default => false, :null => false
72 t.column "html_color", :string, :limit => 6, :default => "FFFFFF", :null => false
73 end
74
75 create_table "issues", :force => true do |t|
76 t.column "tracker_id", :integer, :default => 0, :null => false
77 t.column "project_id", :integer, :default => 0, :null => false
78 t.column "subject", :string, :default => "", :null => false
79 t.column "descr", :text, :default => "", :null => false
80 t.column "category_id", :integer
81 t.column "status_id", :integer, :default => 0, :null => false
82 t.column "assigned_to_id", :integer
83 t.column "priority_id", :integer, :default => 0, :null => false
84 t.column "fixed_version_id", :integer
85 t.column "author_id", :integer, :default => 0, :null => false
86 t.column "created_on", :timestamp
87 t.column "updated_on", :timestamp
88 end
89
90 add_index "issues", ["project_id"], :name => "issues_project_id"
91
92 create_table "members", :force => true do |t|
93 t.column "user_id", :integer, :default => 0, :null => false
94 t.column "project_id", :integer, :default => 0, :null => false
95 t.column "role_id", :integer, :default => 0, :null => false
96 t.column "created_on", :timestamp
97 end
98
99 create_table "news", :force => true do |t|
100 t.column "project_id", :integer
101 t.column "title", :string, :limit => 60, :default => "", :null => false
102 t.column "shortdescr", :string, :default => "", :null => false
103 t.column "descr", :text, :default => "", :null => false
104 t.column "author_id", :integer, :default => 0, :null => false
105 t.column "created_on", :timestamp
106 end
107
108 create_table "permissions", :force => true do |t|
109 t.column "controller", :string, :limit => 30, :default => "", :null => false
110 t.column "action", :string, :limit => 30, :default => "", :null => false
111 t.column "descr", :string, :limit => 60, :default => "", :null => false
112 t.column "public", :boolean, :default => false, :null => false
113 t.column "sort", :integer, :default => 0, :null => false
114 t.column "mail_option", :boolean, :default => false, :null => false
115 t.column "mail_enabled", :boolean, :default => false, :null => false
116 end
117
118 create_table "permissions_roles", :id => false, :force => true do |t|
119 t.column "permission_id", :integer, :default => 0, :null => false
120 t.column "role_id", :integer, :default => 0, :null => false
121 end
122
123 add_index "permissions_roles", ["role_id"], :name => "permissions_roles_role_id"
124
125 create_table "projects", :force => true do |t|
126 t.column "name", :string, :limit => 30, :default => "", :null => false
127 t.column "descr", :string, :default => "", :null => false
128 t.column "homepage", :string, :limit => 60, :default => "", :null => false
129 t.column "public", :boolean, :default => true, :null => false
130 t.column "created_on", :timestamp
131 t.column "updated_on", :timestamp
132 end
133
134 create_table "roles", :force => true do |t|
135 t.column "name", :string, :limit => 30, :default => "", :null => false
136 end
137
138 create_table "trackers", :force => true do |t|
139 t.column "name", :string, :limit => 30, :default => "", :null => false
140 t.column "is_in_chlog", :boolean, :default => false, :null => false
141 end
142
143 create_table "users", :force => true do |t|
144 t.column "login", :string, :limit => 30, :default => "", :null => false
145 t.column "hashed_password", :string, :limit => 40, :default => "", :null => false
146 t.column "firstname", :string, :limit => 30, :default => "", :null => false
147 t.column "lastname", :string, :limit => 30, :default => "", :null => false
148 t.column "mail", :string, :limit => 60, :default => "", :null => false
149 t.column "mail_notification", :boolean, :default => true, :null => false
150 t.column "admin", :boolean, :default => false, :null => false
151 t.column "locked", :boolean, :default => false, :null => false
152 t.column "last_login_on", :datetime
153 t.column "language", :string, :limit => 2, :default => "", :null => false
154 t.column "created_on", :timestamp
155 t.column "updated_on", :timestamp
156 end
157
158 create_table "versions", :force => true do |t|
159 t.column "project_id", :integer, :default => 0, :null => false
160 t.column "name", :string, :limit => 30, :default => "", :null => false
161 t.column "descr", :string, :default => "", :null => false
162 t.column "date", :date, :null => false
163 t.column "created_on", :timestamp
164 t.column "updated_on", :timestamp
165 end
166
167 create_table "workflows", :force => true do |t|
168 t.column "tracker_id", :integer, :default => 0, :null => false
169 t.column "old_status_id", :integer, :default => 0, :null => false
170 t.column "new_status_id", :integer, :default => 0, :null => false
171 t.column "role_id", :integer, :default => 0, :null => false
172 end
173
174 # project
175 Permission.create :controller => "projects", :action => "show", :descr => "Overview", :sort => 100, :public => true
176 Permission.create :controller => "projects", :action => "changelog", :descr => "View change log", :sort => 105, :public => true
177 Permission.create :controller => "reports", :action => "issue_report", :descr => "View reports", :sort => 110, :public => true
178 Permission.create :controller => "projects", :action => "settings", :descr => "Settings", :sort => 150
179 Permission.create :controller => "projects", :action => "edit", :descr => "Edit", :sort => 151
180 # members
181 Permission.create :controller => "projects", :action => "list_members", :descr => "View list", :sort => 200, :public => true
182 Permission.create :controller => "projects", :action => "add_member", :descr => "New member", :sort => 220
183 Permission.create :controller => "members", :action => "edit", :descr => "Edit", :sort => 221
184 Permission.create :controller => "members", :action => "destroy", :descr => "Delete", :sort => 222
185 # versions
186 Permission.create :controller => "projects", :action => "add_version", :descr => "New version", :sort => 320
187 Permission.create :controller => "versions", :action => "edit", :descr => "Edit", :sort => 321
188 Permission.create :controller => "versions", :action => "destroy", :descr => "Delete", :sort => 322
189 # issue categories
190 Permission.create :controller => "projects", :action => "add_issue_category", :descr => "New issue category", :sort => 420
191 Permission.create :controller => "issue_categories", :action => "edit", :descr => "Edit", :sort => 421
192 Permission.create :controller => "issue_categories", :action => "destroy", :descr => "Delete", :sort => 422
193 # issues
194 Permission.create :controller => "projects", :action => "list_issues", :descr => "View list", :sort => 1000, :public => true
195 Permission.create :controller => "issues", :action => "show", :descr => "View", :sort => 1005, :public => true
196 Permission.create :controller => "issues", :action => "download", :descr => "Download file", :sort => 1010, :public => true
197 Permission.create :controller => "projects", :action => "add_issue", :descr => "Report an issue", :sort => 1050, :mail_option => 1, :mail_enabled => 1
198 Permission.create :controller => "issues", :action => "edit", :descr => "Edit", :sort => 1055
199 Permission.create :controller => "issues", :action => "change_status", :descr => "Change status", :sort => 1060, :mail_option => 1, :mail_enabled => 1
200 Permission.create :controller => "issues", :action => "destroy", :descr => "Delete", :sort => 1065
201 Permission.create :controller => "issues", :action => "add_attachment", :descr => "Add file", :sort => 1070
202 Permission.create :controller => "issues", :action => "destroy_attachment", :descr => "Delete file", :sort => 1075
203 # news
204 Permission.create :controller => "projects", :action => "list_news", :descr => "View list", :sort => 1100, :public => true
205 Permission.create :controller => "news", :action => "show", :descr => "View", :sort => 1101, :public => true
206 Permission.create :controller => "projects", :action => "add_news", :descr => "Add", :sort => 1120
207 Permission.create :controller => "news", :action => "edit", :descr => "Edit", :sort => 1121
208 Permission.create :controller => "news", :action => "destroy", :descr => "Delete", :sort => 1122
209 # documents
210 Permission.create :controller => "projects", :action => "list_documents", :descr => "View list", :sort => 1200, :public => true
211 Permission.create :controller => "documents", :action => "show", :descr => "View", :sort => 1201, :public => true
212 Permission.create :controller => "documents", :action => "download", :descr => "Download", :sort => 1202, :public => true
213 Permission.create :controller => "projects", :action => "add_document", :descr => "Add", :sort => 1220
214 Permission.create :controller => "documents", :action => "edit", :descr => "Edit", :sort => 1221
215 Permission.create :controller => "documents", :action => "destroy", :descr => "Delete", :sort => 1222
216 Permission.create :controller => "documents", :action => "add_attachment", :descr => "Add file", :sort => 1223
217 Permission.create :controller => "documents", :action => "destroy_attachment", :descr => "Delete file", :sort => 1224
218 # files
219 Permission.create :controller => "projects", :action => "list_files", :descr => "View list", :sort => 1300, :public => true
220 Permission.create :controller => "versions", :action => "download", :descr => "Download", :sort => 1301, :public => true
221 Permission.create :controller => "projects", :action => "add_file", :descr => "Add", :sort => 1320
222 Permission.create :controller => "versions", :action => "destroy_file", :descr => "Delete", :sort => 1322
223
224 # create default administrator account
225 user = User.create :login => "admin", :password => "admin", :firstname => "redMine", :lastname => "Admin", :mail => "admin@somenet.foo", :mail_notification => true, :language => "en"
226 user.admin = true
227 user.save
228
229
230 end
231
232 def self.down
233 drop_table :attachments
234 drop_table :custom_fields
235 drop_table :custom_fields_projects
236 drop_table :custom_values
237 drop_table :documents
238 drop_table :enumerations
239 drop_table :issue_categories
240 drop_table :issue_histories
241 drop_table :issue_statuses
242 drop_table :issues
243 drop_table :members
244 drop_table :news
245 drop_table :permissions
246 drop_table :permissions_roles
247 drop_table :projects
248 drop_table :roles
249 drop_table :trackers
250 drop_table :users
251 drop_table :versions
252 drop_table :workflows
253 end
254 end
@@ -0,0 +1,44
1 class DefaultConfiguration < ActiveRecord::Migration
2 def self.up
3 # roles
4 r = Role.create(:name => "Manager")
5 r.permissions = Permission.find(:all)
6 r = Role.create :name => "Developer"
7 r.permissions = Permission.find([1, 2, 3, 6, 10, 11, 12, 16, 17, 18, 19, 20, 21, 23, 25, 26, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41])
8 r = Role.create :name => "Reporter"
9 r.permissions = Permission.find([1, 2, 3, 6, 16, 17, 18, 19, 20, 21, 23, 25, 26, 30, 31, 32, 38, 39])
10 # trackers
11 Tracker.create(:name => "Bug", :is_in_chlog => true)
12 Tracker.create(:name => "Feature request", :is_in_chlog => true)
13 Tracker.create(:name => "Support request", :is_in_chlog => false)
14 # issue statuses
15 IssueStatus.create(:name => "New", :is_closed => false, :is_default => true, :html_color => 'F98787')
16 IssueStatus.create(:name => "Assigned", :is_closed => false, :is_default => false, :html_color => 'C0C0FF')
17 IssueStatus.create(:name => "Resolved", :is_closed => false, :is_default => false, :html_color => '88E0B3')
18 IssueStatus.create(:name => "Feedback", :is_closed => false, :is_default => false, :html_color => 'F3A4F4')
19 IssueStatus.create(:name => "Closed", :is_closed => true, :is_default => false, :html_color => 'DBDBDB')
20 IssueStatus.create(:name => "Rejected", :is_closed => true, :is_default => false, :html_color => 'F5C28B')
21 # workflow
22 Tracker.find(:all).each { |t|
23 Role.find(:all).each { |r|
24 IssueStatus.find(:all).each { |os|
25 IssueStatus.find(:all).each { |ns|
26 Workflow.create(:tracker_id => t.id, :role_id => r.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
27 }
28 }
29 }
30 }
31 # enumeartions
32 Enumeration.create(:opt => "DCAT", :name => 'Uncategorized')
33 Enumeration.create(:opt => "DCAT", :name => 'User documentation')
34 Enumeration.create(:opt => "DCAT", :name => 'Technical documentation')
35 Enumeration.create(:opt => "IPRI", :name => 'Low')
36 Enumeration.create(:opt => "IPRI", :name => 'Normal')
37 Enumeration.create(:opt => "IPRI", :name => 'High')
38 Enumeration.create(:opt => "IPRI", :name => 'Urgent')
39 Enumeration.create(:opt => "IPRI", :name => 'Immediate')
40 end
41
42 def self.down
43 end
44 end
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,17
1 == redMine changelog
2
3 redMine - project management software
4 Copyright (C) 2006 Jean-Philippe Lang
5 http://redmine.sourceforge.net/
6
7
8 == 06/25/2006 - v0.1.0
9
10 * multiple users/multiple projects
11 * role based access control
12 * issue tracking system
13 * fully customizable workflow
14 * documents/files repository
15 * email notifications on issue creation and update
16 * multilanguage support (except for error messages):english, french, spanish
17 * online manual in french (unfinished) No newline at end of file
@@ -0,0 +1,339
1 GNU GENERAL PUBLIC LICENSE
2 Version 2, June 1991
3
4 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 Everyone is permitted to copy and distribute verbatim copies
7 of this license document, but changing it is not allowed.
8
9 Preamble
10
11 The licenses for most software are designed to take away your
12 freedom to share and change it. By contrast, the GNU General Public
13 License is intended to guarantee your freedom to share and change free
14 software--to make sure the software is free for all its users. This
15 General Public License applies to most of the Free Software
16 Foundation's software and to any other program whose authors commit to
17 using it. (Some other Free Software Foundation software is covered by
18 the GNU Lesser General Public License instead.) You can apply it to
19 your programs, too.
20
21 When we speak of free software, we are referring to freedom, not
22 price. Our General Public Licenses are designed to make sure that you
23 have the freedom to distribute copies of free software (and charge for
24 this service if you wish), that you receive source code or can get it
25 if you want it, that you can change the software or use pieces of it
26 in new free programs; and that you know you can do these things.
27
28 To protect your rights, we need to make restrictions that forbid
29 anyone to deny you these rights or to ask you to surrender the rights.
30 These restrictions translate to certain responsibilities for you if you
31 distribute copies of the software, or if you modify it.
32
33 For example, if you distribute copies of such a program, whether
34 gratis or for a fee, you must give the recipients all the rights that
35 you have. You must make sure that they, too, receive or can get the
36 source code. And you must show them these terms so they know their
37 rights.
38
39 We protect your rights with two steps: (1) copyright the software, and
40 (2) offer you this license which gives you legal permission to copy,
41 distribute and/or modify the software.
42
43 Also, for each author's protection and ours, we want to make certain
44 that everyone understands that there is no warranty for this free
45 software. If the software is modified by someone else and passed on, we
46 want its recipients to know that what they have is not the original, so
47 that any problems introduced by others will not reflect on the original
48 authors' reputations.
49
50 Finally, any free program is threatened constantly by software
51 patents. We wish to avoid the danger that redistributors of a free
52 program will individually obtain patent licenses, in effect making the
53 program proprietary. To prevent this, we have made it clear that any
54 patent must be licensed for everyone's free use or not licensed at all.
55
56 The precise terms and conditions for copying, distribution and
57 modification follow.
58
59 GNU GENERAL PUBLIC LICENSE
60 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
62 0. This License applies to any program or other work which contains
63 a notice placed by the copyright holder saying it may be distributed
64 under the terms of this General Public License. The "Program", below,
65 refers to any such program or work, and a "work based on the Program"
66 means either the Program or any derivative work under copyright law:
67 that is to say, a work containing the Program or a portion of it,
68 either verbatim or with modifications and/or translated into another
69 language. (Hereinafter, translation is included without limitation in
70 the term "modification".) Each licensee is addressed as "you".
71
72 Activities other than copying, distribution and modification are not
73 covered by this License; they are outside its scope. The act of
74 running the Program is not restricted, and the output from the Program
75 is covered only if its contents constitute a work based on the
76 Program (independent of having been made by running the Program).
77 Whether that is true depends on what the Program does.
78
79 1. You may copy and distribute verbatim copies of the Program's
80 source code as you receive it, in any medium, provided that you
81 conspicuously and appropriately publish on each copy an appropriate
82 copyright notice and disclaimer of warranty; keep intact all the
83 notices that refer to this License and to the absence of any warranty;
84 and give any other recipients of the Program a copy of this License
85 along with the Program.
86
87 You may charge a fee for the physical act of transferring a copy, and
88 you may at your option offer warranty protection in exchange for a fee.
89
90 2. You may modify your copy or copies of the Program or any portion
91 of it, thus forming a work based on the Program, and copy and
92 distribute such modifications or work under the terms of Section 1
93 above, provided that you also meet all of these conditions:
94
95 a) You must cause the modified files to carry prominent notices
96 stating that you changed the files and the date of any change.
97
98 b) You must cause any work that you distribute or publish, that in
99 whole or in part contains or is derived from the Program or any
100 part thereof, to be licensed as a whole at no charge to all third
101 parties under the terms of this License.
102
103 c) If the modified program normally reads commands interactively
104 when run, you must cause it, when started running for such
105 interactive use in the most ordinary way, to print or display an
106 announcement including an appropriate copyright notice and a
107 notice that there is no warranty (or else, saying that you provide
108 a warranty) and that users may redistribute the program under
109 these conditions, and telling the user how to view a copy of this
110 License. (Exception: if the Program itself is interactive but
111 does not normally print such an announcement, your work based on
112 the Program is not required to print an announcement.)
113
114 These requirements apply to the modified work as a whole. If
115 identifiable sections of that work are not derived from the Program,
116 and can be reasonably considered independent and separate works in
117 themselves, then this License, and its terms, do not apply to those
118 sections when you distribute them as separate works. But when you
119 distribute the same sections as part of a whole which is a work based
120 on the Program, the distribution of the whole must be on the terms of
121 this License, whose permissions for other licensees extend to the
122 entire whole, and thus to each and every part regardless of who wrote it.
123
124 Thus, it is not the intent of this section to claim rights or contest
125 your rights to work written entirely by you; rather, the intent is to
126 exercise the right to control the distribution of derivative or
127 collective works based on the Program.
128
129 In addition, mere aggregation of another work not based on the Program
130 with the Program (or with a work based on the Program) on a volume of
131 a storage or distribution medium does not bring the other work under
132 the scope of this License.
133
134 3. You may copy and distribute the Program (or a work based on it,
135 under Section 2) in object code or executable form under the terms of
136 Sections 1 and 2 above provided that you also do one of the following:
137
138 a) Accompany it with the complete corresponding machine-readable
139 source code, which must be distributed under the terms of Sections
140 1 and 2 above on a medium customarily used for software interchange; or,
141
142 b) Accompany it with a written offer, valid for at least three
143 years, to give any third party, for a charge no more than your
144 cost of physically performing source distribution, a complete
145 machine-readable copy of the corresponding source code, to be
146 distributed under the terms of Sections 1 and 2 above on a medium
147 customarily used for software interchange; or,
148
149 c) Accompany it with the information you received as to the offer
150 to distribute corresponding source code. (This alternative is
151 allowed only for noncommercial distribution and only if you
152 received the program in object code or executable form with such
153 an offer, in accord with Subsection b above.)
154
155 The source code for a work means the preferred form of the work for
156 making modifications to it. For an executable work, complete source
157 code means all the source code for all modules it contains, plus any
158 associated interface definition files, plus the scripts used to
159 control compilation and installation of the executable. However, as a
160 special exception, the source code distributed need not include
161 anything that is normally distributed (in either source or binary
162 form) with the major components (compiler, kernel, and so on) of the
163 operating system on which the executable runs, unless that component
164 itself accompanies the executable.
165
166 If distribution of executable or object code is made by offering
167 access to copy from a designated place, then offering equivalent
168 access to copy the source code from the same place counts as
169 distribution of the source code, even though third parties are not
170 compelled to copy the source along with the object code.
171
172 4. You may not copy, modify, sublicense, or distribute the Program
173 except as expressly provided under this License. Any attempt
174 otherwise to copy, modify, sublicense or distribute the Program is
175 void, and will automatically terminate your rights under this License.
176 However, parties who have received copies, or rights, from you under
177 this License will not have their licenses terminated so long as such
178 parties remain in full compliance.
179
180 5. You are not required to accept this License, since you have not
181 signed it. However, nothing else grants you permission to modify or
182 distribute the Program or its derivative works. These actions are
183 prohibited by law if you do not accept this License. Therefore, by
184 modifying or distributing the Program (or any work based on the
185 Program), you indicate your acceptance of this License to do so, and
186 all its terms and conditions for copying, distributing or modifying
187 the Program or works based on it.
188
189 6. Each time you redistribute the Program (or any work based on the
190 Program), the recipient automatically receives a license from the
191 original licensor to copy, distribute or modify the Program subject to
192 these terms and conditions. You may not impose any further
193 restrictions on the recipients' exercise of the rights granted herein.
194 You are not responsible for enforcing compliance by third parties to
195 this License.
196
197 7. If, as a consequence of a court judgment or allegation of patent
198 infringement or for any other reason (not limited to patent issues),
199 conditions are imposed on you (whether by court order, agreement or
200 otherwise) that contradict the conditions of this License, they do not
201 excuse you from the conditions of this License. If you cannot
202 distribute so as to satisfy simultaneously your obligations under this
203 License and any other pertinent obligations, then as a consequence you
204 may not distribute the Program at all. For example, if a patent
205 license would not permit royalty-free redistribution of the Program by
206 all those who receive copies directly or indirectly through you, then
207 the only way you could satisfy both it and this License would be to
208 refrain entirely from distribution of the Program.
209
210 If any portion of this section is held invalid or unenforceable under
211 any particular circumstance, the balance of the section is intended to
212 apply and the section as a whole is intended to apply in other
213 circumstances.
214
215 It is not the purpose of this section to induce you to infringe any
216 patents or other property right claims or to contest validity of any
217 such claims; this section has the sole purpose of protecting the
218 integrity of the free software distribution system, which is
219 implemented by public license practices. Many people have made
220 generous contributions to the wide range of software distributed
221 through that system in reliance on consistent application of that
222 system; it is up to the author/donor to decide if he or she is willing
223 to distribute software through any other system and a licensee cannot
224 impose that choice.
225
226 This section is intended to make thoroughly clear what is believed to
227 be a consequence of the rest of this License.
228
229 8. If the distribution and/or use of the Program is restricted in
230 certain countries either by patents or by copyrighted interfaces, the
231 original copyright holder who places the Program under this License
232 may add an explicit geographical distribution limitation excluding
233 those countries, so that distribution is permitted only in or among
234 countries not thus excluded. In such case, this License incorporates
235 the limitation as if written in the body of this License.
236
237 9. The Free Software Foundation may publish revised and/or new versions
238 of the General Public License from time to time. Such new versions will
239 be similar in spirit to the present version, but may differ in detail to
240 address new problems or concerns.
241
242 Each version is given a distinguishing version number. If the Program
243 specifies a version number of this License which applies to it and "any
244 later version", you have the option of following the terms and conditions
245 either of that version or of any later version published by the Free
246 Software Foundation. If the Program does not specify a version number of
247 this License, you may choose any version ever published by the Free Software
248 Foundation.
249
250 10. If you wish to incorporate parts of the Program into other free
251 programs whose distribution conditions are different, write to the author
252 to ask for permission. For software which is copyrighted by the Free
253 Software Foundation, write to the Free Software Foundation; we sometimes
254 make exceptions for this. Our decision will be guided by the two goals
255 of preserving the free status of all derivatives of our free software and
256 of promoting the sharing and reuse of software generally.
257
258 NO WARRANTY
259
260 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 REPAIR OR CORRECTION.
269
270 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 POSSIBILITY OF SUCH DAMAGES.
279
280 END OF TERMS AND CONDITIONS
281
282 How to Apply These Terms to Your New Programs
283
284 If you develop a new program, and you want it to be of the greatest
285 possible use to the public, the best way to achieve this is to make it
286 free software which everyone can redistribute and change under these terms.
287
288 To do so, attach the following notices to the program. It is safest
289 to attach them to the start of each source file to most effectively
290 convey the exclusion of warranty; and each file should have at least
291 the "copyright" line and a pointer to where the full notice is found.
292
293 <one line to give the program's name and a brief idea of what it does.>
294 Copyright (C) <year> <name of author>
295
296 This program is free software; you can redistribute it and/or modify
297 it under the terms of the GNU General Public License as published by
298 the Free Software Foundation; either version 2 of the License, or
299 (at your option) any later version.
300
301 This program is distributed in the hope that it will be useful,
302 but WITHOUT ANY WARRANTY; without even the implied warranty of
303 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 GNU General Public License for more details.
305
306 You should have received a copy of the GNU General Public License along
307 with this program; if not, write to the Free Software Foundation, Inc.,
308 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309
310 Also add information on how to contact you by electronic and paper mail.
311
312 If the program is interactive, make it output a short notice like this
313 when it starts in an interactive mode:
314
315 Gnomovision version 69, Copyright (C) year name of author
316 Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 This is free software, and you are welcome to redistribute it
318 under certain conditions; type `show c' for details.
319
320 The hypothetical commands `show w' and `show c' should show the appropriate
321 parts of the General Public License. Of course, the commands you use may
322 be called something other than `show w' and `show c'; they could even be
323 mouse-clicks or menu items--whatever suits your program.
324
325 You should also get your employer (if you work as a programmer) or your
326 school, if any, to sign a "copyright disclaimer" for the program, if
327 necessary. Here is a sample; alter the names:
328
329 Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 `Gnomovision' (which makes passes at compilers) written by James Hacker.
331
332 <signature of Ty Coon>, 1 April 1989
333 Ty Coon, President of Vice
334
335 This General Public License does not permit incorporating your program into
336 proprietary programs. If your program is a subroutine library, you may
337 consider it more useful to permit linking proprietary applications with the
338 library. If this is what you want to do, use the GNU Lesser General
339 Public License instead of this License.
@@ -0,0 +1,61
1 == redMine installation
2
3 redMine - project management software
4 Copyright (C) 2006 Jean-Philippe Lang
5 http://redmine.sourceforge.net/
6
7
8 == Requirements
9
10 * Ruby on Rails 1.1
11 * Any database supported by Rails (developped using MySQL 5)
12 * (recommended) Apache/Lighttpd with FCGI support
13
14
15 == Installation
16
17 1. Uncompress program archive:
18 tar zxvf <filename>
19
20 2. Create an empty database: "redmine" for example
21
22 3. Configure database parameters in config/database.yml
23 for "production" environment
24
25 4. Create database structure. Under application main directory:
26 rake migrate RAILS_ENV="production"
27 It will create tables and default configuration data
28
29 5. Test the installation by running WEBrick web server:
30 ruby script/server -e production
31
32 Once WEBrick has started, point your browser to http://localhost:3000/
33 You should now see the application welcome page
34
35 6. Use default administrator account to log in:
36 login: admin
37 password: admin
38
39 7. Setup Apache or Lighttpd with fastcgi for best performance.
40
41
42 == Configuration
43
44 You can setup a few things in config/environment.rb:
45 Don't forget to restart the application after any change.
46
47
48 config.action_mailer.server_settings: SMTP server configuration
49 config.action_mailer.perform_deliveries: set to false to disable mail delivering
50
51 RDM_HOST_NAME: hostname used to provide urls in notification mails
52
53 RDM_STORAGE_PATH: path for all attachments storage (default: "#{RAILS_ROOT}/files")
54 "#{RAILS_ROOT}/" represents application main directory
55
56 RDM_LOGIN_REQUIRED: set to true if you want to force users to login to access
57 any part of the application (default: false)
58
59 RDM_DEFAULT_LANG: default language for anonymous users: 'en' (default), 'es', 'fr' available
60
61
@@ -0,0 +1,49
1 == redMine
2
3 redMine - project management software
4 Copyright (C) 2006 Jean-Philippe Lang
5 http://redmine.sourceforge.net/
6
7 == License
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
23
24 == Main features
25
26 redMine is a project management software written using Ruby on Rails.
27
28 * multiple users/projects
29 * fully customizable role based access control
30 * issue tracking system
31 * fully customizable workflow
32 * documents/files repository
33 * email notifications
34 * multilanguage support
35
36
37 == Versioning
38
39 redMine versioning scheme is major.minor.revision
40 Versions before 1.0.0 must be considered as beta versions and upgrading support
41 may not be provided for these versions.
42
43
44 == Credits
45
46 * Jean-Francois Boutier (spanish translation)
47 * Andreas Viklund (open source XHTML layout, http://andreasviklund.com/)
48
49
@@ -0,0 +1,1
1 default directory for uploaded files No newline at end of file
@@ -0,0 +1,4
1 Localization.define('en', 'English') do |l|
2 l.store '(date)', lambda { |t| t.strftime('%m/%d/%Y') }
3 l.store '(time)', lambda { |t| t.strftime('%m/%d/%Y %I:%M%p') }
4 end No newline at end of file
@@ -0,0 +1,315
1 Localization.define('es', 'Español') do |l|
2
3 # trackers
4 l.store 'Bug', 'Anomalía'
5 l.store 'Feature request', 'Evolución'
6 l.store 'Support request', 'Asistencia'
7 # issue statuses
8 l.store 'New', 'Nuevo'
9 l.store 'Assigned', 'Asignada'
10 l.store 'Resolved', 'Resuelta'
11 l.store 'Closed', 'Cerrada'
12 l.store 'Rejected', 'Rechazada'
13 l.store 'Feedback', 'Comentario'
14
15 # issue priorities
16 l.store 'Issue priorities', 'Prioridad de las peticiones'
17 l.store 'Low', 'Bajo'
18 l.store 'Normal', 'Normal'
19 l.store 'High', 'Alto'
20 l.store 'Urgent', 'Urgente'
21 l.store 'Immediate', 'Ahora'
22 # document categories
23 l.store 'Document categories', 'Categorías del documento'
24 l.store 'Uncategorized', 'Sin categorías '
25 l.store 'User documentation', 'Documentación del usuario'
26 l.store 'Technical documentation', 'Documentación tecnica'
27 # dates
28 l.store '(date)', lambda { |t| t.strftime('%d/%m/%Y') }
29 l.store '(time)', lambda { |t| t.strftime('%d/%m/%Y %H:%M') }
30
31 # ./script/../config/../app/views/account/login.rhtml
32
33 # ./script/../config/../app/views/account/my_account.rhtml
34 l.store 'My account', 'Mi cuenta'
35 l.store 'Login', 'Identificador'
36 l.store 'Created on', 'Creado el'
37 l.store 'Last update', 'Actualizado'
38 l.store 'Information', 'Informaciones'
39 l.store 'Firstname', 'Nombre'
40 l.store 'Lastname', 'Apellido'
41 l.store 'Mail', 'Mail'
42 l.store 'Language', 'Lengua'
43 l.store 'Mail notifications', 'Notificación por mail'
44 l.store 'Save', 'Validar'
45 l.store 'Password', 'Contraseña'
46 l.store 'New password', 'Nueva contraseña'
47 l.store 'Confirmation', 'Confirmación'
48
49 # ./script/../config/../app/views/account/my_page.rhtml
50 l.store 'My page', 'Mi página'
51 l.store 'Welcome', 'Bienvenido'
52 l.store 'Last login', 'Última conexión'
53 l.store 'Reported issues', 'Peticiones registradas'
54 l.store 'Assigned to me', 'Peticiones que me están asignadas'
55
56 # ./script/../config/../app/views/account/show.rhtml
57 l.store 'Registered on', 'Inscrito el'
58 l.store 'Projects', 'Proyectos'
59 l.store 'Activity', 'Actividad'
60
61 # ./script/../config/../app/views/admin/index.rhtml
62 l.store 'Administration', 'Administración'
63 l.store 'Users', 'Usuarios'
64 l.store 'Roles and permissions', 'Papeles y permisos'
65 l.store 'Trackers', 'Trackers'
66 l.store 'Custom fields', 'Campos personalizados'
67 l.store 'Issue Statuses', 'Estatutos de las peticiones'
68 l.store 'Workflow', 'Workflow'
69 l.store 'Enumerations', 'Listas de valores'
70
71 # ./script/../config/../app/views/admin/info.rhtml
72 l.store 'Version', 'Versión'
73 l.store 'Database', 'Base de datos'
74
75 # ./script/../config/../app/views/admin/mail_options.rhtml
76 l.store 'Select actions for which mail notification should be enabled.', 'Seleccionar las actividades que necesitan la activación de la notificación por mail.'
77 l.store 'Check all', 'Seleccionar todo'
78 l.store 'Uncheck all', 'No seleccionar nada'
79
80 # ./script/../config/../app/views/admin/projects.rhtml
81 l.store 'Project', 'Proyecto'
82 l.store 'Description', 'Descripción'
83 l.store 'Public', 'Público'
84 l.store 'Delete', 'Suprimir'
85 l.store 'Previous', 'Precedente'
86 l.store 'Next', 'Próximo'
87
88 # ./script/../config/../app/views/custom_fields/edit.rhtml
89 l.store 'Custom field', 'Campo personalizado'
90
91 # ./script/../config/../app/views/custom_fields/list.rhtml
92 l.store 'Name', 'Nombre'
93 l.store 'Type', 'Tipo'
94 l.store 'Required', 'Obligatorio'
95 l.store 'For all projects', 'Para todos los proyectos'
96 l.store 'Used by', 'Utilizado por'
97
98 # ./script/../config/../app/views/custom_fields/new.rhtml
99 l.store 'New custom field', 'Nuevo campo personalizado'
100 l.store 'Create', 'Crear'
101
102 # ./script/../config/../app/views/custom_fields/_form.rhtml
103 l.store '0 means no restriction', '0 para ninguna restricción'
104 l.store 'Regular expression pattern', 'Expresión regular'
105 l.store 'Possible values', 'Valores posibles'
106
107 # ./script/../config/../app/views/documents/edit.rhtml
108 l.store 'Document', 'Documento'
109
110 # ./script/../config/../app/views/documents/show.rhtml
111 l.store 'Category', 'Categoría'
112 l.store 'Edit', 'Modificar'
113 l.store 'download', 'Telecarga'
114 l.store 'Add file', 'Añadir el fichero'
115 l.store 'Add', 'Añadir'
116
117 # ./script/../config/../app/views/documents/_form.rhtml
118 l.store 'Title', 'Título'
119
120 # ./script/../config/../app/views/enumerations/edit.rhtml
121
122 # ./script/../config/../app/views/enumerations/list.rhtml
123
124 # ./script/../config/../app/views/enumerations/new.rhtml
125 l.store 'New enumeration', 'Nuevo valor'
126
127 # ./script/../config/../app/views/enumerations/_form.rhtml
128
129 # ./script/../config/../app/views/issues/change_status.rhtml
130 l.store 'Issue', 'Petición'
131 l.store 'New status', 'Nuevo estatuto'
132 l.store 'Assigned to', 'Asignado a'
133 l.store 'Fixed in version', 'Versión corregida'
134 l.store 'Notes', 'Anotación'
135
136 # ./script/../config/../app/views/issues/edit.rhtml
137 l.store 'Status', 'Estatuto'
138 l.store 'Tracker', 'Tracker'
139 l.store 'Priority', 'Prioridad'
140 l.store 'Subject', 'Tema'
141
142 # ./script/../config/../app/views/issues/show.rhtml
143 l.store 'Author', 'Autor'
144 l.store 'Change status', 'Cambiar el estatuto'
145 l.store 'History', 'Histórico'
146 l.store 'Attachments', 'Ficheros'
147 l.store 'Update...', 'Actualizar...'
148
149 # ./script/../config/../app/views/issues/_list_simple.rhtml
150 l.store 'No issue', 'Ninguna petición'
151
152 # ./script/../config/../app/views/issue_categories/edit.rhtml
153
154 # ./script/../config/../app/views/issue_categories/_form.rhtml
155
156 # ./script/../config/../app/views/issue_statuses/edit.rhtml
157 l.store 'Issue status', 'Estatuto de petición'
158
159 # ./script/../config/../app/views/issue_statuses/list.rhtml
160 l.store 'Issue statuses', 'Estatutos de la petición'
161 l.store 'Default status', 'Estatuto por defecto'
162 l.store 'Issue closed', 'Petición resuelta'
163 l.store 'Color', 'Color'
164
165 # ./script/../config/../app/views/issue_statuses/new.rhtml
166 l.store 'New issue status', 'Nuevo estatuto'
167
168 # ./script/../config/../app/views/issue_statuses/_form.rhtml
169
170 # ./script/../config/../app/views/layouts/base.rhtml
171 l.store 'Home', 'Acogida'
172 l.store 'Help', 'Ayuda'
173 l.store 'Log in', 'Conexión'
174 l.store 'Logout', 'Desconexión'
175 l.store 'Overview', 'Vistazo'
176 l.store 'Issues', 'Peticiones'
177 l.store 'Reports', 'Rapports'
178 l.store 'News', 'Noticias'
179 l.store 'Change log', 'Cambios'
180 l.store 'Documents', 'Documentos'
181 l.store 'Members', 'Miembros'
182 l.store 'Files', 'Ficheros'
183 l.store 'Settings', 'Configuración'
184 l.store 'My projects', 'Mis proyectos'
185 l.store 'Logged as', 'Conectado como'
186
187 # ./script/../config/../app/views/mailer/issue_add.rhtml
188
189 # ./script/../config/../app/views/mailer/issue_change_status.rhtml
190
191 # ./script/../config/../app/views/mailer/_issue.rhtml
192
193 # ./script/../config/../app/views/news/edit.rhtml
194
195 # ./script/../config/../app/views/news/show.rhtml
196 l.store 'Summary', 'Resumen'
197 l.store 'By', 'Por'
198 l.store 'Date', 'Fecha'
199
200 # ./script/../config/../app/views/news/_form.rhtml
201
202 # ./script/../config/../app/views/projects/add.rhtml
203 l.store 'New project', 'Nuevo proyecto'
204
205 # ./script/../config/../app/views/projects/add_document.rhtml
206 l.store 'New document', 'Nuevo documento'
207 l.store 'File', 'Fichero'
208
209 # ./script/../config/../app/views/projects/add_issue.rhtml
210 l.store 'New issue', 'Nueva petición'
211 l.store 'Attachment', 'Fichero'
212
213 # ./script/../config/../app/views/projects/add_news.rhtml
214
215 # ./script/../config/../app/views/projects/add_version.rhtml
216 l.store 'New version', 'Nueva versión'
217
218 # ./script/../config/../app/views/projects/changelog.rhtml
219
220 # ./script/../config/../app/views/projects/destroy.rhtml
221 l.store 'Are you sure you want to delete project', '¿ Estás seguro de querer eliminar el proyecto ?'
222
223 # ./script/../config/../app/views/projects/list.rhtml
224 l.store 'Public projects', 'Proyectos publicos'
225
226 # ./script/../config/../app/views/projects/list_documents.rhtml
227 l.store 'Desciption', 'Descripción'
228
229 # ./script/../config/../app/views/projects/list_files.rhtml
230 l.store 'New file', 'Nuevo fichero'
231
232 # ./script/../config/../app/views/projects/list_issues.rhtml
233 l.store 'Apply filter', 'Aplicar'
234 l.store 'Reset', 'Anular'
235 l.store 'Report an issue', 'Nueva petición'
236
237 # ./script/../config/../app/views/projects/list_members.rhtml
238 l.store 'Project members', 'Miembros del proyecto'
239
240 # ./script/../config/../app/views/projects/list_news.rhtml
241 l.store 'Read...', 'Leer...'
242
243 # ./script/../config/../app/views/projects/settings.rhtml
244 l.store 'New member', 'Nuevo miembro'
245 l.store 'Versions', 'Versiónes'
246 l.store 'New version...', 'Nueva versión...'
247 l.store 'Issue categories', 'Categorías de las peticiones'
248 l.store 'New category', 'Nueva categoría'
249
250 # ./script/../config/../app/views/projects/show.rhtml
251 l.store 'Homepage', 'Sitio web'
252 l.store 'open', 'abierta(s)'
253 l.store 'View all issues', 'Ver todas las peticiones'
254 l.store 'View all news', 'Ver todas las noticias'
255 l.store 'Latest news', 'Últimas noticias'
256
257 # ./script/../config/../app/views/projects/_form.rhtml
258
259 # ./script/../config/../app/views/reports/issue_report.rhtml
260 l.store 'Issues by tracker', 'Peticiones por tracker'
261 l.store 'Issues by priority', 'Peticiones por prioridad'
262 l.store 'Issues by category', 'Peticiones por categoría'
263
264 # ./script/../config/../app/views/reports/_simple.rhtml
265 l.store 'Open', 'Abierta'
266 l.store 'Total', 'Total'
267
268 # ./script/../config/../app/views/roles/edit.rhtml
269 l.store 'Role', 'Papel'
270
271 # ./script/../config/../app/views/roles/list.rhtml
272 l.store 'Roles', 'Papeles'
273
274 # ./script/../config/../app/views/roles/new.rhtml
275 l.store 'New role', 'Nuevo papel'
276
277 # ./script/../config/../app/views/roles/workflow.rhtml
278 l.store 'Workflow setup', 'Configuración del workflow'
279 l.store 'Select a workflow to edit', 'Seleccionar un workflow para actualizar'
280 l.store 'New statuses allowed', 'Nuevos estatutos autorizados'
281
282 # ./script/../config/../app/views/roles/_form.rhtml
283 l.store 'Permissions', 'Permisos'
284
285 # ./script/../config/../app/views/trackers/edit.rhtml
286
287 # ./script/../config/../app/views/trackers/list.rhtml
288 l.store 'View issues in change log', 'Consultar las peticiones en el histórico'
289 l.store 'New tracker', 'Nuevo tracker'
290
291 # ./script/../config/../app/views/trackers/new.rhtml
292
293 # ./script/../config/../app/views/trackers/_form.rhtml
294
295 # ./script/../config/../app/views/users/add.rhtml
296 l.store 'New user', 'Nuevo usuario'
297
298 # ./script/../config/../app/views/users/edit.rhtml
299 l.store 'User', 'Usuario'
300
301 # ./script/../config/../app/views/users/list.rhtml
302 l.store 'Admin', 'Admin'
303 l.store 'Locked', 'Cerrado'
304
305 # ./script/../config/../app/views/users/_form.rhtml
306 l.store 'Administrator', 'Administrador'
307
308 # ./script/../config/../app/views/versions/edit.rhtml
309
310 # ./script/../config/../app/views/versions/_form.rhtml
311
312 # ./script/../config/../app/views/welcome/index.rhtml
313
314
315 end
@@ -0,0 +1,316
1 Localization.define('fr', 'Français') do |l|
2
3 # trackers
4 l.store 'Bug', 'Anomalie'
5 l.store 'Feature request', 'Evolution'
6 l.store 'Support request', 'Assistance'
7 # issue statuses
8 l.store 'New', 'Nouveau'
9 l.store 'Assigned', 'Assignée'
10 l.store 'Resolved', 'Résolue'
11 l.store 'Closed', 'Fermée'
12 l.store 'Rejected', 'Rejetée'
13 l.store 'Feedback', 'Commentaire'
14
15 # issue priorities
16 l.store 'Issue priorities', 'Priorités des demandes'
17 l.store 'Low', 'Bas'
18 l.store 'Normal', 'Normal'
19 l.store 'High', 'Haut'
20 l.store 'Urgent', 'Urgent'
21 l.store 'Immediate', 'Immédiat'
22 # document categories
23 l.store 'Document categories', 'Catégories de documents'
24 l.store 'Uncategorized', 'Sans catégorie'
25 l.store 'User documentation', 'Documentation utilisateur'
26 l.store 'Technical documentation', 'Documentation technique'
27 # dates
28 l.store '(date)', lambda { |t| t.strftime('%d/%m/%Y') }
29 l.store '(time)', lambda { |t| t.strftime('%d/%m/%Y %H:%M') }
30
31 # ./script/../config/../app/views/account/login.rhtml
32
33 # ./script/../config/../app/views/account/my_account.rhtml
34 l.store 'My account', 'Mon compte'
35 l.store 'Login', 'Identifiant'
36 l.store 'Created on', 'Crée le'
37 l.store 'Last update', 'Mis à jour'
38 l.store 'Information', 'Informations'
39 l.store 'Firstname', 'Prénom'
40 l.store 'Lastname', 'Nom'
41 l.store 'Mail', 'Mail'
42 l.store 'Language', 'Langue'
43 l.store 'Mail notifications', 'Notifications par mail'
44 l.store 'Save', 'Valider'
45 l.store 'Password', 'Mot de passe'
46 l.store 'New password', 'Nouveau mot de passe'
47 l.store 'Confirmation', 'Confirmation'
48
49 # ./script/../config/../app/views/account/my_page.rhtml
50 l.store 'My page', 'Ma page'
51 l.store 'Welcome', 'Bienvenue'
52 l.store 'Last login', 'Dernière connexion'
53 l.store 'Reported issues', 'Demandes soumises'
54 l.store 'Assigned to me', 'Demandes qui me sont assignées'
55
56 # ./script/../config/../app/views/account/show.rhtml
57 l.store 'Registered on', 'Inscrit le'
58 l.store 'Projects', 'Projets'
59 l.store 'Activity', 'Activité'
60
61 # ./script/../config/../app/views/admin/index.rhtml
62 l.store 'Administration', 'Administration'
63 l.store 'Users', 'Utilisateurs'
64 l.store 'Roles and permissions', 'Rôles et permissions'
65 l.store 'Trackers', 'Trackers'
66 l.store 'Custom fields', 'Champs personnalisés'
67 l.store 'Issue Statuses', 'Statuts des demandes'
68 l.store 'Workflow', 'Workflow'
69 l.store 'Enumerations', 'Listes de valeurs'
70
71 # ./script/../config/../app/views/admin/info.rhtml
72 l.store 'Version', 'Version'
73 l.store 'Database', 'Base de données'
74
75 # ./script/../config/../app/views/admin/mail_options.rhtml
76 l.store 'Select actions for which mail notification should be enabled.', 'Sélectionner les actions pour lesquelles la notification par mail doit être activée.'
77 l.store 'Check all', 'Cocher tout'
78 l.store 'Uncheck all', 'Décocher tout'
79
80 # ./script/../config/../app/views/admin/projects.rhtml
81 l.store 'Project', 'Projet'
82 l.store 'Description', 'Description'
83 l.store 'Public', 'Public'
84 l.store 'Delete', 'Supprimer'
85 l.store 'Previous', 'Précédent'
86 l.store 'Next', 'Suivant'
87
88 # ./script/../config/../app/views/custom_fields/edit.rhtml
89 l.store 'Custom field', 'Champ personnalisé'
90
91 # ./script/../config/../app/views/custom_fields/list.rhtml
92 l.store 'Name', 'Nom'
93 l.store 'Type', 'Type'
94 l.store 'Required', 'Obligatoire'
95 l.store 'For all projects', 'Pour tous les projets'
96 l.store 'Used by', 'Utilisé par'
97
98 # ./script/../config/../app/views/custom_fields/new.rhtml
99 l.store 'New custom field', 'Nouveau champ personnalisé'
100 l.store 'Create', 'Créer'
101
102 # ./script/../config/../app/views/custom_fields/_form.rhtml
103 l.store '0 means no restriction', '0 pour aucune restriction'
104 l.store 'Regular expression pattern', 'Expression régulière'
105 l.store 'Possible values', 'Valeurs possibles'
106
107 # ./script/../config/../app/views/documents/edit.rhtml
108 l.store 'Document', 'Document'
109
110 # ./script/../config/../app/views/documents/show.rhtml
111 l.store 'Category', 'Catégorie'
112 l.store 'Edit', 'Modifier'
113 l.store 'download', 'téléchargement'
114 l.store 'Add file', 'Ajouter le fichier'
115 l.store 'Add', 'Ajouter'
116
117 # ./script/../config/../app/views/documents/_form.rhtml
118 l.store 'Title', 'Titre'
119
120 # ./script/../config/../app/views/enumerations/edit.rhtml
121
122 # ./script/../config/../app/views/enumerations/list.rhtml
123
124 # ./script/../config/../app/views/enumerations/new.rhtml
125 l.store 'New enumeration', 'Nouvelle valeur'
126
127 # ./script/../config/../app/views/enumerations/_form.rhtml
128
129 # ./script/../config/../app/views/issues/change_status.rhtml
130 l.store 'Issue', 'Demande'
131 l.store 'New status', 'Nouveau statut'
132 l.store 'Assigned to', 'Assigné à'
133 l.store 'Fixed in version', 'Version corrigée'
134 l.store 'Notes', 'Remarques'
135
136 # ./script/../config/../app/views/issues/edit.rhtml
137 l.store 'Status', 'Statut'
138 l.store 'Tracker', 'Tracker'
139 l.store 'Priority', 'Priorité'
140 l.store 'Subject', 'Sujet'
141
142 # ./script/../config/../app/views/issues/show.rhtml
143 l.store 'Author', 'Auteur'
144 l.store 'Change status', 'Changer le statut'
145 l.store 'History', 'Historique'
146 l.store 'Attachments', 'Fichiers'
147 l.store 'Update...', 'Changer...'
148
149 # ./script/../config/../app/views/issues/_list_simple.rhtml
150 l.store 'No issue', 'Aucune demande'
151
152 # ./script/../config/../app/views/issue_categories/edit.rhtml
153
154 # ./script/../config/../app/views/issue_categories/_form.rhtml
155
156 # ./script/../config/../app/views/issue_statuses/edit.rhtml
157 l.store 'Issue status', 'Statut de demande'
158
159 # ./script/../config/../app/views/issue_statuses/list.rhtml
160 l.store 'Issue statuses', 'Statuts de demande'
161 l.store 'Default status', 'Statut par défaut'
162 l.store 'Issue closed', 'Demande fermée'
163 l.store 'Color', 'Couleur'
164
165 # ./script/../config/../app/views/issue_statuses/new.rhtml
166 l.store 'New issue status', 'Nouveau statut'
167
168 # ./script/../config/../app/views/issue_statuses/_form.rhtml
169
170 # ./script/../config/../app/views/layouts/base.rhtml
171 l.store 'Home', 'Accueil'
172 l.store 'Help', 'Aide'
173 l.store 'Log in', 'Connexion'
174 l.store 'Logout', 'Déconnexion'
175 l.store 'Overview', 'Aperçu'
176 l.store 'Issues', 'Demandes'
177 l.store 'Reports', 'Rapports'
178 l.store 'News', 'Annonces'
179 l.store 'Change log', 'Historique'
180 l.store 'Documents', 'Documents'
181 l.store 'Members', 'Membres'
182 l.store 'Files', 'Fichiers'
183 l.store 'Settings', 'Configuration'
184 l.store 'My projects', 'Mes projets'
185 l.store 'Logged as', 'Connecté en tant que'
186
187 # ./script/../config/../app/views/mailer/issue_add.rhtml
188
189 # ./script/../config/../app/views/mailer/issue_change_status.rhtml
190
191 # ./script/../config/../app/views/mailer/_issue.rhtml
192
193 # ./script/../config/../app/views/news/edit.rhtml
194
195 # ./script/../config/../app/views/news/show.rhtml
196 l.store 'Summary', 'Résumé'
197 l.store 'By', 'Par'
198 l.store 'Date', 'Date'
199
200 # ./script/../config/../app/views/news/_form.rhtml
201
202 # ./script/../config/../app/views/projects/add.rhtml
203 l.store 'New project', 'Nouveau projet'
204
205 # ./script/../config/../app/views/projects/add_document.rhtml
206 l.store 'New document', 'Nouveau document'
207 l.store 'File', 'Fichier'
208
209 # ./script/../config/../app/views/projects/add_issue.rhtml
210 l.store 'New issue', 'Nouvelle demande'
211 l.store 'Attachment', 'Fichier'
212
213 # ./script/../config/../app/views/projects/add_news.rhtml
214
215 # ./script/../config/../app/views/projects/add_version.rhtml
216 l.store 'New version', 'Nouvelle version'
217
218 # ./script/../config/../app/views/projects/changelog.rhtml
219
220 # ./script/../config/../app/views/projects/destroy.rhtml
221 l.store 'Are you sure you want to delete project', 'Êtes-vous sûr de vouloir supprimer le projet'
222
223 # ./script/../config/../app/views/projects/list.rhtml
224 l.store 'Public projects', 'Projets publics'
225
226 # ./script/../config/../app/views/projects/list_documents.rhtml
227 l.store 'Desciption', 'Description'
228
229 # ./script/../config/../app/views/projects/list_files.rhtml
230 l.store 'Files', 'Fichiers'
231 l.store 'New file', 'Nouveau fichier'
232
233 # ./script/../config/../app/views/projects/list_issues.rhtml
234 l.store 'Apply filter', 'Appliquer'
235 l.store 'Reset', 'Annuler'
236 l.store 'Report an issue', 'Nouvelle demande'
237
238 # ./script/../config/../app/views/projects/list_members.rhtml
239 l.store 'Project members', 'Membres du projet'
240
241 # ./script/../config/../app/views/projects/list_news.rhtml
242 l.store 'Read...', 'Lire...'
243
244 # ./script/../config/../app/views/projects/settings.rhtml
245 l.store 'New member', 'Nouveau membre'
246 l.store 'Versions', 'Versions'
247 l.store 'New version...', 'Nouvelle version...'
248 l.store 'Issue categories', 'Catégories des demandes'
249 l.store 'New category', 'Nouvelle catégorie'
250
251 # ./script/../config/../app/views/projects/show.rhtml
252 l.store 'Homepage', 'Site web'
253 l.store 'open', 'ouverte(s)'
254 l.store 'View all issues', 'Voir toutes les demandes'
255 l.store 'View all news', 'Voir toutes les annonces'
256 l.store 'Latest news', 'Dernières annonces'
257
258 # ./script/../config/../app/views/projects/_form.rhtml
259
260 # ./script/../config/../app/views/reports/issue_report.rhtml
261 l.store 'Issues by tracker', 'Demandes par tracker'
262 l.store 'Issues by priority', 'Demandes par priorité'
263 l.store 'Issues by category', 'Demandes par catégorie'
264
265 # ./script/../config/../app/views/reports/_simple.rhtml
266 l.store 'Open', 'Ouverte'
267 l.store 'Total', 'Total'
268
269 # ./script/../config/../app/views/roles/edit.rhtml
270 l.store 'Role', 'Rôle'
271
272 # ./script/../config/../app/views/roles/list.rhtml
273 l.store 'Roles', 'Rôles'
274
275 # ./script/../config/../app/views/roles/new.rhtml
276 l.store 'New role', 'Nouveau rôle'
277
278 # ./script/../config/../app/views/roles/workflow.rhtml
279 l.store 'Workflow setup', 'Configuration du workflow'
280 l.store 'Select a workflow to edit', 'Sélectionner un workflow à mettre à jour'
281 l.store 'New statuses allowed', 'Nouveaux statuts autorisés'
282
283 # ./script/../config/../app/views/roles/_form.rhtml
284 l.store 'Permissions', 'Permissions'
285
286 # ./script/../config/../app/views/trackers/edit.rhtml
287
288 # ./script/../config/../app/views/trackers/list.rhtml
289 l.store 'View issues in change log', 'Demandes affichées dans l\'historique'
290
291 # ./script/../config/../app/views/trackers/new.rhtml
292 l.store 'New tracker', 'Nouveau tracker'
293
294 # ./script/../config/../app/views/trackers/_form.rhtml
295
296 # ./script/../config/../app/views/users/add.rhtml
297 l.store 'New user', 'Nouvel utilisateur'
298
299 # ./script/../config/../app/views/users/edit.rhtml
300 l.store 'User', 'Utilisateur'
301
302 # ./script/../config/../app/views/users/list.rhtml
303 l.store 'Admin', 'Admin'
304 l.store 'Locked', 'Verrouillé'
305
306 # ./script/../config/../app/views/users/_form.rhtml
307 l.store 'Administrator', 'Administrateur'
308
309 # ./script/../config/../app/views/versions/edit.rhtml
310
311 # ./script/../config/../app/views/versions/_form.rhtml
312
313 # ./script/../config/../app/views/welcome/index.rhtml
314
315
316 end
@@ -0,0 +1,40
1 # General Apache options
2 AddHandler fastcgi-script .fcgi
3 AddHandler cgi-script .cgi
4 Options +FollowSymLinks +ExecCGI
5
6 # If you don't want Rails to look in certain directories,
7 # use the following rewrite rules so that Apache won't rewrite certain requests
8 #
9 # Example:
10 # RewriteCond %{REQUEST_URI} ^/notrails.*
11 # RewriteRule .* - [L]
12
13 # Redirect all requests not available on the filesystem to Rails
14 # By default the cgi dispatcher is used which is very slow
15 #
16 # For better performance replace the dispatcher with the fastcgi one
17 #
18 # Example:
19 # RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
20 RewriteEngine On
21
22 # If your Rails application is accessed via an Alias directive,
23 # then you MUST also set the RewriteBase in this htaccess file.
24 #
25 # Example:
26 # Alias /myrailsapp /path/to/myrailsapp/public
27 # RewriteBase /myrailsapp
28
29 RewriteRule ^$ index.html [QSA]
30 RewriteRule ^([^.]+)$ $1.html [QSA]
31 RewriteCond %{REQUEST_FILENAME} !-f
32 RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
33
34 # In case Rails experiences terminal errors
35 # Instead of displaying this message you can supply a file here which will be rendered instead
36 #
37 # Example:
38 # ErrorDocument 500 /500.html
39
40 ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly" No newline at end of file
@@ -0,0 +1,7
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2 "http://www.w3.org/TR/html4/loose.dtd">
3 <html>
4 <body>
5 <h1>File not found</h1>
6 </body>
7 </html> No newline at end of file
@@ -0,0 +1,7
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2 "http://www.w3.org/TR/html4/loose.dtd">
3 <html>
4 <body>
5 <h1>Sorry, an application error occured</h1>
6 </body>
7 </html> No newline at end of file
@@ -0,0 +1,10
1 #!e:/ruby/bin/ruby
2
3 require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
4
5 # If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
6 # "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
7 require "dispatcher"
8
9 ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
10 Dispatcher.dispatch No newline at end of file
@@ -0,0 +1,24
1 #!e:/ruby/bin/ruby
2 #
3 # You may specify the path to the FastCGI crash log (a log of unhandled
4 # exceptions which forced the FastCGI instance to exit, great for debugging)
5 # and the number of requests to process before running garbage collection.
6 #
7 # By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log
8 # and the GC period is nil (turned off). A reasonable number of requests
9 # could range from 10-100 depending on the memory footprint of your app.
10 #
11 # Example:
12 # # Default log path, normal GC behavior.
13 # RailsFCGIHandler.process!
14 #
15 # # Default log path, 50 requests between GC.
16 # RailsFCGIHandler.process! nil, 50
17 #
18 # # Custom log path, normal GC behavior.
19 # RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log'
20 #
21 require File.dirname(__FILE__) + "/../config/environment"
22 require 'fcgi_handler'
23
24 RailsFCGIHandler.process!
@@ -0,0 +1,10
1 #!e:/ruby/bin/ruby
2
3 require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
4
5 # If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
6 # "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
7 require "dispatcher"
8
9 ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
10 Dispatcher.dispatch No newline at end of file
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,8
1 function checkAll (id, checked) {
2 var el = document.getElementById(id);
3 for (var i = 0; i < el.elements.length; i++) {
4 if (el.elements[i].disabled==false) {
5 el.elements[i].checked = checked;
6 }
7 }
8 } No newline at end of file
This diff has been collapsed as it changes many lines, (750 lines changed) Show them Hide them
@@ -0,0 +1,750
1 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 // (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3 // (c) 2005 Jon Tirsen (http://www.tirsen.com)
4 // Contributors:
5 // Richard Livsey
6 // Rahul Bhargava
7 // Rob Wills
8 //
9 // See scriptaculous.js for full license.
10
11 // Autocompleter.Base handles all the autocompletion functionality
12 // that's independent of the data source for autocompletion. This
13 // includes drawing the autocompletion menu, observing keyboard
14 // and mouse events, and similar.
15 //
16 // Specific autocompleters need to provide, at the very least,
17 // a getUpdatedChoices function that will be invoked every time
18 // the text inside the monitored textbox changes. This method
19 // should get the text for which to provide autocompletion by
20 // invoking this.getToken(), NOT by directly accessing
21 // this.element.value. This is to allow incremental tokenized
22 // autocompletion. Specific auto-completion logic (AJAX, etc)
23 // belongs in getUpdatedChoices.
24 //
25 // Tokenized incremental autocompletion is enabled automatically
26 // when an autocompleter is instantiated with the 'tokens' option
27 // in the options parameter, e.g.:
28 // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
29 // will incrementally autocomplete with a comma as the token.
30 // Additionally, ',' in the above example can be replaced with
31 // a token array, e.g. { tokens: [',', '\n'] } which
32 // enables autocompletion on multiple tokens. This is most
33 // useful when one of the tokens is \n (a newline), as it
34 // allows smart autocompletion after linebreaks.
35
36 var Autocompleter = {}
37 Autocompleter.Base = function() {};
38 Autocompleter.Base.prototype = {
39 baseInitialize: function(element, update, options) {
40 this.element = $(element);
41 this.update = $(update);
42 this.hasFocus = false;
43 this.changed = false;
44 this.active = false;
45 this.index = 0;
46 this.entryCount = 0;
47
48 if (this.setOptions)
49 this.setOptions(options);
50 else
51 this.options = options || {};
52
53 this.options.paramName = this.options.paramName || this.element.name;
54 this.options.tokens = this.options.tokens || [];
55 this.options.frequency = this.options.frequency || 0.4;
56 this.options.minChars = this.options.minChars || 1;
57 this.options.onShow = this.options.onShow ||
58 function(element, update){
59 if(!update.style.position || update.style.position=='absolute') {
60 update.style.position = 'absolute';
61 Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
62 }
63 Effect.Appear(update,{duration:0.15});
64 };
65 this.options.onHide = this.options.onHide ||
66 function(element, update){ new Effect.Fade(update,{duration:0.15}) };
67
68 if (typeof(this.options.tokens) == 'string')
69 this.options.tokens = new Array(this.options.tokens);
70
71 this.observer = null;
72
73 this.element.setAttribute('autocomplete','off');
74
75 Element.hide(this.update);
76
77 Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
78 Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
79 },
80
81 show: function() {
82 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
83 if(!this.iefix &&
84 (navigator.appVersion.indexOf('MSIE')>0) &&
85 (navigator.userAgent.indexOf('Opera')<0) &&
86 (Element.getStyle(this.update, 'position')=='absolute')) {
87 new Insertion.After(this.update,
88 '<iframe id="' + this.update.id + '_iefix" '+
89 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
90 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
91 this.iefix = $(this.update.id+'_iefix');
92 }
93 if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
94 },
95
96 fixIEOverlapping: function() {
97 Position.clone(this.update, this.iefix);
98 this.iefix.style.zIndex = 1;
99 this.update.style.zIndex = 2;
100 Element.show(this.iefix);
101 },
102
103 hide: function() {
104 this.stopIndicator();
105 if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
106 if(this.iefix) Element.hide(this.iefix);
107 },
108
109 startIndicator: function() {
110 if(this.options.indicator) Element.show(this.options.indicator);
111 },
112
113 stopIndicator: function() {
114 if(this.options.indicator) Element.hide(this.options.indicator);
115 },
116
117 onKeyPress: function(event) {
118 if(this.active)
119 switch(event.keyCode) {
120 case Event.KEY_TAB:
121 case Event.KEY_RETURN:
122 this.selectEntry();
123 Event.stop(event);
124 case Event.KEY_ESC:
125 this.hide();
126 this.active = false;
127 Event.stop(event);
128 return;
129 case Event.KEY_LEFT:
130 case Event.KEY_RIGHT:
131 return;
132 case Event.KEY_UP:
133 this.markPrevious();
134 this.render();
135 if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
136 return;
137 case Event.KEY_DOWN:
138 this.markNext();
139 this.render();
140 if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
141 return;
142 }
143 else
144 if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
145 return;
146
147 this.changed = true;
148 this.hasFocus = true;
149
150 if(this.observer) clearTimeout(this.observer);
151 this.observer =
152 setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
153 },
154
155 onHover: function(event) {
156 var element = Event.findElement(event, 'LI');
157 if(this.index != element.autocompleteIndex)
158 {
159 this.index = element.autocompleteIndex;
160 this.render();
161 }
162 Event.stop(event);
163 },
164
165 onClick: function(event) {
166 var element = Event.findElement(event, 'LI');
167 this.index = element.autocompleteIndex;
168 this.selectEntry();
169 this.hide();
170 },
171
172 onBlur: function(event) {
173 // needed to make click events working
174 setTimeout(this.hide.bind(this), 250);
175 this.hasFocus = false;
176 this.active = false;
177 },
178
179 render: function() {
180 if(this.entryCount > 0) {
181 for (var i = 0; i < this.entryCount; i++)
182 this.index==i ?
183 Element.addClassName(this.getEntry(i),"selected") :
184 Element.removeClassName(this.getEntry(i),"selected");
185
186 if(this.hasFocus) {
187 this.show();
188 this.active = true;
189 }
190 } else {
191 this.active = false;
192 this.hide();
193 }
194 },
195
196 markPrevious: function() {
197 if(this.index > 0) this.index--
198 else this.index = this.entryCount-1;
199 },
200
201 markNext: function() {
202 if(this.index < this.entryCount-1) this.index++
203 else this.index = 0;
204 },
205
206 getEntry: function(index) {
207 return this.update.firstChild.childNodes[index];
208 },
209
210 getCurrentEntry: function() {
211 return this.getEntry(this.index);
212 },
213
214 selectEntry: function() {
215 this.active = false;
216 this.updateElement(this.getCurrentEntry());
217 },
218
219 updateElement: function(selectedElement) {
220 if (this.options.updateElement) {
221 this.options.updateElement(selectedElement);
222 return;
223 }
224
225 var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
226 var lastTokenPos = this.findLastToken();
227 if (lastTokenPos != -1) {
228 var newValue = this.element.value.substr(0, lastTokenPos + 1);
229 var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
230 if (whitespace)
231 newValue += whitespace[0];
232 this.element.value = newValue + value;
233 } else {
234 this.element.value = value;
235 }
236 this.element.focus();
237
238 if (this.options.afterUpdateElement)
239 this.options.afterUpdateElement(this.element, selectedElement);
240 },
241
242 updateChoices: function(choices) {
243 if(!this.changed && this.hasFocus) {
244 this.update.innerHTML = choices;
245 Element.cleanWhitespace(this.update);
246 Element.cleanWhitespace(this.update.firstChild);
247
248 if(this.update.firstChild && this.update.firstChild.childNodes) {
249 this.entryCount =
250 this.update.firstChild.childNodes.length;
251 for (var i = 0; i < this.entryCount; i++) {
252 var entry = this.getEntry(i);
253 entry.autocompleteIndex = i;
254 this.addObservers(entry);
255 }
256 } else {
257 this.entryCount = 0;
258 }
259
260 this.stopIndicator();
261
262 this.index = 0;
263 this.render();
264 }
265 },
266
267 addObservers: function(element) {
268 Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
269 Event.observe(element, "click", this.onClick.bindAsEventListener(this));
270 },
271
272 onObserverEvent: function() {
273 this.changed = false;
274 if(this.getToken().length>=this.options.minChars) {
275 this.startIndicator();
276 this.getUpdatedChoices();
277 } else {
278 this.active = false;
279 this.hide();
280 }
281 },
282
283 getToken: function() {
284 var tokenPos = this.findLastToken();
285 if (tokenPos != -1)
286 var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
287 else
288 var ret = this.element.value;
289
290 return /\n/.test(ret) ? '' : ret;
291 },
292
293 findLastToken: function() {
294 var lastTokenPos = -1;
295
296 for (var i=0; i<this.options.tokens.length; i++) {
297 var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
298 if (thisTokenPos > lastTokenPos)
299 lastTokenPos = thisTokenPos;
300 }
301 return lastTokenPos;
302 }
303 }
304
305 Ajax.Autocompleter = Class.create();
306 Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
307 initialize: function(element, update, url, options) {
308 this.baseInitialize(element, update, options);
309 this.options.asynchronous = true;
310 this.options.onComplete = this.onComplete.bind(this);
311 this.options.defaultParams = this.options.parameters || null;
312 this.url = url;
313 },
314
315 getUpdatedChoices: function() {
316 entry = encodeURIComponent(this.options.paramName) + '=' +
317 encodeURIComponent(this.getToken());
318
319 this.options.parameters = this.options.callback ?
320 this.options.callback(this.element, entry) : entry;
321
322 if(this.options.defaultParams)
323 this.options.parameters += '&' + this.options.defaultParams;
324
325 new Ajax.Request(this.url, this.options);
326 },
327
328 onComplete: function(request) {
329 this.updateChoices(request.responseText);
330 }
331
332 });
333
334 // The local array autocompleter. Used when you'd prefer to
335 // inject an array of autocompletion options into the page, rather
336 // than sending out Ajax queries, which can be quite slow sometimes.
337 //
338 // The constructor takes four parameters. The first two are, as usual,
339 // the id of the monitored textbox, and id of the autocompletion menu.
340 // The third is the array you want to autocomplete from, and the fourth
341 // is the options block.
342 //
343 // Extra local autocompletion options:
344 // - choices - How many autocompletion choices to offer
345 //
346 // - partialSearch - If false, the autocompleter will match entered
347 // text only at the beginning of strings in the
348 // autocomplete array. Defaults to true, which will
349 // match text at the beginning of any *word* in the
350 // strings in the autocomplete array. If you want to
351 // search anywhere in the string, additionally set
352 // the option fullSearch to true (default: off).
353 //
354 // - fullSsearch - Search anywhere in autocomplete array strings.
355 //
356 // - partialChars - How many characters to enter before triggering
357 // a partial match (unlike minChars, which defines
358 // how many characters are required to do any match
359 // at all). Defaults to 2.
360 //
361 // - ignoreCase - Whether to ignore case when autocompleting.
362 // Defaults to true.
363 //
364 // It's possible to pass in a custom function as the 'selector'
365 // option, if you prefer to write your own autocompletion logic.
366 // In that case, the other options above will not apply unless
367 // you support them.
368
369 Autocompleter.Local = Class.create();
370 Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
371 initialize: function(element, update, array, options) {
372 this.baseInitialize(element, update, options);
373 this.options.array = array;
374 },
375
376 getUpdatedChoices: function() {
377 this.updateChoices(this.options.selector(this));
378 },
379
380 setOptions: function(options) {
381 this.options = Object.extend({
382 choices: 10,
383 partialSearch: true,
384 partialChars: 2,
385 ignoreCase: true,
386 fullSearch: false,
387 selector: function(instance) {
388 var ret = []; // Beginning matches
389 var partial = []; // Inside matches
390 var entry = instance.getToken();
391 var count = 0;
392
393 for (var i = 0; i < instance.options.array.length &&
394 ret.length < instance.options.choices ; i++) {
395
396 var elem = instance.options.array[i];
397 var foundPos = instance.options.ignoreCase ?
398 elem.toLowerCase().indexOf(entry.toLowerCase()) :
399 elem.indexOf(entry);
400
401 while (foundPos != -1) {
402 if (foundPos == 0 && elem.length != entry.length) {
403 ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
404 elem.substr(entry.length) + "</li>");
405 break;
406 } else if (entry.length >= instance.options.partialChars &&
407 instance.options.partialSearch && foundPos != -1) {
408 if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
409 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
410 elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
411 foundPos + entry.length) + "</li>");
412 break;
413 }
414 }
415
416 foundPos = instance.options.ignoreCase ?
417 elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
418 elem.indexOf(entry, foundPos + 1);
419
420 }
421 }
422 if (partial.length)
423 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
424 return "<ul>" + ret.join('') + "</ul>";
425 }
426 }, options || {});
427 }
428 });
429
430 // AJAX in-place editor
431 //
432 // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
433
434 // Use this if you notice weird scrolling problems on some browsers,
435 // the DOM might be a bit confused when this gets called so do this
436 // waits 1 ms (with setTimeout) until it does the activation
437 Field.scrollFreeActivate = function(field) {
438 setTimeout(function() {
439 Field.activate(field);
440 }, 1);
441 }
442
443 Ajax.InPlaceEditor = Class.create();
444 Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
445 Ajax.InPlaceEditor.prototype = {
446 initialize: function(element, url, options) {
447 this.url = url;
448 this.element = $(element);
449
450 this.options = Object.extend({
451 okText: "ok",
452 cancelText: "cancel",
453 savingText: "Saving...",
454 clickToEditText: "Click to edit",
455 okText: "ok",
456 rows: 1,
457 onComplete: function(transport, element) {
458 new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
459 },
460 onFailure: function(transport) {
461 alert("Error communicating with the server: " + transport.responseText.stripTags());
462 },
463 callback: function(form) {
464 return Form.serialize(form);
465 },
466 handleLineBreaks: true,
467 loadingText: 'Loading...',
468 savingClassName: 'inplaceeditor-saving',
469 loadingClassName: 'inplaceeditor-loading',
470 formClassName: 'inplaceeditor-form',
471 highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
472 highlightendcolor: "#FFFFFF",
473 externalControl: null,
474 ajaxOptions: {}
475 }, options || {});
476
477 if(!this.options.formId && this.element.id) {
478 this.options.formId = this.element.id + "-inplaceeditor";
479 if ($(this.options.formId)) {
480 // there's already a form with that name, don't specify an id
481 this.options.formId = null;
482 }
483 }
484
485 if (this.options.externalControl) {
486 this.options.externalControl = $(this.options.externalControl);
487 }
488
489 this.originalBackground = Element.getStyle(this.element, 'background-color');
490 if (!this.originalBackground) {
491 this.originalBackground = "transparent";
492 }
493
494 this.element.title = this.options.clickToEditText;
495
496 this.onclickListener = this.enterEditMode.bindAsEventListener(this);
497 this.mouseoverListener = this.enterHover.bindAsEventListener(this);
498 this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
499 Event.observe(this.element, 'click', this.onclickListener);
500 Event.observe(this.element, 'mouseover', this.mouseoverListener);
501 Event.observe(this.element, 'mouseout', this.mouseoutListener);
502 if (this.options.externalControl) {
503 Event.observe(this.options.externalControl, 'click', this.onclickListener);
504 Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
505 Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
506 }
507 },
508 enterEditMode: function(evt) {
509 if (this.saving) return;
510 if (this.editing) return;
511 this.editing = true;
512 this.onEnterEditMode();
513 if (this.options.externalControl) {
514 Element.hide(this.options.externalControl);
515 }
516 Element.hide(this.element);
517 this.createForm();
518 this.element.parentNode.insertBefore(this.form, this.element);
519 Field.scrollFreeActivate(this.editField);
520 // stop the event to avoid a page refresh in Safari
521 if (evt) {
522 Event.stop(evt);
523 }
524 return false;
525 },
526 createForm: function() {
527 this.form = document.createElement("form");
528 this.form.id = this.options.formId;
529 Element.addClassName(this.form, this.options.formClassName)
530 this.form.onsubmit = this.onSubmit.bind(this);
531
532 this.createEditField();
533
534 if (this.options.textarea) {
535 var br = document.createElement("br");
536 this.form.appendChild(br);
537 }
538
539 okButton = document.createElement("input");
540 okButton.type = "submit";
541 okButton.value = this.options.okText;
542 this.form.appendChild(okButton);
543
544 cancelLink = document.createElement("a");
545 cancelLink.href = "#";
546 cancelLink.appendChild(document.createTextNode(this.options.cancelText));
547 cancelLink.onclick = this.onclickCancel.bind(this);
548 this.form.appendChild(cancelLink);
549 },
550 hasHTMLLineBreaks: function(string) {
551 if (!this.options.handleLineBreaks) return false;
552 return string.match(/<br/i) || string.match(/<p>/i);
553 },
554 convertHTMLLineBreaks: function(string) {
555 return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
556 },
557 createEditField: function() {
558 var text;
559 if(this.options.loadTextURL) {
560 text = this.options.loadingText;
561 } else {
562 text = this.getText();
563 }
564
565 if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
566 this.options.textarea = false;
567 var textField = document.createElement("input");
568 textField.type = "text";
569 textField.name = "value";
570 textField.value = text;
571 textField.style.backgroundColor = this.options.highlightcolor;
572 var size = this.options.size || this.options.cols || 0;
573 if (size != 0) textField.size = size;
574 this.editField = textField;
575 } else {
576 this.options.textarea = true;
577 var textArea = document.createElement("textarea");
578 textArea.name = "value";
579 textArea.value = this.convertHTMLLineBreaks(text);
580 textArea.rows = this.options.rows;
581 textArea.cols = this.options.cols || 40;
582 this.editField = textArea;
583 }
584
585 if(this.options.loadTextURL) {
586 this.loadExternalText();
587 }
588 this.form.appendChild(this.editField);
589 },
590 getText: function() {
591 return this.element.innerHTML;
592 },
593 loadExternalText: function() {
594 Element.addClassName(this.form, this.options.loadingClassName);
595 this.editField.disabled = true;
596 new Ajax.Request(
597 this.options.loadTextURL,
598 Object.extend({
599 asynchronous: true,
600 onComplete: this.onLoadedExternalText.bind(this)
601 }, this.options.ajaxOptions)
602 );
603 },
604 onLoadedExternalText: function(transport) {
605 Element.removeClassName(this.form, this.options.loadingClassName);
606 this.editField.disabled = false;
607 this.editField.value = transport.responseText.stripTags();
608 },
609 onclickCancel: function() {
610 this.onComplete();
611 this.leaveEditMode();
612 return false;
613 },
614 onFailure: function(transport) {
615 this.options.onFailure(transport);
616 if (this.oldInnerHTML) {
617 this.element.innerHTML = this.oldInnerHTML;
618 this.oldInnerHTML = null;
619 }
620 return false;
621 },
622 onSubmit: function() {
623 // onLoading resets these so we need to save them away for the Ajax call
624 var form = this.form;
625 var value = this.editField.value;
626
627 // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
628 // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
629 // to be displayed indefinitely
630 this.onLoading();
631
632 new Ajax.Updater(
633 {
634 success: this.element,
635 // don't update on failure (this could be an option)
636 failure: null
637 },
638 this.url,
639 Object.extend({
640 parameters: this.options.callback(form, value),
641 onComplete: this.onComplete.bind(this),
642 onFailure: this.onFailure.bind(this)
643 }, this.options.ajaxOptions)
644 );
645 // stop the event to avoid a page refresh in Safari
646 if (arguments.length > 1) {
647 Event.stop(arguments[0]);
648 }
649 return false;
650 },
651 onLoading: function() {
652 this.saving = true;
653 this.removeForm();
654 this.leaveHover();
655 this.showSaving();
656 },
657 showSaving: function() {
658 this.oldInnerHTML = this.element.innerHTML;
659 this.element.innerHTML = this.options.savingText;
660 Element.addClassName(this.element, this.options.savingClassName);
661 this.element.style.backgroundColor = this.originalBackground;
662 Element.show(this.element);
663 },
664 removeForm: function() {
665 if(this.form) {
666 if (this.form.parentNode) Element.remove(this.form);
667 this.form = null;
668 }
669 },
670 enterHover: function() {
671 if (this.saving) return;
672 this.element.style.backgroundColor = this.options.highlightcolor;
673 if (this.effect) {
674 this.effect.cancel();
675 }
676 Element.addClassName(this.element, this.options.hoverClassName)
677 },
678 leaveHover: function() {
679 if (this.options.backgroundColor) {
680 this.element.style.backgroundColor = this.oldBackground;
681 }
682 Element.removeClassName(this.element, this.options.hoverClassName)
683 if (this.saving) return;
684 this.effect = new Effect.Highlight(this.element, {
685 startcolor: this.options.highlightcolor,
686 endcolor: this.options.highlightendcolor,
687 restorecolor: this.originalBackground
688 });
689 },
690 leaveEditMode: function() {
691 Element.removeClassName(this.element, this.options.savingClassName);
692 this.removeForm();
693 this.leaveHover();
694 this.element.style.backgroundColor = this.originalBackground;
695 Element.show(this.element);
696 if (this.options.externalControl) {
697 Element.show(this.options.externalControl);
698 }
699 this.editing = false;
700 this.saving = false;
701 this.oldInnerHTML = null;
702 this.onLeaveEditMode();
703 },
704 onComplete: function(transport) {
705 this.leaveEditMode();
706 this.options.onComplete.bind(this)(transport, this.element);
707 },
708 onEnterEditMode: function() {},
709 onLeaveEditMode: function() {},
710 dispose: function() {
711 if (this.oldInnerHTML) {
712 this.element.innerHTML = this.oldInnerHTML;
713 }
714 this.leaveEditMode();
715 Event.stopObserving(this.element, 'click', this.onclickListener);
716 Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
717 Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
718 if (this.options.externalControl) {
719 Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
720 Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
721 Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
722 }
723 }
724 };
725
726 // Delayed observer, like Form.Element.Observer,
727 // but waits for delay after last key input
728 // Ideal for live-search fields
729
730 Form.Element.DelayedObserver = Class.create();
731 Form.Element.DelayedObserver.prototype = {
732 initialize: function(element, delay, callback) {
733 this.delay = delay || 0.5;
734 this.element = $(element);
735 this.callback = callback;
736 this.timer = null;
737 this.lastValue = $F(this.element);
738 Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
739 },
740 delayedListener: function(event) {
741 if(this.lastValue == $F(this.element)) return;
742 if(this.timer) clearTimeout(this.timer);
743 this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
744 this.lastValue = $F(this.element);
745 },
746 onTimerEvent: function() {
747 this.timer = null;
748 this.callback(this.element, $F(this.element));
749 }
750 }; No newline at end of file
This diff has been collapsed as it changes many lines, (584 lines changed) Show them Hide them
@@ -0,0 +1,584
1 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 //
3 // See scriptaculous.js for full license.
4
5 /*--------------------------------------------------------------------------*/
6
7 var Droppables = {
8 drops: [],
9
10 remove: function(element) {
11 this.drops = this.drops.reject(function(d) { return d.element==$(element) });
12 },
13
14 add: function(element) {
15 element = $(element);
16 var options = Object.extend({
17 greedy: true,
18 hoverclass: null
19 }, arguments[1] || {});
20
21 // cache containers
22 if(options.containment) {
23 options._containers = [];
24 var containment = options.containment;
25 if((typeof containment == 'object') &&
26 (containment.constructor == Array)) {
27 containment.each( function(c) { options._containers.push($(c)) });
28 } else {
29 options._containers.push($(containment));
30 }
31 }
32
33 if(options.accept) options.accept = [options.accept].flatten();
34
35 Element.makePositioned(element); // fix IE
36 options.element = element;
37
38 this.drops.push(options);
39 },
40
41 isContained: function(element, drop) {
42 var parentNode = element.parentNode;
43 return drop._containers.detect(function(c) { return parentNode == c });
44 },
45
46 isAffected: function(point, element, drop) {
47 return (
48 (drop.element!=element) &&
49 ((!drop._containers) ||
50 this.isContained(element, drop)) &&
51 ((!drop.accept) ||
52 (Element.classNames(element).detect(
53 function(v) { return drop.accept.include(v) } ) )) &&
54 Position.within(drop.element, point[0], point[1]) );
55 },
56
57 deactivate: function(drop) {
58 if(drop.hoverclass)
59 Element.removeClassName(drop.element, drop.hoverclass);
60 this.last_active = null;
61 },
62
63 activate: function(drop) {
64 if(drop.hoverclass)
65 Element.addClassName(drop.element, drop.hoverclass);
66 this.last_active = drop;
67 },
68
69 show: function(point, element) {
70 if(!this.drops.length) return;
71
72 if(this.last_active) this.deactivate(this.last_active);
73 this.drops.each( function(drop) {
74 if(Droppables.isAffected(point, element, drop)) {
75 if(drop.onHover)
76 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
77 if(drop.greedy) {
78 Droppables.activate(drop);
79 throw $break;
80 }
81 }
82 });
83 },
84
85 fire: function(event, element) {
86 if(!this.last_active) return;
87 Position.prepare();
88
89 if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
90 if (this.last_active.onDrop)
91 this.last_active.onDrop(element, this.last_active.element, event);
92 },
93
94 reset: function() {
95 if(this.last_active)
96 this.deactivate(this.last_active);
97 }
98 }
99
100 var Draggables = {
101 drags: [],
102 observers: [],
103
104 register: function(draggable) {
105 if(this.drags.length == 0) {
106 this.eventMouseUp = this.endDrag.bindAsEventListener(this);
107 this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
108 this.eventKeypress = this.keyPress.bindAsEventListener(this);
109
110 Event.observe(document, "mouseup", this.eventMouseUp);
111 Event.observe(document, "mousemove", this.eventMouseMove);
112 Event.observe(document, "keypress", this.eventKeypress);
113 }
114 this.drags.push(draggable);
115 },
116
117 unregister: function(draggable) {
118 this.drags = this.drags.reject(function(d) { return d==draggable });
119 if(this.drags.length == 0) {
120 Event.stopObserving(document, "mouseup", this.eventMouseUp);
121 Event.stopObserving(document, "mousemove", this.eventMouseMove);
122 Event.stopObserving(document, "keypress", this.eventKeypress);
123 }
124 },
125
126 activate: function(draggable) {
127 window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
128 this.activeDraggable = draggable;
129 },
130
131 deactivate: function(draggbale) {
132 this.activeDraggable = null;
133 },
134
135 updateDrag: function(event) {
136 if(!this.activeDraggable) return;
137 var pointer = [Event.pointerX(event), Event.pointerY(event)];
138 // Mozilla-based browsers fire successive mousemove events with
139 // the same coordinates, prevent needless redrawing (moz bug?)
140 if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
141 this._lastPointer = pointer;
142 this.activeDraggable.updateDrag(event, pointer);
143 },
144
145 endDrag: function(event) {
146 if(!this.activeDraggable) return;
147 this._lastPointer = null;
148 this.activeDraggable.endDrag(event);
149 },
150
151 keyPress: function(event) {
152 if(this.activeDraggable)
153 this.activeDraggable.keyPress(event);
154 },
155
156 addObserver: function(observer) {
157 this.observers.push(observer);
158 this._cacheObserverCallbacks();
159 },
160
161 removeObserver: function(element) { // element instead of observer fixes mem leaks
162 this.observers = this.observers.reject( function(o) { return o.element==element });
163 this._cacheObserverCallbacks();
164 },
165
166 notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
167 if(this[eventName+'Count'] > 0)
168 this.observers.each( function(o) {
169 if(o[eventName]) o[eventName](eventName, draggable, event);
170 });
171 },
172
173 _cacheObserverCallbacks: function() {
174 ['onStart','onEnd','onDrag'].each( function(eventName) {
175 Draggables[eventName+'Count'] = Draggables.observers.select(
176 function(o) { return o[eventName]; }
177 ).length;
178 });
179 }
180 }
181
182 /*--------------------------------------------------------------------------*/
183
184 var Draggable = Class.create();
185 Draggable.prototype = {
186 initialize: function(element) {
187 var options = Object.extend({
188 handle: false,
189 starteffect: function(element) {
190 new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
191 },
192 reverteffect: function(element, top_offset, left_offset) {
193 var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
194 element._revert = new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
195 },
196 endeffect: function(element) {
197 new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
198 },
199 zindex: 1000,
200 revert: false,
201 snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
202 }, arguments[1] || {});
203
204 this.element = $(element);
205
206 if(options.handle && (typeof options.handle == 'string'))
207 this.handle = Element.childrenWithClassName(this.element, options.handle)[0];
208 if(!this.handle) this.handle = $(options.handle);
209 if(!this.handle) this.handle = this.element;
210
211 Element.makePositioned(this.element); // fix IE
212
213 this.delta = this.currentDelta();
214 this.options = options;
215 this.dragging = false;
216
217 this.eventMouseDown = this.initDrag.bindAsEventListener(this);
218 Event.observe(this.handle, "mousedown", this.eventMouseDown);
219
220 Draggables.register(this);
221 },
222
223 destroy: function() {
224 Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
225 Draggables.unregister(this);
226 },
227
228 currentDelta: function() {
229 return([
230 parseInt(this.element.style.left || '0'),
231 parseInt(this.element.style.top || '0')]);
232 },
233
234 initDrag: function(event) {
235 if(Event.isLeftClick(event)) {
236 // abort on form elements, fixes a Firefox issue
237 var src = Event.element(event);
238 if(src.tagName && (
239 src.tagName=='INPUT' ||
240 src.tagName=='SELECT' ||
241 src.tagName=='BUTTON' ||
242 src.tagName=='TEXTAREA')) return;
243
244 if(this.element._revert) {
245 this.element._revert.cancel();
246 this.element._revert = null;
247 }
248
249 var pointer = [Event.pointerX(event), Event.pointerY(event)];
250 var pos = Position.cumulativeOffset(this.element);
251 this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
252
253 Draggables.activate(this);
254 Event.stop(event);
255 }
256 },
257
258 startDrag: function(event) {
259 this.dragging = true;
260
261 if(this.options.zindex) {
262 this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
263 this.element.style.zIndex = this.options.zindex;
264 }
265
266 if(this.options.ghosting) {
267 this._clone = this.element.cloneNode(true);
268 Position.absolutize(this.element);
269 this.element.parentNode.insertBefore(this._clone, this.element);
270 }
271
272 Draggables.notify('onStart', this, event);
273 if(this.options.starteffect) this.options.starteffect(this.element);
274 },
275
276 updateDrag: function(event, pointer) {
277 if(!this.dragging) this.startDrag(event);
278 Position.prepare();
279 Droppables.show(pointer, this.element);
280 Draggables.notify('onDrag', this, event);
281 this.draw(pointer);
282 if(this.options.change) this.options.change(this);
283
284 // fix AppleWebKit rendering
285 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
286 Event.stop(event);
287 },
288
289 finishDrag: function(event, success) {
290 this.dragging = false;
291
292 if(this.options.ghosting) {
293 Position.relativize(this.element);
294 Element.remove(this._clone);
295 this._clone = null;
296 }
297
298 if(success) Droppables.fire(event, this.element);
299 Draggables.notify('onEnd', this, event);
300
301 var revert = this.options.revert;
302 if(revert && typeof revert == 'function') revert = revert(this.element);
303
304 var d = this.currentDelta();
305 if(revert && this.options.reverteffect) {
306 this.options.reverteffect(this.element,
307 d[1]-this.delta[1], d[0]-this.delta[0]);
308 } else {
309 this.delta = d;
310 }
311
312 if(this.options.zindex)
313 this.element.style.zIndex = this.originalZ;
314
315 if(this.options.endeffect)
316 this.options.endeffect(this.element);
317
318 Draggables.deactivate(this);
319 Droppables.reset();
320 },
321
322 keyPress: function(event) {
323 if(!event.keyCode==Event.KEY_ESC) return;
324 this.finishDrag(event, false);
325 Event.stop(event);
326 },
327
328 endDrag: function(event) {
329 if(!this.dragging) return;
330 this.finishDrag(event, true);
331 Event.stop(event);
332 },
333
334 draw: function(point) {
335 var pos = Position.cumulativeOffset(this.element);
336 var d = this.currentDelta();
337 pos[0] -= d[0]; pos[1] -= d[1];
338
339 var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this));
340
341 if(this.options.snap) {
342 if(typeof this.options.snap == 'function') {
343 p = this.options.snap(p[0],p[1]);
344 } else {
345 if(this.options.snap instanceof Array) {
346 p = p.map( function(v, i) {
347 return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
348 } else {
349 p = p.map( function(v) {
350 return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
351 }
352 }}
353
354 var style = this.element.style;
355 if((!this.options.constraint) || (this.options.constraint=='horizontal'))
356 style.left = p[0] + "px";
357 if((!this.options.constraint) || (this.options.constraint=='vertical'))
358 style.top = p[1] + "px";
359 if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
360 }
361 }
362
363 /*--------------------------------------------------------------------------*/
364
365 var SortableObserver = Class.create();
366 SortableObserver.prototype = {
367 initialize: function(element, observer) {
368 this.element = $(element);
369 this.observer = observer;
370 this.lastValue = Sortable.serialize(this.element);
371 },
372
373 onStart: function() {
374 this.lastValue = Sortable.serialize(this.element);
375 },
376
377 onEnd: function() {
378 Sortable.unmark();
379 if(this.lastValue != Sortable.serialize(this.element))
380 this.observer(this.element)
381 }
382 }
383
384 var Sortable = {
385 sortables: new Array(),
386
387 options: function(element){
388 element = $(element);
389 return this.sortables.detect(function(s) { return s.element == element });
390 },
391
392 destroy: function(element){
393 element = $(element);
394 this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
395 Draggables.removeObserver(s.element);
396 s.droppables.each(function(d){ Droppables.remove(d) });
397 s.draggables.invoke('destroy');
398 });
399 this.sortables = this.sortables.reject(function(s) { return s.element == element });
400 },
401
402 create: function(element) {
403 element = $(element);
404 var options = Object.extend({
405 element: element,
406 tag: 'li', // assumes li children, override with tag: 'tagname'
407 dropOnEmpty: false,
408 tree: false, // fixme: unimplemented
409 overlap: 'vertical', // one of 'vertical', 'horizontal'
410 constraint: 'vertical', // one of 'vertical', 'horizontal', false
411 containment: element, // also takes array of elements (or id's); or false
412 handle: false, // or a CSS class
413 only: false,
414 hoverclass: null,
415 ghosting: false,
416 format: null,
417 onChange: Prototype.emptyFunction,
418 onUpdate: Prototype.emptyFunction
419 }, arguments[1] || {});
420
421 // clear any old sortable with same element
422 this.destroy(element);
423
424 // build options for the draggables
425 var options_for_draggable = {
426 revert: true,
427 ghosting: options.ghosting,
428 constraint: options.constraint,
429 handle: options.handle };
430
431 if(options.starteffect)
432 options_for_draggable.starteffect = options.starteffect;
433
434 if(options.reverteffect)
435 options_for_draggable.reverteffect = options.reverteffect;
436 else
437 if(options.ghosting) options_for_draggable.reverteffect = function(element) {
438 element.style.top = 0;
439 element.style.left = 0;
440 };
441
442 if(options.endeffect)
443 options_for_draggable.endeffect = options.endeffect;
444
445 if(options.zindex)
446 options_for_draggable.zindex = options.zindex;
447
448 // build options for the droppables
449 var options_for_droppable = {
450 overlap: options.overlap,
451 containment: options.containment,
452 hoverclass: options.hoverclass,
453 onHover: Sortable.onHover,
454 greedy: !options.dropOnEmpty
455 }
456
457 // fix for gecko engine
458 Element.cleanWhitespace(element);
459
460 options.draggables = [];
461 options.droppables = [];
462
463 // make it so
464
465 // drop on empty handling
466 if(options.dropOnEmpty) {
467 Droppables.add(element,
468 {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
469 options.droppables.push(element);
470 }
471
472 (this.findElements(element, options) || []).each( function(e) {
473 // handles are per-draggable
474 var handle = options.handle ?
475 Element.childrenWithClassName(e, options.handle)[0] : e;
476 options.draggables.push(
477 new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
478 Droppables.add(e, options_for_droppable);
479 options.droppables.push(e);
480 });
481
482 // keep reference
483 this.sortables.push(options);
484
485 // for onupdate
486 Draggables.addObserver(new SortableObserver(element, options.onUpdate));
487
488 },
489
490 // return all suitable-for-sortable elements in a guaranteed order
491 findElements: function(element, options) {
492 if(!element.hasChildNodes()) return null;
493 var elements = [];
494 $A(element.childNodes).each( function(e) {
495 if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() &&
496 (!options.only || (Element.hasClassName(e, options.only))))
497 elements.push(e);
498 if(options.tree) {
499 var grandchildren = this.findElements(e, options);
500 if(grandchildren) elements.push(grandchildren);
501 }
502 });
503
504 return (elements.length>0 ? elements.flatten() : null);
505 },
506
507 onHover: function(element, dropon, overlap) {
508 if(overlap>0.5) {
509 Sortable.mark(dropon, 'before');
510 if(dropon.previousSibling != element) {
511 var oldParentNode = element.parentNode;
512 element.style.visibility = "hidden"; // fix gecko rendering
513 dropon.parentNode.insertBefore(element, dropon);
514 if(dropon.parentNode!=oldParentNode)
515 Sortable.options(oldParentNode).onChange(element);
516 Sortable.options(dropon.parentNode).onChange(element);
517 }
518 } else {
519 Sortable.mark(dropon, 'after');
520 var nextElement = dropon.nextSibling || null;
521 if(nextElement != element) {
522 var oldParentNode = element.parentNode;
523 element.style.visibility = "hidden"; // fix gecko rendering
524 dropon.parentNode.insertBefore(element, nextElement);
525 if(dropon.parentNode!=oldParentNode)
526 Sortable.options(oldParentNode).onChange(element);
527 Sortable.options(dropon.parentNode).onChange(element);
528 }
529 }
530 },
531
532 onEmptyHover: function(element, dropon) {
533 if(element.parentNode!=dropon) {
534 var oldParentNode = element.parentNode;
535 dropon.appendChild(element);
536 Sortable.options(oldParentNode).onChange(element);
537 Sortable.options(dropon).onChange(element);
538 }
539 },
540
541 unmark: function() {
542 if(Sortable._marker) Element.hide(Sortable._marker);
543 },
544
545 mark: function(dropon, position) {
546 // mark on ghosting only
547 var sortable = Sortable.options(dropon.parentNode);
548 if(sortable && !sortable.ghosting) return;
549
550 if(!Sortable._marker) {
551 Sortable._marker = $('dropmarker') || document.createElement('DIV');
552 Element.hide(Sortable._marker);
553 Element.addClassName(Sortable._marker, 'dropmarker');
554 Sortable._marker.style.position = 'absolute';
555 document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
556 }
557 var offsets = Position.cumulativeOffset(dropon);
558 Sortable._marker.style.left = offsets[0] + 'px';
559 Sortable._marker.style.top = offsets[1] + 'px';
560
561 if(position=='after')
562 if(sortable.overlap == 'horizontal')
563 Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
564 else
565 Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
566
567 Element.show(Sortable._marker);
568 },
569
570 serialize: function(element) {
571 element = $(element);
572 var sortableOptions = this.options(element);
573 var options = Object.extend({
574 tag: sortableOptions.tag,
575 only: sortableOptions.only,
576 name: element.id,
577 format: sortableOptions.format || /^[^_]*_(.*)$/
578 }, arguments[1] || {});
579 return $(this.findElements(element, options) || []).map( function(item) {
580 return (encodeURIComponent(options.name) + "[]=" +
581 encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
582 }).join("&");
583 }
584 } No newline at end of file
This diff has been collapsed as it changes many lines, (854 lines changed) Show them Hide them
@@ -0,0 +1,854
1 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 // Contributors:
3 // Justin Palmer (http://encytemedia.com/)
4 // Mark Pilgrim (http://diveintomark.org/)
5 // Martin Bialasinki
6 //
7 // See scriptaculous.js for full license.
8
9 /* ------------- element ext -------------- */
10
11 // converts rgb() and #xxx to #xxxxxx format,
12 // returns self (or first argument) if not convertable
13 String.prototype.parseColor = function() {
14 var color = '#';
15 if(this.slice(0,4) == 'rgb(') {
16 var cols = this.slice(4,this.length-1).split(',');
17 var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
18 } else {
19 if(this.slice(0,1) == '#') {
20 if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
21 if(this.length==7) color = this.toLowerCase();
22 }
23 }
24 return(color.length==7 ? color : (arguments[0] || this));
25 }
26
27 Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
28 var children = $(element).childNodes;
29 var text = '';
30 var classtest = new RegExp('^([^ ]+ )*' + ignoreclass+ '( [^ ]+)*$','i');
31
32 for (var i = 0; i < children.length; i++) {
33 if(children[i].nodeType==3) {
34 text+=children[i].nodeValue;
35 } else {
36 if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
37 text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
38 }
39 }
40
41 return text;
42 }
43
44 Element.setStyle = function(element, style) {
45 element = $(element);
46 for(k in style) element.style[k.camelize()] = style[k];
47 }
48
49 Element.setContentZoom = function(element, percent) {
50 Element.setStyle(element, {fontSize: (percent/100) + 'em'});
51 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
52 }
53
54 Element.getOpacity = function(element){
55 var opacity;
56 if (opacity = Element.getStyle(element, 'opacity'))
57 return parseFloat(opacity);
58 if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))
59 if(opacity[1]) return parseFloat(opacity[1]) / 100;
60 return 1.0;
61 }
62
63 Element.setOpacity = function(element, value){
64 element= $(element);
65 if (value == 1){
66 Element.setStyle(element, { opacity:
67 (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
68 0.999999 : null });
69 if(/MSIE/.test(navigator.userAgent))
70 Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
71 } else {
72 if(value < 0.00001) value = 0;
73 Element.setStyle(element, {opacity: value});
74 if(/MSIE/.test(navigator.userAgent))
75 Element.setStyle(element,
76 { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
77 'alpha(opacity='+value*100+')' });
78 }
79 }
80
81 Element.getInlineOpacity = function(element){
82 return $(element).style.opacity || '';
83 }
84
85 Element.childrenWithClassName = function(element, className) {
86 return $A($(element).getElementsByTagName('*')).select(
87 function(c) { return Element.hasClassName(c, className) });
88 }
89
90 Array.prototype.call = function() {
91 var args = arguments;
92 this.each(function(f){ f.apply(this, args) });
93 }
94
95 /*--------------------------------------------------------------------------*/
96
97 var Effect = {
98 tagifyText: function(element) {
99 var tagifyStyle = 'position:relative';
100 if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
101 element = $(element);
102 $A(element.childNodes).each( function(child) {
103 if(child.nodeType==3) {
104 child.nodeValue.toArray().each( function(character) {
105 element.insertBefore(
106 Builder.node('span',{style: tagifyStyle},
107 character == ' ' ? String.fromCharCode(160) : character),
108 child);
109 });
110 Element.remove(child);
111 }
112 });
113 },
114 multiple: function(element, effect) {
115 var elements;
116 if(((typeof element == 'object') ||
117 (typeof element == 'function')) &&
118 (element.length))
119 elements = element;
120 else
121 elements = $(element).childNodes;
122
123 var options = Object.extend({
124 speed: 0.1,
125 delay: 0.0
126 }, arguments[2] || {});
127 var masterDelay = options.delay;
128
129 $A(elements).each( function(element, index) {
130 new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
131 });
132 }
133 };
134
135 var Effect2 = Effect; // deprecated
136
137 /* ------------- transitions ------------- */
138
139 Effect.Transitions = {}
140
141 Effect.Transitions.linear = function(pos) {
142 return pos;
143 }
144 Effect.Transitions.sinoidal = function(pos) {
145 return (-Math.cos(pos*Math.PI)/2) + 0.5;
146 }
147 Effect.Transitions.reverse = function(pos) {
148 return 1-pos;
149 }
150 Effect.Transitions.flicker = function(pos) {
151 return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
152 }
153 Effect.Transitions.wobble = function(pos) {
154 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
155 }
156 Effect.Transitions.pulse = function(pos) {
157 return (Math.floor(pos*10) % 2 == 0 ?
158 (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
159 }
160 Effect.Transitions.none = function(pos) {
161 return 0;
162 }
163 Effect.Transitions.full = function(pos) {
164 return 1;
165 }
166
167 /* ------------- core effects ------------- */
168
169 Effect.Queue = {
170 effects: [],
171 _each: function(iterator) {
172 this.effects._each(iterator);
173 },
174 interval: null,
175 add: function(effect) {
176 var timestamp = new Date().getTime();
177
178 switch(effect.options.queue) {
179 case 'front':
180 // move unstarted effects after this effect
181 this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
182 e.startOn += effect.finishOn;
183 e.finishOn += effect.finishOn;
184 });
185 break;
186 case 'end':
187 // start effect after last queued effect has finished
188 timestamp = this.effects.pluck('finishOn').max() || timestamp;
189 break;
190 }
191
192 effect.startOn += timestamp;
193 effect.finishOn += timestamp;
194 this.effects.push(effect);
195 if(!this.interval)
196 this.interval = setInterval(this.loop.bind(this), 40);
197 },
198 remove: function(effect) {
199 this.effects = this.effects.reject(function(e) { return e==effect });
200 if(this.effects.length == 0) {
201 clearInterval(this.interval);
202 this.interval = null;
203 }
204 },
205 loop: function() {
206 var timePos = new Date().getTime();
207 this.effects.invoke('loop', timePos);
208 }
209 }
210 Object.extend(Effect.Queue, Enumerable);
211
212 Effect.Base = function() {};
213 Effect.Base.prototype = {
214 position: null,
215 setOptions: function(options) {
216 this.options = Object.extend({
217 transition: Effect.Transitions.sinoidal,
218 duration: 1.0, // seconds
219 fps: 25.0, // max. 25fps due to Effect.Queue implementation
220 sync: false, // true for combining
221 from: 0.0,
222 to: 1.0,
223 delay: 0.0,
224 queue: 'parallel'
225 }, options || {});
226 },
227 start: function(options) {
228 this.setOptions(options || {});
229 this.currentFrame = 0;
230 this.state = 'idle';
231 this.startOn = this.options.delay*1000;
232 this.finishOn = this.startOn + (this.options.duration*1000);
233 this.event('beforeStart');
234 if(!this.options.sync) Effect.Queue.add(this);
235 },
236 loop: function(timePos) {
237 if(timePos >= this.startOn) {
238 if(timePos >= this.finishOn) {
239 this.render(1.0);
240 this.cancel();
241 this.event('beforeFinish');
242 if(this.finish) this.finish();
243 this.event('afterFinish');
244 return;
245 }
246 var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
247 var frame = Math.round(pos * this.options.fps * this.options.duration);
248 if(frame > this.currentFrame) {
249 this.render(pos);
250 this.currentFrame = frame;
251 }
252 }
253 },
254 render: function(pos) {
255 if(this.state == 'idle') {
256 this.state = 'running';
257 this.event('beforeSetup');
258 if(this.setup) this.setup();
259 this.event('afterSetup');
260 }
261 if(this.state == 'running') {
262 if(this.options.transition) pos = this.options.transition(pos);
263 pos *= (this.options.to-this.options.from);
264 pos += this.options.from;
265 this.position = pos;
266 this.event('beforeUpdate');
267 if(this.update) this.update(pos);
268 this.event('afterUpdate');
269 }
270 },
271 cancel: function() {
272 if(!this.options.sync) Effect.Queue.remove(this);
273 this.state = 'finished';
274 },
275 event: function(eventName) {
276 if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
277 if(this.options[eventName]) this.options[eventName](this);
278 },
279 inspect: function() {
280 return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
281 }
282 }
283
284 Effect.Parallel = Class.create();
285 Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
286 initialize: function(effects) {
287 this.effects = effects || [];
288 this.start(arguments[1]);
289 },
290 update: function(position) {
291 this.effects.invoke('render', position);
292 },
293 finish: function(position) {
294 this.effects.each( function(effect) {
295 effect.render(1.0);
296 effect.cancel();
297 effect.event('beforeFinish');
298 if(effect.finish) effect.finish(position);
299 effect.event('afterFinish');
300 });
301 }
302 });
303
304 Effect.Opacity = Class.create();
305 Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
306 initialize: function(element) {
307 this.element = $(element);
308 // make this work on IE on elements without 'layout'
309 if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
310 Element.setStyle(this.element, {zoom: 1});
311 var options = Object.extend({
312 from: Element.getOpacity(this.element) || 0.0,
313 to: 1.0
314 }, arguments[1] || {});
315 this.start(options);
316 },
317 update: function(position) {
318 Element.setOpacity(this.element, position);
319 }
320 });
321
322 Effect.MoveBy = Class.create();
323 Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
324 initialize: function(element, toTop, toLeft) {
325 this.element = $(element);
326 this.toTop = toTop;
327 this.toLeft = toLeft;
328 this.start(arguments[3]);
329 },
330 setup: function() {
331 // Bug in Opera: Opera returns the "real" position of a static element or
332 // relative element that does not have top/left explicitly set.
333 // ==> Always set top and left for position relative elements in your stylesheets
334 // (to 0 if you do not need them)
335 Element.makePositioned(this.element);
336 this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0');
337 this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
338 },
339 update: function(position) {
340 Element.setStyle(this.element, {
341 top: this.toTop * position + this.originalTop + 'px',
342 left: this.toLeft * position + this.originalLeft + 'px'
343 });
344 }
345 });
346
347 Effect.Scale = Class.create();
348 Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
349 initialize: function(element, percent) {
350 this.element = $(element)
351 var options = Object.extend({
352 scaleX: true,
353 scaleY: true,
354 scaleContent: true,
355 scaleFromCenter: false,
356 scaleMode: 'box', // 'box' or 'contents' or {} with provided values
357 scaleFrom: 100.0,
358 scaleTo: percent
359 }, arguments[2] || {});
360 this.start(options);
361 },
362 setup: function() {
363 this.restoreAfterFinish = this.options.restoreAfterFinish || false;
364 this.elementPositioning = Element.getStyle(this.element,'position');
365
366 this.originalStyle = {};
367 ['top','left','width','height','fontSize'].each( function(k) {
368 this.originalStyle[k] = this.element.style[k];
369 }.bind(this));
370
371 this.originalTop = this.element.offsetTop;
372 this.originalLeft = this.element.offsetLeft;
373
374 var fontSize = Element.getStyle(this.element,'font-size') || '100%';
375 ['em','px','%'].each( function(fontSizeType) {
376 if(fontSize.indexOf(fontSizeType)>0) {
377 this.fontSize = parseFloat(fontSize);
378 this.fontSizeType = fontSizeType;
379 }
380 }.bind(this));
381
382 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
383
384 this.dims = null;
385 if(this.options.scaleMode=='box')
386 this.dims = [this.element.offsetHeight, this.element.offsetWidth];
387 if(/^content/.test(this.options.scaleMode))
388 this.dims = [this.element.scrollHeight, this.element.scrollWidth];
389 if(!this.dims)
390 this.dims = [this.options.scaleMode.originalHeight,
391 this.options.scaleMode.originalWidth];
392 },
393 update: function(position) {
394 var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
395 if(this.options.scaleContent && this.fontSize)
396 Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType });
397 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
398 },
399 finish: function(position) {
400 if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle);
401 },
402 setDimensions: function(height, width) {
403 var d = {};
404 if(this.options.scaleX) d.width = width + 'px';
405 if(this.options.scaleY) d.height = height + 'px';
406 if(this.options.scaleFromCenter) {
407 var topd = (height - this.dims[0])/2;
408 var leftd = (width - this.dims[1])/2;
409 if(this.elementPositioning == 'absolute') {
410 if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
411 if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
412 } else {
413 if(this.options.scaleY) d.top = -topd + 'px';
414 if(this.options.scaleX) d.left = -leftd + 'px';
415 }
416 }
417 Element.setStyle(this.element, d);
418 }
419 });
420
421 Effect.Highlight = Class.create();
422 Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
423 initialize: function(element) {
424 this.element = $(element);
425 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
426 this.start(options);
427 },
428 setup: function() {
429 // Prevent executing on elements not in the layout flow
430 if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; }
431 // Disable background image during the effect
432 this.oldStyle = {
433 backgroundImage: Element.getStyle(this.element, 'background-image') };
434 Element.setStyle(this.element, {backgroundImage: 'none'});
435 if(!this.options.endcolor)
436 this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
437 if(!this.options.restorecolor)
438 this.options.restorecolor = Element.getStyle(this.element, 'background-color');
439 // init color calculations
440 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
441 this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
442 },
443 update: function(position) {
444 Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){
445 return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
446 },
447 finish: function() {
448 Element.setStyle(this.element, Object.extend(this.oldStyle, {
449 backgroundColor: this.options.restorecolor
450 }));
451 }
452 });
453
454 Effect.ScrollTo = Class.create();
455 Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
456 initialize: function(element) {
457 this.element = $(element);
458 this.start(arguments[1] || {});
459 },
460 setup: function() {
461 Position.prepare();
462 var offsets = Position.cumulativeOffset(this.element);
463 if(this.options.offset) offsets[1] += this.options.offset;
464 var max = window.innerHeight ?
465 window.height - window.innerHeight :
466 document.body.scrollHeight -
467 (document.documentElement.clientHeight ?
468 document.documentElement.clientHeight : document.body.clientHeight);
469 this.scrollStart = Position.deltaY;
470 this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
471 },
472 update: function(position) {
473 Position.prepare();
474 window.scrollTo(Position.deltaX,
475 this.scrollStart + (position*this.delta));
476 }
477 });
478
479 /* ------------- combination effects ------------- */
480
481 Effect.Fade = function(element) {
482 var oldOpacity = Element.getInlineOpacity(element);
483 var options = Object.extend({
484 from: Element.getOpacity(element) || 1.0,
485 to: 0.0,
486 afterFinishInternal: function(effect) { with(Element) {
487 if(effect.options.to!=0) return;
488 hide(effect.element);
489 setStyle(effect.element, {opacity: oldOpacity}); }}
490 }, arguments[1] || {});
491 return new Effect.Opacity(element,options);
492 }
493
494 Effect.Appear = function(element) {
495 var options = Object.extend({
496 from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0),
497 to: 1.0,
498 beforeSetup: function(effect) { with(Element) {
499 setOpacity(effect.element, effect.options.from);
500 show(effect.element); }}
501 }, arguments[1] || {});
502 return new Effect.Opacity(element,options);
503 }
504
505 Effect.Puff = function(element) {
506 element = $(element);
507 var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') };
508 return new Effect.Parallel(
509 [ new Effect.Scale(element, 200,
510 { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
511 new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
512 Object.extend({ duration: 1.0,
513 beforeSetupInternal: function(effect) { with(Element) {
514 setStyle(effect.effects[0].element, {position: 'absolute'}); }},
515 afterFinishInternal: function(effect) { with(Element) {
516 hide(effect.effects[0].element);
517 setStyle(effect.effects[0].element, oldStyle); }}
518 }, arguments[1] || {})
519 );
520 }
521
522 Effect.BlindUp = function(element) {
523 element = $(element);
524 Element.makeClipping(element);
525 return new Effect.Scale(element, 0,
526 Object.extend({ scaleContent: false,
527 scaleX: false,
528 restoreAfterFinish: true,
529 afterFinishInternal: function(effect) { with(Element) {
530 [hide, undoClipping].call(effect.element); }}
531 }, arguments[1] || {})
532 );
533 }
534
535 Effect.BlindDown = function(element) {
536 element = $(element);
537 var oldHeight = Element.getStyle(element, 'height');
538 var elementDimensions = Element.getDimensions(element);
539 return new Effect.Scale(element, 100,
540 Object.extend({ scaleContent: false,
541 scaleX: false,
542 scaleFrom: 0,
543 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
544 restoreAfterFinish: true,
545 afterSetup: function(effect) { with(Element) {
546 makeClipping(effect.element);
547 setStyle(effect.element, {height: '0px'});
548 show(effect.element);
549 }},
550 afterFinishInternal: function(effect) { with(Element) {
551 undoClipping(effect.element);
552 setStyle(effect.element, {height: oldHeight});
553 }}
554 }, arguments[1] || {})
555 );
556 }
557
558 Effect.SwitchOff = function(element) {
559 element = $(element);
560 var oldOpacity = Element.getInlineOpacity(element);
561 return new Effect.Appear(element, {
562 duration: 0.4,
563 from: 0,
564 transition: Effect.Transitions.flicker,
565 afterFinishInternal: function(effect) {
566 new Effect.Scale(effect.element, 1, {
567 duration: 0.3, scaleFromCenter: true,
568 scaleX: false, scaleContent: false, restoreAfterFinish: true,
569 beforeSetup: function(effect) { with(Element) {
570 [makePositioned,makeClipping].call(effect.element);
571 }},
572 afterFinishInternal: function(effect) { with(Element) {
573 [hide,undoClipping,undoPositioned].call(effect.element);
574 setStyle(effect.element, {opacity: oldOpacity});
575 }}
576 })
577 }
578 });
579 }
580
581 Effect.DropOut = function(element) {
582 element = $(element);
583 var oldStyle = {
584 top: Element.getStyle(element, 'top'),
585 left: Element.getStyle(element, 'left'),
586 opacity: Element.getInlineOpacity(element) };
587 return new Effect.Parallel(
588 [ new Effect.MoveBy(element, 100, 0, { sync: true }),
589 new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
590 Object.extend(
591 { duration: 0.5,
592 beforeSetup: function(effect) { with(Element) {
593 makePositioned(effect.effects[0].element); }},
594 afterFinishInternal: function(effect) { with(Element) {
595 [hide, undoPositioned].call(effect.effects[0].element);
596 setStyle(effect.effects[0].element, oldStyle); }}
597 }, arguments[1] || {}));
598 }
599
600 Effect.Shake = function(element) {
601 element = $(element);
602 var oldStyle = {
603 top: Element.getStyle(element, 'top'),
604 left: Element.getStyle(element, 'left') };
605 return new Effect.MoveBy(element, 0, 20,
606 { duration: 0.05, afterFinishInternal: function(effect) {
607 new Effect.MoveBy(effect.element, 0, -40,
608 { duration: 0.1, afterFinishInternal: function(effect) {
609 new Effect.MoveBy(effect.element, 0, 40,
610 { duration: 0.1, afterFinishInternal: function(effect) {
611 new Effect.MoveBy(effect.element, 0, -40,
612 { duration: 0.1, afterFinishInternal: function(effect) {
613 new Effect.MoveBy(effect.element, 0, 40,
614 { duration: 0.1, afterFinishInternal: function(effect) {
615 new Effect.MoveBy(effect.element, 0, -20,
616 { duration: 0.05, afterFinishInternal: function(effect) { with(Element) {
617 undoPositioned(effect.element);
618 setStyle(effect.element, oldStyle);
619 }}}) }}) }}) }}) }}) }});
620 }
621
622 Effect.SlideDown = function(element) {
623 element = $(element);
624 Element.cleanWhitespace(element);
625 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
626 var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
627 var elementDimensions = Element.getDimensions(element);
628 return new Effect.Scale(element, 100, Object.extend({
629 scaleContent: false,
630 scaleX: false,
631 scaleFrom: 0,
632 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
633 restoreAfterFinish: true,
634 afterSetup: function(effect) { with(Element) {
635 makePositioned(effect.element);
636 makePositioned(effect.element.firstChild);
637 if(window.opera) setStyle(effect.element, {top: ''});
638 makeClipping(effect.element);
639 setStyle(effect.element, {height: '0px'});
640 show(element); }},
641 afterUpdateInternal: function(effect) { with(Element) {
642 setStyle(effect.element.firstChild, {bottom:
643 (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
644 afterFinishInternal: function(effect) { with(Element) {
645 undoClipping(effect.element);
646 undoPositioned(effect.element.firstChild);
647 undoPositioned(effect.element);
648 setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
649 }, arguments[1] || {})
650 );
651 }
652
653 Effect.SlideUp = function(element) {
654 element = $(element);
655 Element.cleanWhitespace(element);
656 var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
657 return new Effect.Scale(element, 0,
658 Object.extend({ scaleContent: false,
659 scaleX: false,
660 scaleMode: 'box',
661 scaleFrom: 100,
662 restoreAfterFinish: true,
663 beforeStartInternal: function(effect) { with(Element) {
664 makePositioned(effect.element);
665 makePositioned(effect.element.firstChild);
666 if(window.opera) setStyle(effect.element, {top: ''});
667 makeClipping(effect.element);
668 show(element); }},
669 afterUpdateInternal: function(effect) { with(Element) {
670 setStyle(effect.element.firstChild, {bottom:
671 (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
672 afterFinishInternal: function(effect) { with(Element) {
673 [hide, undoClipping].call(effect.element);
674 undoPositioned(effect.element.firstChild);
675 undoPositioned(effect.element);
676 setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
677 }, arguments[1] || {})
678 );
679 }
680
681 // Bug in opera makes the TD containing this element expand for a instance after finish
682 Effect.Squish = function(element) {
683 return new Effect.Scale(element, window.opera ? 1 : 0,
684 { restoreAfterFinish: true,
685 beforeSetup: function(effect) { with(Element) {
686 makeClipping(effect.element); }},
687 afterFinishInternal: function(effect) { with(Element) {
688 hide(effect.element);
689 undoClipping(effect.element); }}
690 });
691 }
692
693 Effect.Grow = function(element) {
694 element = $(element);
695 var options = Object.extend({
696 direction: 'center',
697 moveTransistion: Effect.Transitions.sinoidal,
698 scaleTransition: Effect.Transitions.sinoidal,
699 opacityTransition: Effect.Transitions.full
700 }, arguments[1] || {});
701 var oldStyle = {
702 top: element.style.top,
703 left: element.style.left,
704 height: element.style.height,
705 width: element.style.width,
706 opacity: Element.getInlineOpacity(element) };
707
708 var dims = Element.getDimensions(element);
709 var initialMoveX, initialMoveY;
710 var moveX, moveY;
711
712 switch (options.direction) {
713 case 'top-left':
714 initialMoveX = initialMoveY = moveX = moveY = 0;
715 break;
716 case 'top-right':
717 initialMoveX = dims.width;
718 initialMoveY = moveY = 0;
719 moveX = -dims.width;
720 break;
721 case 'bottom-left':
722 initialMoveX = moveX = 0;
723 initialMoveY = dims.height;
724 moveY = -dims.height;
725 break;
726 case 'bottom-right':
727 initialMoveX = dims.width;
728 initialMoveY = dims.height;
729 moveX = -dims.width;
730 moveY = -dims.height;
731 break;
732 case 'center':
733 initialMoveX = dims.width / 2;
734 initialMoveY = dims.height / 2;
735 moveX = -dims.width / 2;
736 moveY = -dims.height / 2;
737 break;
738 }
739
740 return new Effect.MoveBy(element, initialMoveY, initialMoveX, {
741 duration: 0.01,
742 beforeSetup: function(effect) { with(Element) {
743 hide(effect.element);
744 makeClipping(effect.element);
745 makePositioned(effect.element);
746 }},
747 afterFinishInternal: function(effect) {
748 new Effect.Parallel(
749 [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
750 new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: options.moveTransition }),
751 new Effect.Scale(effect.element, 100, {
752 scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
753 sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
754 ], Object.extend({
755 beforeSetup: function(effect) { with(Element) {
756 setStyle(effect.effects[0].element, {height: '0px'});
757 show(effect.effects[0].element); }},
758 afterFinishInternal: function(effect) { with(Element) {
759 [undoClipping, undoPositioned].call(effect.effects[0].element);
760 setStyle(effect.effects[0].element, oldStyle); }}
761 }, options)
762 )
763 }
764 });
765 }
766
767 Effect.Shrink = function(element) {
768 element = $(element);
769 var options = Object.extend({
770 direction: 'center',
771 moveTransistion: Effect.Transitions.sinoidal,
772 scaleTransition: Effect.Transitions.sinoidal,
773 opacityTransition: Effect.Transitions.none
774 }, arguments[1] || {});
775 var oldStyle = {
776 top: element.style.top,
777 left: element.style.left,
778 height: element.style.height,
779 width: element.style.width,
780 opacity: Element.getInlineOpacity(element) };
781
782 var dims = Element.getDimensions(element);
783 var moveX, moveY;
784
785 switch (options.direction) {
786 case 'top-left':
787 moveX = moveY = 0;
788 break;
789 case 'top-right':
790 moveX = dims.width;
791 moveY = 0;
792 break;
793 case 'bottom-left':
794 moveX = 0;
795 moveY = dims.height;
796 break;
797 case 'bottom-right':
798 moveX = dims.width;
799 moveY = dims.height;
800 break;
801 case 'center':
802 moveX = dims.width / 2;
803 moveY = dims.height / 2;
804 break;
805 }
806
807 return new Effect.Parallel(
808 [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
809 new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
810 new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: options.moveTransition })
811 ], Object.extend({
812 beforeStartInternal: function(effect) { with(Element) {
813 [makePositioned, makeClipping].call(effect.effects[0].element) }},
814 afterFinishInternal: function(effect) { with(Element) {
815 [hide, undoClipping, undoPositioned].call(effect.effects[0].element);
816 setStyle(effect.effects[0].element, oldStyle); }}
817 }, options)
818 );
819 }
820
821 Effect.Pulsate = function(element) {
822 element = $(element);
823 var options = arguments[1] || {};
824 var oldOpacity = Element.getInlineOpacity(element);
825 var transition = options.transition || Effect.Transitions.sinoidal;
826 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
827 reverser.bind(transition);
828 return new Effect.Opacity(element,
829 Object.extend(Object.extend({ duration: 3.0, from: 0,
830 afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); }
831 }, options), {transition: reverser}));
832 }
833
834 Effect.Fold = function(element) {
835 element = $(element);
836 var oldStyle = {
837 top: element.style.top,
838 left: element.style.left,
839 width: element.style.width,
840 height: element.style.height };
841 Element.makeClipping(element);
842 return new Effect.Scale(element, 5, Object.extend({
843 scaleContent: false,
844 scaleX: false,
845 afterFinishInternal: function(effect) {
846 new Effect.Scale(element, 1, {
847 scaleContent: false,
848 scaleY: false,
849 afterFinishInternal: function(effect) { with(Element) {
850 [hide, undoClipping].call(effect.element);
851 setStyle(effect.element, oldStyle);
852 }} });
853 }}, arguments[1] || {}));
854 }
This diff has been collapsed as it changes many lines, (1785 lines changed) Show them Hide them
@@ -0,0 +1,1785
1 /* Prototype JavaScript framework, version 1.4.0
2 * (c) 2005 Sam Stephenson <sam@conio.net>
3 *
4 * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
5 * against the source tree, available from the Prototype darcs repository.
6 *
7 * Prototype is freely distributable under the terms of an MIT-style license.
8 *
9 * For details, see the Prototype web site: http://prototype.conio.net/
10 *
11 /*--------------------------------------------------------------------------*/
12
13 var Prototype = {
14 Version: '1.4.0',
15 ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
16
17 emptyFunction: function() {},
18 K: function(x) {return x}
19 }
20
21 var Class = {
22 create: function() {
23 return function() {
24 this.initialize.apply(this, arguments);
25 }
26 }
27 }
28
29 var Abstract = new Object();
30
31 Object.extend = function(destination, source) {
32 for (property in source) {
33 destination[property] = source[property];
34 }
35 return destination;
36 }
37
38 Object.inspect = function(object) {
39 try {
40 if (object == undefined) return 'undefined';
41 if (object == null) return 'null';
42 return object.inspect ? object.inspect() : object.toString();
43 } catch (e) {
44 if (e instanceof RangeError) return '...';
45 throw e;
46 }
47 }
48
49 Function.prototype.bind = function() {
50 var __method = this, args = $A(arguments), object = args.shift();
51 return function() {
52 return __method.apply(object, args.concat($A(arguments)));
53 }
54 }
55
56 Function.prototype.bindAsEventListener = function(object) {
57 var __method = this;
58 return function(event) {
59 return __method.call(object, event || window.event);
60 }
61 }
62
63 Object.extend(Number.prototype, {
64 toColorPart: function() {
65 var digits = this.toString(16);
66 if (this < 16) return '0' + digits;
67 return digits;
68 },
69
70 succ: function() {
71 return this + 1;
72 },
73
74 times: function(iterator) {
75 $R(0, this, true).each(iterator);
76 return this;
77 }
78 });
79
80 var Try = {
81 these: function() {
82 var returnValue;
83
84 for (var i = 0; i < arguments.length; i++) {
85 var lambda = arguments[i];
86 try {
87 returnValue = lambda();
88 break;
89 } catch (e) {}
90 }
91
92 return returnValue;
93 }
94 }
95
96 /*--------------------------------------------------------------------------*/
97
98 var PeriodicalExecuter = Class.create();
99 PeriodicalExecuter.prototype = {
100 initialize: function(callback, frequency) {
101 this.callback = callback;
102 this.frequency = frequency;
103 this.currentlyExecuting = false;
104
105 this.registerCallback();
106 },
107
108 registerCallback: function() {
109 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
110 },
111
112 onTimerEvent: function() {
113 if (!this.currentlyExecuting) {
114 try {
115 this.currentlyExecuting = true;
116 this.callback();
117 } finally {
118 this.currentlyExecuting = false;
119 }
120 }
121 }
122 }
123
124 /*--------------------------------------------------------------------------*/
125
126 function $() {
127 var elements = new Array();
128
129 for (var i = 0; i < arguments.length; i++) {
130 var element = arguments[i];
131 if (typeof element == 'string')
132 element = document.getElementById(element);
133
134 if (arguments.length == 1)
135 return element;
136
137 elements.push(element);
138 }
139
140 return elements;
141 }
142 Object.extend(String.prototype, {
143 stripTags: function() {
144 return this.replace(/<\/?[^>]+>/gi, '');
145 },
146
147 stripScripts: function() {
148 return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
149 },
150
151 extractScripts: function() {
152 var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
153 var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
154 return (this.match(matchAll) || []).map(function(scriptTag) {
155 return (scriptTag.match(matchOne) || ['', ''])[1];
156 });
157 },
158
159 evalScripts: function() {
160 return this.extractScripts().map(eval);
161 },
162
163 escapeHTML: function() {
164 var div = document.createElement('div');
165 var text = document.createTextNode(this);
166 div.appendChild(text);
167 return div.innerHTML;
168 },
169
170 unescapeHTML: function() {
171 var div = document.createElement('div');
172 div.innerHTML = this.stripTags();
173 return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
174 },
175
176 toQueryParams: function() {
177 var pairs = this.match(/^\??(.*)$/)[1].split('&');
178 return pairs.inject({}, function(params, pairString) {
179 var pair = pairString.split('=');
180 params[pair[0]] = pair[1];
181 return params;
182 });
183 },
184
185 toArray: function() {
186 return this.split('');
187 },
188
189 camelize: function() {
190 var oStringList = this.split('-');
191 if (oStringList.length == 1) return oStringList[0];
192
193 var camelizedString = this.indexOf('-') == 0
194 ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
195 : oStringList[0];
196
197 for (var i = 1, len = oStringList.length; i < len; i++) {
198 var s = oStringList[i];
199 camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
200 }
201
202 return camelizedString;
203 },
204
205 inspect: function() {
206 return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
207 }
208 });
209
210 String.prototype.parseQuery = String.prototype.toQueryParams;
211
212 var $break = new Object();
213 var $continue = new Object();
214
215 var Enumerable = {
216 each: function(iterator) {
217 var index = 0;
218 try {
219 this._each(function(value) {
220 try {
221 iterator(value, index++);
222 } catch (e) {
223 if (e != $continue) throw e;
224 }
225 });
226 } catch (e) {
227 if (e != $break) throw e;
228 }
229 },
230
231 all: function(iterator) {
232 var result = true;
233 this.each(function(value, index) {
234 result = result && !!(iterator || Prototype.K)(value, index);
235 if (!result) throw $break;
236 });
237 return result;
238 },
239
240 any: function(iterator) {
241 var result = true;
242 this.each(function(value, index) {
243 if (result = !!(iterator || Prototype.K)(value, index))
244 throw $break;
245 });
246 return result;
247 },
248
249 collect: function(iterator) {
250 var results = [];
251 this.each(function(value, index) {
252 results.push(iterator(value, index));
253 });
254 return results;
255 },
256
257 detect: function (iterator) {
258 var result;
259 this.each(function(value, index) {
260 if (iterator(value, index)) {
261 result = value;
262 throw $break;
263 }
264 });
265 return result;
266 },
267
268 findAll: function(iterator) {
269 var results = [];
270 this.each(function(value, index) {
271 if (iterator(value, index))
272 results.push(value);
273 });
274 return results;
275 },
276
277 grep: function(pattern, iterator) {
278 var results = [];
279 this.each(function(value, index) {
280 var stringValue = value.toString();
281 if (stringValue.match(pattern))
282 results.push((iterator || Prototype.K)(value, index));
283 })
284 return results;
285 },
286
287 include: function(object) {
288 var found = false;
289 this.each(function(value) {
290 if (value == object) {
291 found = true;
292 throw $break;
293 }
294 });
295 return found;
296 },
297
298 inject: function(memo, iterator) {
299 this.each(function(value, index) {
300 memo = iterator(memo, value, index);
301 });
302 return memo;
303 },
304
305 invoke: function(method) {
306 var args = $A(arguments).slice(1);
307 return this.collect(function(value) {
308 return value[method].apply(value, args);
309 });
310 },
311
312 max: function(iterator) {
313 var result;
314 this.each(function(value, index) {
315 value = (iterator || Prototype.K)(value, index);
316 if (value >= (result || value))
317 result = value;
318 });
319 return result;
320 },
321
322 min: function(iterator) {
323 var result;
324 this.each(function(value, index) {
325 value = (iterator || Prototype.K)(value, index);
326 if (value <= (result || value))
327 result = value;
328 });
329 return result;
330 },
331
332 partition: function(iterator) {
333 var trues = [], falses = [];
334 this.each(function(value, index) {
335 ((iterator || Prototype.K)(value, index) ?
336 trues : falses).push(value);
337 });
338 return [trues, falses];
339 },
340
341 pluck: function(property) {
342 var results = [];
343 this.each(function(value, index) {
344 results.push(value[property]);
345 });
346 return results;
347 },
348
349 reject: function(iterator) {
350 var results = [];
351 this.each(function(value, index) {
352 if (!iterator(value, index))
353 results.push(value);
354 });
355 return results;
356 },
357
358 sortBy: function(iterator) {
359 return this.collect(function(value, index) {
360 return {value: value, criteria: iterator(value, index)};
361 }).sort(function(left, right) {
362 var a = left.criteria, b = right.criteria;
363 return a < b ? -1 : a > b ? 1 : 0;
364 }).pluck('value');
365 },
366
367 toArray: function() {
368 return this.collect(Prototype.K);
369 },
370
371 zip: function() {
372 var iterator = Prototype.K, args = $A(arguments);
373 if (typeof args.last() == 'function')
374 iterator = args.pop();
375
376 var collections = [this].concat(args).map($A);
377 return this.map(function(value, index) {
378 iterator(value = collections.pluck(index));
379 return value;
380 });
381 },
382
383 inspect: function() {
384 return '#<Enumerable:' + this.toArray().inspect() + '>';
385 }
386 }
387
388 Object.extend(Enumerable, {
389 map: Enumerable.collect,
390 find: Enumerable.detect,
391 select: Enumerable.findAll,
392 member: Enumerable.include,
393 entries: Enumerable.toArray
394 });
395 var $A = Array.from = function(iterable) {
396 if (!iterable) return [];
397 if (iterable.toArray) {
398 return iterable.toArray();
399 } else {
400 var results = [];
401 for (var i = 0; i < iterable.length; i++)
402 results.push(iterable[i]);
403 return results;
404 }
405 }
406
407 Object.extend(Array.prototype, Enumerable);
408
409 Array.prototype._reverse = Array.prototype.reverse;
410
411 Object.extend(Array.prototype, {
412 _each: function(iterator) {
413 for (var i = 0; i < this.length; i++)
414 iterator(this[i]);
415 },
416
417 clear: function() {
418 this.length = 0;
419 return this;
420 },
421
422 first: function() {
423 return this[0];
424 },
425
426 last: function() {
427 return this[this.length - 1];
428 },
429
430 compact: function() {
431 return this.select(function(value) {
432 return value != undefined || value != null;
433 });
434 },
435
436 flatten: function() {
437 return this.inject([], function(array, value) {
438 return array.concat(value.constructor == Array ?
439 value.flatten() : [value]);
440 });
441 },
442
443 without: function() {
444 var values = $A(arguments);
445 return this.select(function(value) {
446 return !values.include(value);
447 });
448 },
449
450 indexOf: function(object) {
451 for (var i = 0; i < this.length; i++)
452 if (this[i] == object) return i;
453 return -1;
454 },
455
456 reverse: function(inline) {
457 return (inline !== false ? this : this.toArray())._reverse();
458 },
459
460 shift: function() {
461 var result = this[0];
462 for (var i = 0; i < this.length - 1; i++)
463 this[i] = this[i + 1];
464 this.length--;
465 return result;
466 },
467
468 inspect: function() {
469 return '[' + this.map(Object.inspect).join(', ') + ']';
470 }
471 });
472 var Hash = {
473 _each: function(iterator) {
474 for (key in this) {
475 var value = this[key];
476 if (typeof value == 'function') continue;
477
478 var pair = [key, value];
479 pair.key = key;
480 pair.value = value;
481 iterator(pair);
482 }
483 },
484
485 keys: function() {
486 return this.pluck('key');
487 },
488
489 values: function() {
490 return this.pluck('value');
491 },
492
493 merge: function(hash) {
494 return $H(hash).inject($H(this), function(mergedHash, pair) {
495 mergedHash[pair.key] = pair.value;
496 return mergedHash;
497 });
498 },
499
500 toQueryString: function() {
501 return this.map(function(pair) {
502 return pair.map(encodeURIComponent).join('=');
503 }).join('&');
504 },
505
506 inspect: function() {
507 return '#<Hash:{' + this.map(function(pair) {
508 return pair.map(Object.inspect).join(': ');
509 }).join(', ') + '}>';
510 }
511 }
512
513 function $H(object) {
514 var hash = Object.extend({}, object || {});
515 Object.extend(hash, Enumerable);
516 Object.extend(hash, Hash);
517 return hash;
518 }
519 ObjectRange = Class.create();
520 Object.extend(ObjectRange.prototype, Enumerable);
521 Object.extend(ObjectRange.prototype, {
522 initialize: function(start, end, exclusive) {
523 this.start = start;
524 this.end = end;
525 this.exclusive = exclusive;
526 },
527
528 _each: function(iterator) {
529 var value = this.start;
530 do {
531 iterator(value);
532 value = value.succ();
533 } while (this.include(value));
534 },
535
536 include: function(value) {
537 if (value < this.start)
538 return false;
539 if (this.exclusive)
540 return value < this.end;
541 return value <= this.end;
542 }
543 });
544
545 var $R = function(start, end, exclusive) {
546 return new ObjectRange(start, end, exclusive);
547 }
548
549 var Ajax = {
550 getTransport: function() {
551 return Try.these(
552 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
553 function() {return new ActiveXObject('Microsoft.XMLHTTP')},
554 function() {return new XMLHttpRequest()}
555 ) || false;
556 },
557
558 activeRequestCount: 0
559 }
560
561 Ajax.Responders = {
562 responders: [],
563
564 _each: function(iterator) {
565 this.responders._each(iterator);
566 },
567
568 register: function(responderToAdd) {
569 if (!this.include(responderToAdd))
570 this.responders.push(responderToAdd);
571 },
572
573 unregister: function(responderToRemove) {
574 this.responders = this.responders.without(responderToRemove);
575 },
576
577 dispatch: function(callback, request, transport, json) {
578 this.each(function(responder) {
579 if (responder[callback] && typeof responder[callback] == 'function') {
580 try {
581 responder[callback].apply(responder, [request, transport, json]);
582 } catch (e) {}
583 }
584 });
585 }
586 };
587
588 Object.extend(Ajax.Responders, Enumerable);
589
590 Ajax.Responders.register({
591 onCreate: function() {
592 Ajax.activeRequestCount++;
593 },
594
595 onComplete: function() {
596 Ajax.activeRequestCount--;
597 }
598 });
599
600 Ajax.Base = function() {};
601 Ajax.Base.prototype = {
602 setOptions: function(options) {
603 this.options = {
604 method: 'post',
605 asynchronous: true,
606 parameters: ''
607 }
608 Object.extend(this.options, options || {});
609 },
610
611 responseIsSuccess: function() {
612 return this.transport.status == undefined
613 || this.transport.status == 0
614 || (this.transport.status >= 200 && this.transport.status < 300);
615 },
616
617 responseIsFailure: function() {
618 return !this.responseIsSuccess();
619 }
620 }
621
622 Ajax.Request = Class.create();
623 Ajax.Request.Events =
624 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
625
626 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
627 initialize: function(url, options) {
628 this.transport = Ajax.getTransport();
629 this.setOptions(options);
630 this.request(url);
631 },
632
633 request: function(url) {
634 var parameters = this.options.parameters || '';
635 if (parameters.length > 0) parameters += '&_=';
636
637 try {
638 this.url = url;
639 if (this.options.method == 'get' && parameters.length > 0)
640 this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
641
642 Ajax.Responders.dispatch('onCreate', this, this.transport);
643
644 this.transport.open(this.options.method, this.url,
645 this.options.asynchronous);
646
647 if (this.options.asynchronous) {
648 this.transport.onreadystatechange = this.onStateChange.bind(this);
649 setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
650 }
651
652 this.setRequestHeaders();
653
654 var body = this.options.postBody ? this.options.postBody : parameters;
655 this.transport.send(this.options.method == 'post' ? body : null);
656
657 } catch (e) {
658 this.dispatchException(e);
659 }
660 },
661
662 setRequestHeaders: function() {
663 var requestHeaders =
664 ['X-Requested-With', 'XMLHttpRequest',
665 'X-Prototype-Version', Prototype.Version];
666
667 if (this.options.method == 'post') {
668 requestHeaders.push('Content-type',
669 'application/x-www-form-urlencoded');
670
671 /* Force "Connection: close" for Mozilla browsers to work around
672 * a bug where XMLHttpReqeuest sends an incorrect Content-length
673 * header. See Mozilla Bugzilla #246651.
674 */
675 if (this.transport.overrideMimeType)
676 requestHeaders.push('Connection', 'close');
677 }
678
679 if (this.options.requestHeaders)
680 requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
681
682 for (var i = 0; i < requestHeaders.length; i += 2)
683 this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
684 },
685
686 onStateChange: function() {
687 var readyState = this.transport.readyState;
688 if (readyState != 1)
689 this.respondToReadyState(this.transport.readyState);
690 },
691
692 header: function(name) {
693 try {
694 return this.transport.getResponseHeader(name);
695 } catch (e) {}
696 },
697
698 evalJSON: function() {
699 try {
700 return eval(this.header('X-JSON'));
701 } catch (e) {}
702 },
703
704 evalResponse: function() {
705 try {
706 return eval(this.transport.responseText);
707 } catch (e) {
708 this.dispatchException(e);
709 }
710 },
711
712 respondToReadyState: function(readyState) {
713 var event = Ajax.Request.Events[readyState];
714 var transport = this.transport, json = this.evalJSON();
715
716 if (event == 'Complete') {
717 try {
718 (this.options['on' + this.transport.status]
719 || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
720 || Prototype.emptyFunction)(transport, json);
721 } catch (e) {
722 this.dispatchException(e);
723 }
724
725 if ((this.header('Content-type') || '').match(/^text\/javascript/i))
726 this.evalResponse();
727 }
728
729 try {
730 (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
731 Ajax.Responders.dispatch('on' + event, this, transport, json);
732 } catch (e) {
733 this.dispatchException(e);
734 }
735
736 /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
737 if (event == 'Complete')
738 this.transport.onreadystatechange = Prototype.emptyFunction;
739 },
740
741 dispatchException: function(exception) {
742 (this.options.onException || Prototype.emptyFunction)(this, exception);
743 Ajax.Responders.dispatch('onException', this, exception);
744 }
745 });
746
747 Ajax.Updater = Class.create();
748
749 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
750 initialize: function(container, url, options) {
751 this.containers = {
752 success: container.success ? $(container.success) : $(container),
753 failure: container.failure ? $(container.failure) :
754 (container.success ? null : $(container))
755 }
756
757 this.transport = Ajax.getTransport();
758 this.setOptions(options);
759
760 var onComplete = this.options.onComplete || Prototype.emptyFunction;
761 this.options.onComplete = (function(transport, object) {
762 this.updateContent();
763 onComplete(transport, object);
764 }).bind(this);
765
766 this.request(url);
767 },
768
769 updateContent: function() {
770 var receiver = this.responseIsSuccess() ?
771 this.containers.success : this.containers.failure;
772 var response = this.transport.responseText;
773
774 if (!this.options.evalScripts)
775 response = response.stripScripts();
776
777 if (receiver) {
778 if (this.options.insertion) {
779 new this.options.insertion(receiver, response);
780 } else {
781 Element.update(receiver, response);
782 }
783 }
784
785 if (this.responseIsSuccess()) {
786 if (this.onComplete)
787 setTimeout(this.onComplete.bind(this), 10);
788 }
789 }
790 });
791
792 Ajax.PeriodicalUpdater = Class.create();
793 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
794 initialize: function(container, url, options) {
795 this.setOptions(options);
796 this.onComplete = this.options.onComplete;
797
798 this.frequency = (this.options.frequency || 2);
799 this.decay = (this.options.decay || 1);
800
801 this.updater = {};
802 this.container = container;
803 this.url = url;
804
805 this.start();
806 },
807
808 start: function() {
809 this.options.onComplete = this.updateComplete.bind(this);
810 this.onTimerEvent();
811 },
812
813 stop: function() {
814 this.updater.onComplete = undefined;
815 clearTimeout(this.timer);
816 (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
817 },
818
819 updateComplete: function(request) {
820 if (this.options.decay) {
821 this.decay = (request.responseText == this.lastText ?
822 this.decay * this.options.decay : 1);
823
824 this.lastText = request.responseText;
825 }
826 this.timer = setTimeout(this.onTimerEvent.bind(this),
827 this.decay * this.frequency * 1000);
828 },
829
830 onTimerEvent: function() {
831 this.updater = new Ajax.Updater(this.container, this.url, this.options);
832 }
833 });
834 document.getElementsByClassName = function(className, parentElement) {
835 var children = ($(parentElement) || document.body).getElementsByTagName('*');
836 return $A(children).inject([], function(elements, child) {
837 if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
838 elements.push(child);
839 return elements;
840 });
841 }
842
843 /*--------------------------------------------------------------------------*/
844
845 if (!window.Element) {
846 var Element = new Object();
847 }
848
849 Object.extend(Element, {
850 visible: function(element) {
851 return $(element).style.display != 'none';
852 },
853
854 toggle: function() {
855 for (var i = 0; i < arguments.length; i++) {
856 var element = $(arguments[i]);
857 Element[Element.visible(element) ? 'hide' : 'show'](element);
858 }
859 },
860
861 hide: function() {
862 for (var i = 0; i < arguments.length; i++) {
863 var element = $(arguments[i]);
864 element.style.display = 'none';
865 }
866 },
867
868 show: function() {
869 for (var i = 0; i < arguments.length; i++) {
870 var element = $(arguments[i]);
871 element.style.display = '';
872 }
873 },
874
875 remove: function(element) {
876 element = $(element);
877 element.parentNode.removeChild(element);
878 },
879
880 update: function(element, html) {
881 $(element).innerHTML = html.stripScripts();
882 setTimeout(function() {html.evalScripts()}, 10);
883 },
884
885 getHeight: function(element) {
886 element = $(element);
887 return element.offsetHeight;
888 },
889
890 classNames: function(element) {
891 return new Element.ClassNames(element);
892 },
893
894 hasClassName: function(element, className) {
895 if (!(element = $(element))) return;
896 return Element.classNames(element).include(className);
897 },
898
899 addClassName: function(element, className) {
900 if (!(element = $(element))) return;
901 return Element.classNames(element).add(className);
902 },
903
904 removeClassName: function(element, className) {
905 if (!(element = $(element))) return;
906 return Element.classNames(element).remove(className);
907 },
908
909 // removes whitespace-only text node children
910 cleanWhitespace: function(element) {
911 element = $(element);
912 for (var i = 0; i < element.childNodes.length; i++) {
913 var node = element.childNodes[i];
914 if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
915 Element.remove(node);
916 }
917 },
918
919 empty: function(element) {
920 return $(element).innerHTML.match(/^\s*$/);
921 },
922
923 scrollTo: function(element) {
924 element = $(element);
925 var x = element.x ? element.x : element.offsetLeft,
926 y = element.y ? element.y : element.offsetTop;
927 window.scrollTo(x, y);
928 },
929
930 getStyle: function(element, style) {
931 element = $(element);
932 var value = element.style[style.camelize()];
933 if (!value) {
934 if (document.defaultView && document.defaultView.getComputedStyle) {
935 var css = document.defaultView.getComputedStyle(element, null);
936 value = css ? css.getPropertyValue(style) : null;
937 } else if (element.currentStyle) {
938 value = element.currentStyle[style.camelize()];
939 }
940 }
941
942 if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
943 if (Element.getStyle(element, 'position') == 'static') value = 'auto';
944
945 return value == 'auto' ? null : value;
946 },
947
948 setStyle: function(element, style) {
949 element = $(element);
950 for (name in style)
951 element.style[name.camelize()] = style[name];
952 },
953
954 getDimensions: function(element) {
955 element = $(element);
956 if (Element.getStyle(element, 'display') != 'none')
957 return {width: element.offsetWidth, height: element.offsetHeight};
958
959 // All *Width and *Height properties give 0 on elements with display none,
960 // so enable the element temporarily
961 var els = element.style;
962 var originalVisibility = els.visibility;
963 var originalPosition = els.position;
964 els.visibility = 'hidden';
965 els.position = 'absolute';
966 els.display = '';
967 var originalWidth = element.clientWidth;
968 var originalHeight = element.clientHeight;
969 els.display = 'none';
970 els.position = originalPosition;
971 els.visibility = originalVisibility;
972 return {width: originalWidth, height: originalHeight};
973 },
974
975 makePositioned: function(element) {
976 element = $(element);
977 var pos = Element.getStyle(element, 'position');
978 if (pos == 'static' || !pos) {
979 element._madePositioned = true;
980 element.style.position = 'relative';
981 // Opera returns the offset relative to the positioning context, when an
982 // element is position relative but top and left have not been defined
983 if (window.opera) {
984 element.style.top = 0;
985 element.style.left = 0;
986 }
987 }
988 },
989
990 undoPositioned: function(element) {
991 element = $(element);
992 if (element._madePositioned) {
993 element._madePositioned = undefined;
994 element.style.position =
995 element.style.top =
996 element.style.left =
997 element.style.bottom =
998 element.style.right = '';
999 }
1000 },
1001
1002 makeClipping: function(element) {
1003 element = $(element);
1004 if (element._overflow) return;
1005 element._overflow = element.style.overflow;
1006 if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
1007 element.style.overflow = 'hidden';
1008 },
1009
1010 undoClipping: function(element) {
1011 element = $(element);
1012 if (element._overflow) return;
1013 element.style.overflow = element._overflow;
1014 element._overflow = undefined;
1015 }
1016 });
1017
1018 var Toggle = new Object();
1019 Toggle.display = Element.toggle;
1020
1021 /*--------------------------------------------------------------------------*/
1022
1023 Abstract.Insertion = function(adjacency) {
1024 this.adjacency = adjacency;
1025 }
1026
1027 Abstract.Insertion.prototype = {
1028 initialize: function(element, content) {
1029 this.element = $(element);
1030 this.content = content.stripScripts();
1031
1032 if (this.adjacency && this.element.insertAdjacentHTML) {
1033 try {
1034 this.element.insertAdjacentHTML(this.adjacency, this.content);
1035 } catch (e) {
1036 if (this.element.tagName.toLowerCase() == 'tbody') {
1037 this.insertContent(this.contentFromAnonymousTable());
1038 } else {
1039 throw e;
1040 }
1041 }
1042 } else {
1043 this.range = this.element.ownerDocument.createRange();
1044 if (this.initializeRange) this.initializeRange();
1045 this.insertContent([this.range.createContextualFragment(this.content)]);
1046 }
1047
1048 setTimeout(function() {content.evalScripts()}, 10);
1049 },
1050
1051 contentFromAnonymousTable: function() {
1052 var div = document.createElement('div');
1053 div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
1054 return $A(div.childNodes[0].childNodes[0].childNodes);
1055 }
1056 }
1057
1058 var Insertion = new Object();
1059
1060 Insertion.Before = Class.create();
1061 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
1062 initializeRange: function() {
1063 this.range.setStartBefore(this.element);
1064 },
1065
1066 insertContent: function(fragments) {
1067 fragments.each((function(fragment) {
1068 this.element.parentNode.insertBefore(fragment, this.element);
1069 }).bind(this));
1070 }
1071 });
1072
1073 Insertion.Top = Class.create();
1074 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
1075 initializeRange: function() {
1076 this.range.selectNodeContents(this.element);
1077 this.range.collapse(true);
1078 },
1079
1080 insertContent: function(fragments) {
1081 fragments.reverse(false).each((function(fragment) {
1082 this.element.insertBefore(fragment, this.element.firstChild);
1083 }).bind(this));
1084 }
1085 });
1086
1087 Insertion.Bottom = Class.create();
1088 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
1089 initializeRange: function() {
1090 this.range.selectNodeContents(this.element);
1091 this.range.collapse(this.element);
1092 },
1093
1094 insertContent: function(fragments) {
1095 fragments.each((function(fragment) {
1096 this.element.appendChild(fragment);
1097 }).bind(this));
1098 }
1099 });
1100
1101 Insertion.After = Class.create();
1102 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
1103 initializeRange: function() {
1104 this.range.setStartAfter(this.element);
1105 },
1106
1107 insertContent: function(fragments) {
1108 fragments.each((function(fragment) {
1109 this.element.parentNode.insertBefore(fragment,
1110 this.element.nextSibling);
1111 }).bind(this));
1112 }
1113 });
1114
1115 /*--------------------------------------------------------------------------*/
1116
1117 Element.ClassNames = Class.create();
1118 Element.ClassNames.prototype = {
1119 initialize: function(element) {
1120 this.element = $(element);
1121 },
1122
1123 _each: function(iterator) {
1124 this.element.className.split(/\s+/).select(function(name) {
1125 return name.length > 0;
1126 })._each(iterator);
1127 },
1128
1129 set: function(className) {
1130 this.element.className = className;
1131 },
1132
1133 add: function(classNameToAdd) {
1134 if (this.include(classNameToAdd)) return;
1135 this.set(this.toArray().concat(classNameToAdd).join(' '));
1136 },
1137
1138 remove: function(classNameToRemove) {
1139 if (!this.include(classNameToRemove)) return;
1140 this.set(this.select(function(className) {
1141 return className != classNameToRemove;
1142 }).join(' '));
1143 },
1144
1145 toString: function() {
1146 return this.toArray().join(' ');
1147 }
1148 }
1149
1150 Object.extend(Element.ClassNames.prototype, Enumerable);
1151 var Field = {
1152 clear: function() {
1153 for (var i = 0; i < arguments.length; i++)
1154 $(arguments[i]).value = '';
1155 },
1156
1157 focus: function(element) {
1158 $(element).focus();
1159 },
1160
1161 present: function() {
1162 for (var i = 0; i < arguments.length; i++)
1163 if ($(arguments[i]).value == '') return false;
1164 return true;
1165 },
1166
1167 select: function(element) {
1168 $(element).select();
1169 },
1170
1171 activate: function(element) {
1172 element = $(element);
1173 element.focus();
1174 if (element.select)
1175 element.select();
1176 }
1177 }
1178
1179 /*--------------------------------------------------------------------------*/
1180
1181 var Form = {
1182 serialize: function(form) {
1183 var elements = Form.getElements($(form));
1184 var queryComponents = new Array();
1185
1186 for (var i = 0; i < elements.length; i++) {
1187 var queryComponent = Form.Element.serialize(elements[i]);
1188 if (queryComponent)
1189 queryComponents.push(queryComponent);
1190 }
1191
1192 return queryComponents.join('&');
1193 },
1194
1195 getElements: function(form) {
1196 form = $(form);
1197 var elements = new Array();
1198
1199 for (tagName in Form.Element.Serializers) {
1200 var tagElements = form.getElementsByTagName(tagName);
1201 for (var j = 0; j < tagElements.length; j++)
1202 elements.push(tagElements[j]);
1203 }
1204 return elements;
1205 },
1206
1207 getInputs: function(form, typeName, name) {
1208 form = $(form);
1209 var inputs = form.getElementsByTagName('input');
1210
1211 if (!typeName && !name)
1212 return inputs;
1213
1214 var matchingInputs = new Array();
1215 for (var i = 0; i < inputs.length; i++) {
1216 var input = inputs[i];
1217 if ((typeName && input.type != typeName) ||
1218 (name && input.name != name))
1219 continue;
1220 matchingInputs.push(input);
1221 }
1222
1223 return matchingInputs;
1224 },
1225
1226 disable: function(form) {
1227 var elements = Form.getElements(form);
1228 for (var i = 0; i < elements.length; i++) {
1229 var element = elements[i];
1230 element.blur();
1231 element.disabled = 'true';
1232 }
1233 },
1234
1235 enable: function(form) {
1236 var elements = Form.getElements(form);
1237 for (var i = 0; i < elements.length; i++) {
1238 var element = elements[i];
1239 element.disabled = '';
1240 }
1241 },
1242
1243 findFirstElement: function(form) {
1244 return Form.getElements(form).find(function(element) {
1245 return element.type != 'hidden' && !element.disabled &&
1246 ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
1247 });
1248 },
1249
1250 focusFirstElement: function(form) {
1251 Field.activate(Form.findFirstElement(form));
1252 },
1253
1254 reset: function(form) {
1255 $(form).reset();
1256 }
1257 }
1258
1259 Form.Element = {
1260 serialize: function(element) {
1261 element = $(element);
1262 var method = element.tagName.toLowerCase();
1263 var parameter = Form.Element.Serializers[method](element);
1264
1265 if (parameter) {
1266 var key = encodeURIComponent(parameter[0]);
1267 if (key.length == 0) return;
1268
1269 if (parameter[1].constructor != Array)
1270 parameter[1] = [parameter[1]];
1271
1272 return parameter[1].map(function(value) {
1273 return key + '=' + encodeURIComponent(value);
1274 }).join('&');
1275 }
1276 },
1277
1278 getValue: function(element) {
1279 element = $(element);
1280 var method = element.tagName.toLowerCase();
1281 var parameter = Form.Element.Serializers[method](element);
1282
1283 if (parameter)
1284 return parameter[1];
1285 }
1286 }
1287
1288 Form.Element.Serializers = {
1289 input: function(element) {
1290 switch (element.type.toLowerCase()) {
1291 case 'submit':
1292 case 'hidden':
1293 case 'password':
1294 case 'text':
1295 return Form.Element.Serializers.textarea(element);
1296 case 'checkbox':
1297 case 'radio':
1298 return Form.Element.Serializers.inputSelector(element);
1299 }
1300 return false;
1301 },
1302
1303 inputSelector: function(element) {
1304 if (element.checked)
1305 return [element.name, element.value];
1306 },
1307
1308 textarea: function(element) {
1309 return [element.name, element.value];
1310 },
1311
1312 select: function(element) {
1313 return Form.Element.Serializers[element.type == 'select-one' ?
1314 'selectOne' : 'selectMany'](element);
1315 },
1316
1317 selectOne: function(element) {
1318 var value = '', opt, index = element.selectedIndex;
1319 if (index >= 0) {
1320 opt = element.options[index];
1321 value = opt.value;
1322 if (!value && !('value' in opt))
1323 value = opt.text;
1324 }
1325 return [element.name, value];
1326 },
1327
1328 selectMany: function(element) {
1329 var value = new Array();
1330 for (var i = 0; i < element.length; i++) {
1331 var opt = element.options[i];
1332 if (opt.selected) {
1333 var optValue = opt.value;
1334 if (!optValue && !('value' in opt))
1335 optValue = opt.text;
1336 value.push(optValue);
1337 }
1338 }
1339 return [element.name, value];
1340 }
1341 }
1342
1343 /*--------------------------------------------------------------------------*/
1344
1345 var $F = Form.Element.getValue;
1346
1347 /*--------------------------------------------------------------------------*/
1348
1349 Abstract.TimedObserver = function() {}
1350 Abstract.TimedObserver.prototype = {
1351 initialize: function(element, frequency, callback) {
1352 this.frequency = frequency;
1353 this.element = $(element);
1354 this.callback = callback;
1355
1356 this.lastValue = this.getValue();
1357 this.registerCallback();
1358 },
1359
1360 registerCallback: function() {
1361 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
1362 },
1363
1364 onTimerEvent: function() {
1365 var value = this.getValue();
1366 if (this.lastValue != value) {
1367 this.callback(this.element, value);
1368 this.lastValue = value;
1369 }
1370 }
1371 }
1372
1373 Form.Element.Observer = Class.create();
1374 Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
1375 getValue: function() {
1376 return Form.Element.getValue(this.element);
1377 }
1378 });
1379
1380 Form.Observer = Class.create();
1381 Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
1382 getValue: function() {
1383 return Form.serialize(this.element);
1384 }
1385 });
1386
1387 /*--------------------------------------------------------------------------*/
1388
1389 Abstract.EventObserver = function() {}
1390 Abstract.EventObserver.prototype = {
1391 initialize: function(element, callback) {
1392 this.element = $(element);
1393 this.callback = callback;
1394
1395 this.lastValue = this.getValue();
1396 if (this.element.tagName.toLowerCase() == 'form')
1397 this.registerFormCallbacks();
1398 else
1399 this.registerCallback(this.element);
1400 },
1401
1402 onElementEvent: function() {
1403 var value = this.getValue();
1404 if (this.lastValue != value) {
1405 this.callback(this.element, value);
1406 this.lastValue = value;
1407 }
1408 },
1409
1410 registerFormCallbacks: function() {
1411 var elements = Form.getElements(this.element);
1412 for (var i = 0; i < elements.length; i++)
1413 this.registerCallback(elements[i]);
1414 },
1415
1416 registerCallback: function(element) {
1417 if (element.type) {
1418 switch (element.type.toLowerCase()) {
1419 case 'checkbox':
1420 case 'radio':
1421 Event.observe(element, 'click', this.onElementEvent.bind(this));
1422 break;
1423 case 'password':
1424 case 'text':
1425 case 'textarea':
1426 case 'select-one':
1427 case 'select-multiple':
1428 Event.observe(element, 'change', this.onElementEvent.bind(this));
1429 break;
1430 }
1431 }
1432 }
1433 }
1434
1435 Form.Element.EventObserver = Class.create();
1436 Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
1437 getValue: function() {
1438 return Form.Element.getValue(this.element);
1439 }
1440 });
1441
1442 Form.EventObserver = Class.create();
1443 Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
1444 getValue: function() {
1445 return Form.serialize(this.element);
1446 }
1447 });
1448 if (!window.Event) {
1449 var Event = new Object();
1450 }
1451
1452 Object.extend(Event, {
1453 KEY_BACKSPACE: 8,
1454 KEY_TAB: 9,
1455 KEY_RETURN: 13,
1456 KEY_ESC: 27,
1457 KEY_LEFT: 37,
1458 KEY_UP: 38,
1459 KEY_RIGHT: 39,
1460 KEY_DOWN: 40,
1461 KEY_DELETE: 46,
1462
1463 element: function(event) {
1464 return event.target || event.srcElement;
1465 },
1466
1467 isLeftClick: function(event) {
1468 return (((event.which) && (event.which == 1)) ||
1469 ((event.button) && (event.button == 1)));
1470 },
1471
1472 pointerX: function(event) {
1473 return event.pageX || (event.clientX +
1474 (document.documentElement.scrollLeft || document.body.scrollLeft));
1475 },
1476
1477 pointerY: function(event) {
1478 return event.pageY || (event.clientY +
1479 (document.documentElement.scrollTop || document.body.scrollTop));
1480 },
1481
1482 stop: function(event) {
1483 if (event.preventDefault) {
1484 event.preventDefault();
1485 event.stopPropagation();
1486 } else {
1487 event.returnValue = false;
1488 event.cancelBubble = true;
1489 }
1490 },
1491
1492 // find the first node with the given tagName, starting from the
1493 // node the event was triggered on; traverses the DOM upwards
1494 findElement: function(event, tagName) {
1495 var element = Event.element(event);
1496 while (element.parentNode && (!element.tagName ||
1497 (element.tagName.toUpperCase() != tagName.toUpperCase())))
1498 element = element.parentNode;
1499 return element;
1500 },
1501
1502 observers: false,
1503
1504 _observeAndCache: function(element, name, observer, useCapture) {
1505 if (!this.observers) this.observers = [];
1506 if (element.addEventListener) {
1507 this.observers.push([element, name, observer, useCapture]);
1508 element.addEventListener(name, observer, useCapture);
1509 } else if (element.attachEvent) {
1510 this.observers.push([element, name, observer, useCapture]);
1511 element.attachEvent('on' + name, observer);
1512 }
1513 },
1514
1515 unloadCache: function() {
1516 if (!Event.observers) return;
1517 for (var i = 0; i < Event.observers.length; i++) {
1518 Event.stopObserving.apply(this, Event.observers[i]);
1519 Event.observers[i][0] = null;
1520 }
1521 Event.observers = false;
1522 },
1523
1524 observe: function(element, name, observer, useCapture) {
1525 var element = $(element);
1526 useCapture = useCapture || false;
1527
1528 if (name == 'keypress' &&
1529 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
1530 || element.attachEvent))
1531 name = 'keydown';
1532
1533 this._observeAndCache(element, name, observer, useCapture);
1534 },
1535
1536 stopObserving: function(element, name, observer, useCapture) {
1537 var element = $(element);
1538 useCapture = useCapture || false;
1539
1540 if (name == 'keypress' &&
1541 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
1542 || element.detachEvent))
1543 name = 'keydown';
1544
1545 if (element.removeEventListener) {
1546 element.removeEventListener(name, observer, useCapture);
1547 } else if (element.detachEvent) {
1548 element.detachEvent('on' + name, observer);
1549 }
1550 }
1551 });
1552
1553 /* prevent memory leaks in IE */
1554 Event.observe(window, 'unload', Event.unloadCache, false);
1555 var Position = {
1556 // set to true if needed, warning: firefox performance problems
1557 // NOT neeeded for page scrolling, only if draggable contained in
1558 // scrollable elements
1559 includeScrollOffsets: false,
1560
1561 // must be called before calling withinIncludingScrolloffset, every time the
1562 // page is scrolled
1563 prepare: function() {
1564 this.deltaX = window.pageXOffset
1565 || document.documentElement.scrollLeft
1566 || document.body.scrollLeft
1567 || 0;
1568 this.deltaY = window.pageYOffset
1569 || document.documentElement.scrollTop
1570 || document.body.scrollTop
1571 || 0;
1572 },
1573
1574 realOffset: function(element) {
1575 var valueT = 0, valueL = 0;
1576 do {
1577 valueT += element.scrollTop || 0;
1578 valueL += element.scrollLeft || 0;
1579 element = element.parentNode;
1580 } while (element);
1581 return [valueL, valueT];
1582 },
1583
1584 cumulativeOffset: function(element) {
1585 var valueT = 0, valueL = 0;
1586 do {
1587 valueT += element.offsetTop || 0;
1588 valueL += element.offsetLeft || 0;
1589 element = element.offsetParent;
1590 } while (element);
1591 return [valueL, valueT];
1592 },
1593
1594 positionedOffset: function(element) {
1595 var valueT = 0, valueL = 0;
1596 do {
1597 valueT += element.offsetTop || 0;
1598 valueL += element.offsetLeft || 0;
1599 element = element.offsetParent;
1600 if (element) {
1601 p = Element.getStyle(element, 'position');
1602 if (p == 'relative' || p == 'absolute') break;
1603 }
1604 } while (element);
1605 return [valueL, valueT];
1606 },
1607
1608 offsetParent: function(element) {
1609 if (element.offsetParent) return element.offsetParent;
1610 if (element == document.body) return element;
1611
1612 while ((element = element.parentNode) && element != document.body)
1613 if (Element.getStyle(element, 'position') != 'static')
1614 return element;
1615
1616 return document.body;
1617 },
1618
1619 // caches x/y coordinate pair to use with overlap
1620 within: function(element, x, y) {
1621 if (this.includeScrollOffsets)
1622 return this.withinIncludingScrolloffsets(element, x, y);
1623 this.xcomp = x;
1624 this.ycomp = y;
1625 this.offset = this.cumulativeOffset(element);
1626
1627 return (y >= this.offset[1] &&
1628 y < this.offset[1] + element.offsetHeight &&
1629 x >= this.offset[0] &&
1630 x < this.offset[0] + element.offsetWidth);
1631 },
1632
1633 withinIncludingScrolloffsets: function(element, x, y) {
1634 var offsetcache = this.realOffset(element);
1635
1636 this.xcomp = x + offsetcache[0] - this.deltaX;
1637 this.ycomp = y + offsetcache[1] - this.deltaY;
1638 this.offset = this.cumulativeOffset(element);
1639
1640 return (this.ycomp >= this.offset[1] &&
1641 this.ycomp < this.offset[1] + element.offsetHeight &&
1642 this.xcomp >= this.offset[0] &&
1643 this.xcomp < this.offset[0] + element.offsetWidth);
1644 },
1645
1646 // within must be called directly before
1647 overlap: function(mode, element) {
1648 if (!mode) return 0;
1649 if (mode == 'vertical')
1650 return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
1651 element.offsetHeight;
1652 if (mode == 'horizontal')
1653 return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
1654 element.offsetWidth;
1655 },
1656
1657 clone: function(source, target) {
1658 source = $(source);
1659 target = $(target);
1660 target.style.position = 'absolute';
1661 var offsets = this.cumulativeOffset(source);
1662 target.style.top = offsets[1] + 'px';
1663 target.style.left = offsets[0] + 'px';
1664 target.style.width = source.offsetWidth + 'px';
1665 target.style.height = source.offsetHeight + 'px';
1666 },
1667
1668 page: function(forElement) {
1669 var valueT = 0, valueL = 0;
1670
1671 var element = forElement;
1672 do {
1673 valueT += element.offsetTop || 0;
1674 valueL += element.offsetLeft || 0;
1675
1676 // Safari fix
1677 if (element.offsetParent==document.body)
1678 if (Element.getStyle(element,'position')=='absolute') break;
1679
1680 } while (element = element.offsetParent);
1681
1682 element = forElement;
1683 do {
1684 valueT -= element.scrollTop || 0;
1685 valueL -= element.scrollLeft || 0;
1686 } while (element = element.parentNode);
1687
1688 return [valueL, valueT];
1689 },
1690
1691 clone: function(source, target) {
1692 var options = Object.extend({
1693 setLeft: true,
1694 setTop: true,
1695 setWidth: true,
1696 setHeight: true,
1697 offsetTop: 0,
1698 offsetLeft: 0
1699 }, arguments[2] || {})
1700
1701 // find page position of source
1702 source = $(source);
1703 var p = Position.page(source);
1704
1705 // find coordinate system to use
1706 target = $(target);
1707 var delta = [0, 0];
1708 var parent = null;
1709 // delta [0,0] will do fine with position: fixed elements,
1710 // position:absolute needs offsetParent deltas
1711 if (Element.getStyle(target,'position') == 'absolute') {
1712 parent = Position.offsetParent(target);
1713 delta = Position.page(parent);
1714 }
1715
1716 // correct by body offsets (fixes Safari)
1717 if (parent == document.body) {
1718 delta[0] -= document.body.offsetLeft;
1719 delta[1] -= document.body.offsetTop;
1720 }
1721
1722 // set position
1723 if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
1724 if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
1725 if(options.setWidth) target.style.width = source.offsetWidth + 'px';
1726 if(options.setHeight) target.style.height = source.offsetHeight + 'px';
1727 },
1728
1729 absolutize: function(element) {
1730 element = $(element);
1731 if (element.style.position == 'absolute') return;
1732 Position.prepare();
1733
1734 var offsets = Position.positionedOffset(element);
1735 var top = offsets[1];
1736 var left = offsets[0];
1737 var width = element.clientWidth;
1738 var height = element.clientHeight;
1739
1740 element._originalLeft = left - parseFloat(element.style.left || 0);
1741 element._originalTop = top - parseFloat(element.style.top || 0);
1742 element._originalWidth = element.style.width;
1743 element._originalHeight = element.style.height;
1744
1745 element.style.position = 'absolute';
1746 element.style.top = top + 'px';;
1747 element.style.left = left + 'px';;
1748 element.style.width = width + 'px';;
1749 element.style.height = height + 'px';;
1750 },
1751
1752 relativize: function(element) {
1753 element = $(element);
1754 if (element.style.position == 'relative') return;
1755 Position.prepare();
1756
1757 element.style.position = 'relative';
1758 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
1759 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
1760
1761 element.style.top = top + 'px';
1762 element.style.left = left + 'px';
1763 element.style.height = element._originalHeight;
1764 element.style.width = element._originalWidth;
1765 }
1766 }
1767
1768 // Safari returns margins on body which is incorrect if the child is absolutely
1769 // positioned. For performance reasons, redefine Position.cumulativeOffset for
1770 // KHTML/WebKit only.
1771 if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
1772 Position.cumulativeOffset = function(element) {
1773 var valueT = 0, valueL = 0;
1774 do {
1775 valueT += element.offsetTop || 0;
1776 valueL += element.offsetLeft || 0;
1777 if (element.offsetParent == document.body)
1778 if (Element.getStyle(element, 'position') == 'absolute') break;
1779
1780 element = element.offsetParent;
1781 } while (element);
1782
1783 return [valueL, valueT];
1784 }
1785 } No newline at end of file
@@ -0,0 +1,121
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
2
3 <head>
4 <title>redMine - Aide en ligne</title>
5 <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
6 <meta name="description" content="redMine" />
7 <meta name="keywords" content="issue,bug,tracker" />
8 <link href="stylesheets/help.css" rel="stylesheet" type="text/css" />
9 </head>
10
11 <body>
12 <p align="right">[ <a href="index.html">Index</a> ]</p>
13 <h1>Administration</h1>
14 Sommaire:
15 <ol>
16 <li><a href="administration.html#users">Utilisateurs</a></li>
17 <li><a href="administration.html#roles">R�les et permissions</a></li>
18 <li><a href="administration.html#trackers">Trackers</a></li>
19 <li><a href="administration.html#custom_fields">Champs personnalis�s</a></li>
20 <li><a href="administration.html#issue_statuses">Statuts de demande</a></li>
21 <li><a href="administration.html#workflow">Workflow</a></li>
22 <li><a href="administration.html#enumerations">Listes de valeurs</a></li>
23 <li><a href="administration.html#mail_notifications">Notifications par mail</a></li>
24 <li><a href="administration.html#app_info">Informations</a></li>
25 </ol>
26
27
28 <h2><a name="users"></a>1. Utilisateurs</h2>
29 <p>Ces �crans vous permettent de g�rer les utilisateurs de l'application.</p>
30 <h3>1.1 Liste des utilisateurs</h3>
31 <center><img src="images/users_list.png"><br />
32 <i><small>Liste des utilisateurs</small></i></center>
33 <h3>1.2 Cr�ation ou modification d'un utilisateur</h3>
34 <ul>
35 <li><b>Administrateur</b>: d�clare l'utilisateur comme administrateur de l'application.</li>
36 <li><b>Notifications par mail</b>: permet d'activer ou non l'envoi automatique de notifications par mail pour cet utilisateur</li>
37 <li><b>Verrouill�</b>: d�sactive le compte de l'utilisateur</li>
38 </ul>
39 <p>En mode modification, laissez le champ <b>Password</b> vide pour laisser le mot de passe de l'utilisateur inchang�.</p>
40 <p>Un utilisateur d�clar� comme administrateur dispose de toutes les permissions sur l'application et sur tous les projets.</p>
41
42
43 <h2><a name="roles"></a>2. R�les et permissions</h2>
44 <p>Les r�les permettent de d�finir les permissions des diff�rents membres d'un projet.<br />
45 Chaque membre d'un projet dispose d'un r�le unique au sein d'un projet.
46 Un utilisateur peut avoir diff�rents r�les au sein de diff�rents projets.</p>
47 <p>Sur l'�cran d'�dition du r�le, cochez les actions que vous souhaitez autoriser pour le r�le.</p>
48
49
50 <h2><a name="trackers"></a>3. Trackers</h2>
51 <p>Les trackers permettent de typer les demandes et de d�finir des workflows sp�cifiques pour chacun de ces types.</p>
52
53 <h2><a name="custom_fields"></a>4. Champs personnalis�s</h2>
54 <p>Les champs personnalis�s vous permettent d'ajouter des informations suppl�mentaires sur les demandes.</p>
55 Un champ personnalis� peut �tre de l'un des types suivants:
56 <ul>
57 <li><b>Integer</b>: entier positif ou n�gatif</li>
58 <li><b>String</b>: cha�ne de caract�re</li>
59 <li><b>Date</b>: date</li>
60 <li><b>Boolean</b>: bool�en (case � cocher)</li>
61 <li><b>List</b>: valeur � s�lectionn�e parmi une liste pr�d�finie (liste d�roulante)</li>
62 </ul>
63 Des �l�ments de validation peuvent �tre d�finis:
64 <ul>
65 <li><b>Required</b>: champ dont la saisie est obligatoire sur les demandes</li>
66 <li><b>For all projects</b>: champ automatiquement associ� � l'ensemble des projets</li>
67 <li><b>Min - max length</b>: longueurs minimales et maximales pour les champs en saisie libre (0 signifie qu'il n'y a pas de restriction)</li>
68 <li><b>Regular expression</b>: expression r�guli�re permettant de valider la valeur saisie</li>
69 <li><b>Possible values (only for lists)</b>: valeurs possibles pour les champs de type "List". Les valeurs sont s�par�es par le caract�re |</li>
70 </ul>
71 <p>Si l'option <b>For all projects</b> n'est pas activ�e, chaque projet pourra ou non utilis� le champ personnalis� pour ses demandes
72 (voir <a href="projects.html#settings">Project settings</a>).</p>
73
74
75 <h2><a name="issue_statuses"></a>5. Statuts des demandes</h2>
76 <p>Cet �cran vous permet de d�finir les diff�rents statuts possibles des demandes.</p>
77 <ul>
78 <li><b>Closed</b>: indique que le statut correspond � une demande consid�r�e comme ferm�e</li>
79 <li><b>Default</b>: statut appliqu� par d�faut aux nouvelles demandes (seul un statut peut �tre d�clar� comme statut par d�faut)</li>
80 <li><b>HTML color</b>: code de couleur HTML repr�sentant le statut � l'affichage</li>
81 </ul>
82
83
84 <h2><a name="workflow"></a>6. Workflow</h2>
85 <p>Le workflow permet de d�finir quels changements les diff�rents membres d'un projet sont autoris�s � effectuer sur le statut des demandes, en fonction de leur type.</p>
86 <p>S�lectionnez le r�le et le type de demande pour lesquels vous souhaitez modifier le workflow, puis cliquez sur <b>Edit</b>.
87 L'�cran vous permet alors de modifier, pour le r�le et le type de demande choisi, les changements autoris�s.</p>
88 <p>Les lignes repr�sentent les statuts initiaux des demandes. Les colonnes repr�sentent les statuts autoris�s � �tre appliqu�s.</p>
89 <p>Dans l'exemple ci-dessous, les demandes de type Bug au statut New pourront �tre pass�es au statut Assigned ou Resolved par le r�le D�veloppeur.<br />
90 Celles au statut Assigned pourront �tre pass�es au statut Resolved.<br />
91 Le statut de toutes les autres demandes de type Bug ne pourra pas �tre modifi� par le D�veloppeur.</p>
92 <center><img src="images/workflow.png"><br />
93 <i><small>Exemple de configuration d'un workflow</small></i></center>
94 <p><b>Remarque</b>: pour qu'un r�le puisse changer le statut des demandes, la permission <i>"Changer le statut des demandes"</i> doit lui �tre explicitement donn�e ind�pendemment de la configuration du workflow (voir <a href="#roles">Roles et permissions</a>).
95
96
97 <h2><a name="enumerations"></a>7. Listes de valeurs</h2>
98 <p>Les listes de valeurs utilis�es par l'application (exemple: les priorit�s des demandes) peuvent �tre personnalis�es en fonction de vos besoins.<br />
99 Cet �cran vous permet de d�finir les valeurs possibles pour chacune des listes suivantes:</p>
100 <ul>
101 <li><b>Priorit�s des demandes</b></li>
102 <li><b>Cat�gories de documents</b></li>
103 </ul>
104
105
106 <h2><a name="mail_notifications"></a>8. Notifications par mail</h2>
107 <p>Cet �cran vous permet de s�lectionner les actions qui donneront lieu � une notification par mail aux membres du projet.</p>
108 <p><b>Remarque</b>: l'envoi de mails doit �tre activ� dans la configuration de l'application si souhaitez effectuer des notifications.</p>
109
110
111 <h2><a name="app_info"></a>9. Informations</h2>
112 <p>Affiche des informations relatives � l'application</p>
113 <ul>
114 <li><b>Version</b>: version de l'application</li>
115 <li><b>Database</b>: type de base de donn�es utilis�e</li>
116 </ul>
117
118
119 </body>
120 </html>
121
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,64
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
2
3 <head>
4 <title>redMine - Aide en ligne</title>
5 <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
6 <meta name="description" content="redMine" />
7 <meta name="keywords" content="issue,bug,tracker" />
8 <link href="stylesheets/help.css" rel="stylesheet" type="text/css" />
9 </head>
10
11 <body>
12 <h1>Aide</h1>
13
14 <p>Documentation en ligne redMine</p>
15
16 Sommaire:
17 <ul>
18
19 <li>
20 <a href="administration.html">Administration</a>
21 <ol>
22 <li><a href="administration.html#users">Utilisateur</a></li>
23 <li><a href="administration.html#roles">R�les et permissions</a></li>
24 <li><a href="administration.html#trackers">Trackers</a></li>
25 <li><a href="administration.html#custom_fields">Champs personnalis�s</a></li>
26 <li><a href="administration.html#issue_statuses">Statuts de demande</a></li>
27 <li><a href="administration.html#workflow">Workflow</a></li>
28 <li><a href="administration.html#enumerations">Liste de valeurs</a></li>
29 <li><a href="administration.html#mail_notifications">Notifications par mail</a></li>
30 <li><a href="administration.html#app_info">Informations</a></li>
31 </ol>
32 </li>
33
34 <li><a href="projects.html">Projets</a>
35 <ol>
36 <li><a href="projects.html#overview">Aper�u</a></li>
37 <li><a href="projects.html#issues">Demandes</a>
38 <ul>
39 <li>Liste des demandes</li>
40 <li>Nouvelle demande</li>
41 <li>Changer le statut d'une demande</li>
42 </ul>
43 </li>
44 <li><a href="projects.html#reports">Rapports</a></li>
45 <li><a href="projects.html#news">Annonces</a></li>
46 <li><a href="projects.html#changelog">Historique</a></li>
47 <li><a href="projects.html#documents">Documents</a></li>
48 <li><a href="projects.html#members">Membres</a></li>
49 <li><a href="projects.html#files">Fichiers</a></li>
50 <li><a href="projects.html#settings">Configuration</a></li>
51 <ul>
52 <li>Projet</li>
53 <li>Membres</li>
54 <li>Versions</li>
55 <li>Cat�gories de demande</li>
56 </ul>
57 </ol>
58 </li>
59
60
61 </ul>
62
63 </body>
64 </html> No newline at end of file
@@ -0,0 +1,109
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
2
3 <head>
4 <title>redMine - Aide en ligne</title>
5 <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
6 <meta name="description" content="redMine" />
7 <meta name="keywords" content="issue,bug,tracker" />
8 <link href="stylesheets/help.css" rel="stylesheet" type="text/css" />
9 </head>
10
11 <body>
12 <p align="right">[ <a href="index.html">Index</a> ]</p>
13 <h1>Projets</h1>
14 Sommaire:
15 <ol>
16 <li><a href="projects.html#overview">Aper�u</a></li>
17 <li><a href="projects.html#issues">Demandes</a>
18 <ul>
19 <li>Liste des demandes</li>
20 <li>Nouvelle demande</li>
21 <li>Changer le statut d'une demande</li>
22 </ul>
23 </li>
24 <li><a href="projects.html#reports">Rapports</a></li>
25 <li><a href="projects.html#news">Annonces</a></li>
26 <li><a href="projects.html#changelog">Historique</a></li>
27 <li><a href="projects.html#documents">Documents</a></li>
28 <li><a href="projects.html#members">Membres</a></li>
29 <li><a href="projects.html#files">Fichiers</a></li>
30 <li><a href="projects.html#settings">Configuration</a></li>
31 <ul>
32 <li>Projet</li>
33 <li>Membres</li>
34 <li>Versions</li>
35 <li>Cat�gories de demande</li>
36 </ul>
37 </ol>
38
39
40 <h2><a name="overview"></a>1. Aper�u</h2>
41 <p>L'aper�u vous pr�sente les informations g�n�rales relatives au projet, les principaux membres, les derni�res annonces, ainsi qu'une synth�se du nombre de demandes ouvertes par tracker.
42
43
44 <h2><a name="issues"></a>2. Demandes</h2>
45 <h3>2.1 Liste des demandes</h3>
46 Par d�faut, l'ensemble des demandes sont affich�es. Vous pouvez utiliser les diff�rents filtres pour limiter l'affichage � certaines demandes seulement.<br />
47 Lorsque vous appliquez un filtre, il reste en place durant toute votre session. Vous pouvez le red�finir, ou le supprimer en cliquant sur <b>Annuler</b>.
48 <center><img src="images/issues_list.png"><br />
49 <i><small>Liste des demandes</small></i></center>
50
51 <h3>2.2 Nouvelle demande</h3>
52 <p>TODO</p>
53
54 <h3>2.3 Changer le statut d'une demande</h3>
55 <p>TODO</p>
56
57
58 <h2><a name="reports"></a>3. Rapports</h2>
59 <p>Synth�se du nombre de demandes par statut et selon diff�rents crit�res (tracker, priorit�, cat�gorie).
60 Des liens directs permettent d'acc�der � la liste d�taill�e des demandes pour chaque crit�re.</p>
61
62
63 <h2><a name="news"></a>4. Annonces</h2>
64 <p>Les nouvelles vous permettent d'informer les utilisateurs sur l'activit� du projet.</p>
65
66
67 <h2><a name="changelog"></a>5. Historique</h2>
68 <p>Cette page pr�sente l'ensemble des demandes r�solues dans chacune des versions du projet.
69 Certains types de demande peuvent �tre exclus de cet affichage (voir <a href="administration.html#trackers">Trackers</a>).</p>
70
71
72 <h2><a name="documents"></a>6. Documents</h2>
73 <p>Les documents sont group�s par cat�gories (voir <a href="administration.html#enumerations">Listes de valeurs</a>).<br />
74 Un document peut contenir plusieurs fichiers (exemple: r�visions ou versions successives)</p>
75
76
77 <h2><a name="members"></a>7. Membres</h2>
78 <p>Affichage de l'ensemble des membres du projet, par r�le</p>
79
80
81 <h2><a name="files"></a>8. Fichiers</h2>
82 <p>Ce module vous permet de publier les fichiers de l'application (sources, binaires, ...) pour chaque version de l'application .</p>
83
84 <h2><a name="settings"></a>9. Configuration</h2>
85 <h3>9.1 Projet</h3>
86 <ul>
87 <li><b>Public</b>: si le projet est public, il sera visible (consultation des demandes, des documents, ...) pour l'ensemble des utilisateurs, y compris ceux qui ne sont pas membres du projet.<br />
88 Si le projet n'est pas public, seuls les membres du projet y ont acc�s, en fonction de leur r�le.</li>
89 <li><b>Champs personnalis�s</b>: s�lectionner les champs personnalis�s que vous souhaitez utiliser au sein du projet.<br />
90 Seul l'administrateur peut ajouter de nouveaux champs personnalis�s.</li>
91 </ul>
92 <h3>9.2 Membres</h3>
93 <p>Cette section vous permet de d�finir les membres du projet ainsi que leurs r�les respectifs.<br />
94 Un utilisateur ne peut avoir qu'un r�le au sein d'un projet donn�. Le r�le d'un membre d�termine les permissions dont il b�n�ficie sur le projet.</p>
95
96 <h3>9.3 Versions</h3>
97 <p>Les versions vous permettent de suivre les changements survenus tout au long du projet.
98 A la fermeture d'une demande, vous pouvez indiquer quelle version la prend en compte.<br />
99 Vous pouvez par ailleurs publier les diff�rentes versions de l'application (voir <a href="projects.html#files">Fichiers</a>).
100 </p>
101
102
103 <h3>9.4 Cat�gories de demande</h3>
104 <p>Les cat�gories de demande vous permettent de typer les demandes. Les cat�gories peuvent par exemple correspondre aux modules de l'application.</p>
105
106
107 </body>
108 </html>
109
@@ -0,0 +1,70
1 /* andreas08 - an open source xhtml/css website layout by Andreas Viklund - http://andreasviklund.com . Free to use in any way and for any purpose as long as the proper credits are given to the original designer. Version: 1.0, November 28, 2005 */
2
3 /**************** Body and tag styles ****************/
4
5
6
7 body{
8 font:76% Verdana,Tahoma,Arial,sans-serif;
9 line-height:1.4em;
10 color:#303030;
11 margin: 20px;
12 }
13
14 a{
15 color:#467aa7;
16 font-weight:bold;
17 text-decoration:none;
18 background-color:inherit;
19 }
20
21 a:hover{color:#2a5a8a; text-decoration:none; background-color:inherit;}
22 a img{border:none;}
23
24 p{padding:0 0 0.2em 0;}
25 p form{margin-top:0; margin-bottom:20px;}
26
27 h1 {
28 display:block;
29
30 font-size:1.7em;
31 font-weight:normal;
32 letter-spacing:-1px;
33 color:#505050;
34 background-color:inherit;
35 }
36
37 h2 {
38 display:block;
39 margin: 30px 0 0 0;
40 font-size:1.5em;
41 font-weight:normal;
42 letter-spacing:-1px;
43 color:#505050;
44 background-color:inherit;
45 }
46
47 hr { border:0px; border-bottom:1px dashed #000000; }
48
49
50 /**************** Misc classes and styles ****************/
51
52 .splitcontentleft{float:left; width:49%;}
53 .splitcontentright{float:right; width:49%;}
54 .clear{clear:both;}
55 .small{font-size:0.8em;line-height:1.4em;padding:0 0 0 0;}
56 .hide{display:none;}
57 .textcenter{text-align:center;}
58 .textright{text-align:right;}
59 .important{color:#f02025; background-color:inherit; }
60
61 .box{
62 margin:0 0 20px 0;
63 padding:10px;
64 border:1px solid #c0c0c0;
65 background-color:#fafbfc;
66 color:#505050;
67 line-height:1.5em;
68 }
69
70
@@ -0,0 +1,1
1 # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file No newline at end of file
@@ -0,0 +1,322
1 /* andreas08 - an open source xhtml/css website layout by Andreas Viklund - http://andreasviklund.com . Free to use in any way and for any purpose as long as the proper credits are given to the original designer. Version: 1.0, November 28, 2005 */
2 /* Edited by Jean-Philippe Lang *>
3 /**************** Body and tag styles ****************/
4
5
6 #header * {margin:0; padding:0;}
7 p, ul, ol, li {margin:0; padding:0;}
8
9
10 body{
11 font:76% Verdana,Tahoma,Arial,sans-serif;
12 line-height:1.4em;
13 text-align:center;
14 color:#303030;
15 background:#e8eaec;
16 }
17
18 a{
19 color:#467aa7;
20 font-weight:bold;
21 text-decoration:none;
22 background-color:inherit;
23 }
24
25 a:hover{color:#2a5a8a; text-decoration:none; background-color:inherit;}
26 a img{border:none;}
27
28 p{padding:0 0 1em 0;}
29 p form{margin-top:0; margin-bottom:20px;}
30
31 img.left,img.center,img.right{padding:4px; border:1px solid #a0a0a0;}
32 img.left{float:left; margin:0 12px 5px 0;}
33 img.center{display:block; margin:0 auto 5px auto;}
34 img.right{float:right; margin:0 0 5px 12px;}
35
36 /**************** Header and navigation styles ****************/
37
38 #container{
39 width:100%;
40 min-width: 800px;
41 margin:5px auto;
42 padding:1px 0;
43 text-align:left;
44 background:#ffffff;
45 color:#303030;
46 border:2px solid #a0a0a0;
47 }
48
49 #header{
50 height:5.5em;
51 /*width:758px;*/
52 margin:0 1px 1px 1px;
53 background:#467aa7;
54 color:#ffffff;
55 }
56
57 #header h1{
58 padding:14px 0 0 20px;
59 font-size:2.4em;
60 background-color:inherit;
61 color:#fff; /*rgb(152, 26, 33);*/
62 letter-spacing:-2px;
63 font-weight:normal;
64 }
65
66 #header h2{
67 margin:10px 0 0 40px;
68 font-size:1.4em;
69 background-color:inherit;
70 color:#f0f2f4;
71 letter-spacing:-1px;
72 font-weight:normal;
73 }
74
75 #navigation{
76 height:2.2em;
77 line-height:2.2em;
78 /*width:758px;*/
79 margin:0 1px;
80 background:#578bb8;
81 color:#ffffff;
82 }
83
84 #navigation li{
85 float:left;
86 list-style-type:none;
87 border-right:1px solid #ffffff;
88 white-space:nowrap;
89 }
90
91 #navigation li.right {
92 float:right;
93 list-style-type:none;
94 border-right:0;
95 border-left:1px solid #ffffff;
96 white-space:nowrap;
97 }
98
99 #navigation li a{
100 display:block;
101 padding:0px 10px 0px 22px;
102 font-size:0.8em;
103 font-weight:normal;
104 /*text-transform:uppercase;*/
105 text-decoration:none;
106 background-color:inherit;
107 color: #ffffff;
108 }
109
110 * html #navigation a {width:1%;}
111
112 #navigation .selected,#navigation a:hover{
113 color:#ffffff;
114 text-decoration:none;
115 background-color: #80b0da;
116 }
117
118 /**************** Icons links *******************/
119 .picHome { background: url(../images/home.png) no-repeat 4px 50%; }
120 .picUser { background: url(../images/user.png) no-repeat 4px 50%; }
121 .picUserPage { background: url(../images/user_page.png) no-repeat 4px 50%; }
122 .picAdmin { background: url(../images/admin.png) no-repeat 4px 50%; }
123 .picProject { background: url(../images/projects.png) no-repeat 4px 50%; }
124 .picLogout { background: url(../images/logout.png) no-repeat 4px 50%; }
125 .picHelp { background: url(../images/help.png) no-repeat 4px 50%; }
126
127 /**************** Content styles ****************/
128
129 #content{
130 /*float:right;*/
131 /*width:530px;*/
132 width: auto;
133 min-height: 500px;
134 font-size:0.9em;
135 padding:20px 10px 10px 20px;
136 /*position: absolute;*/
137 margin: 0 0 0 140px;
138 border-left: 1px dashed #c0c0c0;
139
140 }
141
142 #content h2{
143 display:block;
144 margin:0 0 16px 0;
145 font-size:1.7em;
146 font-weight:normal;
147 letter-spacing:-1px;
148 color:#505050;
149 background-color:inherit;
150 }
151
152 #content h2 a{font-weight:normal;}
153 #content h3{margin:0 0 5px 0; font-size:1.4em; letter-spacing:-1px;}
154 #content a:hover,#subcontent a:hover{text-decoration:underline;}
155 #content ul,#content ol{margin:0 5px 16px 35px;}
156 #content dl{margin:0 5px 10px 25px;}
157 #content dt{font-weight:bold; margin-bottom:5px;}
158 #content dd{margin:0 0 10px 15px;}
159
160
161 /***********************************************/
162
163 /*
164 form{
165 padding:15px;
166 margin:0 0 20px 0;
167 border:1px solid #c0c0c0;
168 background-color:#CEE1ED;
169 width:600px;
170 }
171 */
172
173 form {
174 display: inline;
175 }
176
177 .noborder {
178 border:0px;
179 background-color:#fff;
180 width:100%;
181 }
182
183 input {
184 vertical-align: top;
185 }
186
187 input.button-small
188 {
189 font-size: 0.8em;
190 }
191
192 label {
193 font-weight: bold;
194 font-size: 1em;
195 }
196
197 .required {
198 color: #bb0000;
199 }
200
201 table.listTableContent {
202 /*margin: 2em 2em 2em 0; */
203 border:1px solid #c0c0c0;
204 width:99%;
205 }
206
207 table.listTableContent td {
208 margin: 2px;
209
210 }
211
212 tr.ListHead {
213 background-color:#467aa7;
214 color:#FFFFFF;
215 text-align:center;
216 }
217
218 tr.ListHead a {
219 color:#FFFFFF;
220 text-decoration:underline;
221 }
222
223 tr.ListLine0 {
224 background-color: #C1E2F7;
225 }
226 tr.ListLine1 {
227 background-color:#CEE1ED;
228 }
229
230 hr { border:0px; border-bottom:1px dashed #000000; }
231
232
233 /**************** Sidebar styles ****************/
234
235 #subcontent{
236 float:left;
237 clear:both;
238 width:130px;
239 padding:20px 20px 10px 5px;
240 line-height:1.4em;
241 }
242
243 #subcontent h2{
244 display:block;
245 margin:0 0 15px 0;
246 font-size:1.6em;
247 font-weight:normal;
248 text-align:left;
249 letter-spacing:-1px;
250 color:#505050;
251 background-color:inherit;
252 }
253
254 #subcontent p{margin:0 0 16px 0; font-size:0.9em;}
255
256 /**************** Menublock styles ****************/
257
258 .menublock{margin:0 0 20px 8px; font-size:0.9em;}
259 .menublock li{list-style:none; display:block; padding:1px; margin-bottom:0px;}
260 .menublock li a{font-weight:bold; text-decoration:none;}
261 .menublock li a:hover{text-decoration:none;}
262 .menublock li ul{margin:3px 0 3px 15px; font-size:1em; font-weight:normal;}
263 .menublock li ul li{margin-bottom:0;}
264 .menublock li ul a{font-weight:normal;}
265
266 /**************** Searchbar styles ****************/
267
268 #searchbar{margin:0 0 20px 0;}
269 #searchbar form fieldset{margin-left:10px; border:0 solid;}
270
271 #searchbar #s{
272 height:1.2em;
273 width:110px;
274 margin:0 5px 0 0;
275 border:1px solid #a0a0a0;
276 }
277
278 #searchbar #searchbutton{
279 width:auto;
280 padding:0 1px;
281 border:1px solid #808080;
282 font-size:0.9em;
283 text-align:center;
284 }
285
286 /**************** Footer styles ****************/
287
288 #footer{
289 clear:both;
290 /*width:758px;*/
291 padding:5px 0;
292 margin:0 1px;
293 font-size:0.9em;
294 color:#f0f0f0;
295 background:#467aa7;
296 }
297
298 #footer p{padding:0; margin:0; text-align:center;}
299 #footer a{color:#f0f0f0; background-color:inherit; font-weight:bold;}
300 #footer a:hover{color:#ffffff; background-color:inherit; text-decoration: underline;}
301
302 /**************** Misc classes and styles ****************/
303
304 .splitcontentleft{float:left; width:49%;}
305 .splitcontentright{float:right; width:49%;}
306 .clear{clear:both;}
307 .small{font-size:0.8em;line-height:1.4em;padding:0 0 0 0;}
308 .hide{display:none;}
309 .textcenter{text-align:center;}
310 .textright{text-align:right;}
311 .important{color:#f02025; background-color:inherit; font-weight:bold;}
312
313 .box{
314 margin:0 0 20px 0;
315 padding:10px;
316 border:1px solid #c0c0c0;
317 background-color:#fafbfc;
318 color:#505050;
319 line-height:1.5em;
320 }
321
322
@@ -0,0 +1,56
1
2 .fieldWithErrors {
3 padding: 2px;
4 background-color: red;
5 display: table;
6 }
7
8 #errorExplanation {
9 width: 400px;
10 border: 2px solid red;
11 padding: 7px;
12 padding-bottom: 12px;
13 margin-bottom: 20px;
14 background-color: #f0f0f0;
15 }
16
17 #errorExplanation h2 {
18 text-align: left;
19 font-weight: bold;
20 padding: 5px 5px 5px 15px;
21 font-size: 12px;
22 margin: -7px;
23 background-color: #c00;
24 color: #fff;
25 }
26
27 #errorExplanation p {
28 color: #333;
29 margin-bottom: 0;
30 padding: 5px;
31 }
32
33 #errorExplanation ul li {
34 font-size: 12px;
35 list-style: square;
36 }
37
38 div.uploadStatus {
39 margin: 5px;
40 }
41
42 div.progressBar {
43 margin: 5px;
44 }
45
46 div.progressBar div.border {
47 background-color: #fff;
48 border: 1px solid grey;
49 width: 100%;
50 }
51
52 div.progressBar div.background {
53 background-color: #333;
54 height: 18px;
55 width: 0%;
56 }
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../config/boot'
3 require 'commands/about' No newline at end of file
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../config/boot'
3 require 'commands/breakpointer' No newline at end of file
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../config/boot'
3 require 'commands/console' No newline at end of file
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../config/boot'
3 require 'commands/destroy' No newline at end of file
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../config/boot'
3 require 'commands/generate' No newline at end of file
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../../config/boot'
3 require 'commands/performance/benchmarker'
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../../config/boot'
3 require 'commands/performance/profiler'
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../config/boot'
3 require 'commands/plugin' No newline at end of file
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../../config/boot'
3 require 'commands/process/reaper'
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../../config/boot'
3 require 'commands/process/spawner'
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../../config/boot'
3 require 'commands/process/spinner'
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../config/boot'
3 require 'commands/runner' No newline at end of file
@@ -0,0 +1,3
1 #!/usr/bin/env ruby
2 require File.dirname(__FILE__) + '/../config/boot'
3 require 'commands/server' No newline at end of file
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,3
1 Mailer#issue_closed
2
3 Find me in app/views/mailer/issue_closed.rhtml
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,5
1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 first:
3 id: 1
4 another:
5 id: 2
@@ -0,0 +1,18
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'account_controller'
3
4 # Re-raise errors caught by the controller.
5 class AccountController; def rescue_action(e) raise e end; end
6
7 class AccountControllerTest < Test::Unit::TestCase
8 def setup
9 @controller = AccountController.new
10 @request = ActionController::TestRequest.new
11 @response = ActionController::TestResponse.new
12 end
13
14 # Replace this with your real tests.
15 def test_truth
16 assert true
17 end
18 end
@@ -0,0 +1,18
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'admin_controller'
3
4 # Re-raise errors caught by the controller.
5 class AdminController; def rescue_action(e) raise e end; end
6
7 class AdminControllerTest < Test::Unit::TestCase
8 def setup
9 @controller = AdminController.new
10 @request = ActionController::TestRequest.new
11 @response = ActionController::TestResponse.new
12 end
13
14 # Replace this with your real tests.
15 def test_truth
16 assert true
17 end
18 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'custom_fields_controller'
3
4 # Re-raise errors caught by the controller.
5 class CustomFieldsController; def rescue_action(e) raise e end; end
6
7 class CustomFieldsControllerTest < Test::Unit::TestCase
8 fixtures :custom_fields
9
10 def setup
11 @controller = CustomFieldsController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:custom_fields)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:custom_field)
38 assert assigns(:custom_field).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:custom_field)
48 end
49
50 def test_create
51 num_custom_fields = CustomField.count
52
53 post :create, :custom_field => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_custom_fields + 1, CustomField.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:custom_field)
68 assert assigns(:custom_field).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil CustomField.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 CustomField.find(1)
86 }
87 end
88 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'documents_controller'
3
4 # Re-raise errors caught by the controller.
5 class DocumentsController; def rescue_action(e) raise e end; end
6
7 class DocumentsControllerTest < Test::Unit::TestCase
8 fixtures :documents
9
10 def setup
11 @controller = DocumentsController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:documents)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:document)
38 assert assigns(:document).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:document)
48 end
49
50 def test_create
51 num_documents = Document.count
52
53 post :create, :document => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_documents + 1, Document.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:document)
68 assert assigns(:document).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil Document.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 Document.find(1)
86 }
87 end
88 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'enumerations_controller'
3
4 # Re-raise errors caught by the controller.
5 class EnumerationsController; def rescue_action(e) raise e end; end
6
7 class EnumerationsControllerTest < Test::Unit::TestCase
8 fixtures :enumerations
9
10 def setup
11 @controller = EnumerationsController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:enumerations)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:enumeration)
38 assert assigns(:enumeration).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:enumeration)
48 end
49
50 def test_create
51 num_enumerations = Enumeration.count
52
53 post :create, :enumeration => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_enumerations + 1, Enumeration.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:enumeration)
68 assert assigns(:enumeration).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil Enumeration.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 Enumeration.find(1)
86 }
87 end
88 end
@@ -0,0 +1,18
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'help_controller'
3
4 # Re-raise errors caught by the controller.
5 class HelpController; def rescue_action(e) raise e end; end
6
7 class HelpControllerTest < Test::Unit::TestCase
8 def setup
9 @controller = HelpController.new
10 @request = ActionController::TestRequest.new
11 @response = ActionController::TestResponse.new
12 end
13
14 # Replace this with your real tests.
15 def test_truth
16 assert true
17 end
18 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'issue_categories_controller'
3
4 # Re-raise errors caught by the controller.
5 class IssueCategoriesController; def rescue_action(e) raise e end; end
6
7 class IssueCategoriesControllerTest < Test::Unit::TestCase
8 fixtures :issue_categories
9
10 def setup
11 @controller = IssueCategoriesController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:issue_categories)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:issue_category)
38 assert assigns(:issue_category).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:issue_category)
48 end
49
50 def test_create
51 num_issue_categories = IssueCategory.count
52
53 post :create, :issue_category => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_issue_categories + 1, IssueCategory.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:issue_category)
68 assert assigns(:issue_category).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil IssueCategory.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 IssueCategory.find(1)
86 }
87 end
88 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'issue_statuses_controller'
3
4 # Re-raise errors caught by the controller.
5 class IssueStatusesController; def rescue_action(e) raise e end; end
6
7 class IssueStatusesControllerTest < Test::Unit::TestCase
8 fixtures :issue_statuses
9
10 def setup
11 @controller = IssueStatusesController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:issue_statuses)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:issue_status)
38 assert assigns(:issue_status).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:issue_status)
48 end
49
50 def test_create
51 num_issue_statuses = IssueStatus.count
52
53 post :create, :issue_status => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_issue_statuses + 1, IssueStatus.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:issue_status)
68 assert assigns(:issue_status).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil IssueStatus.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 IssueStatus.find(1)
86 }
87 end
88 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'issues_controller'
3
4 # Re-raise errors caught by the controller.
5 class IssuesController; def rescue_action(e) raise e end; end
6
7 class IssuesControllerTest < Test::Unit::TestCase
8 fixtures :issues
9
10 def setup
11 @controller = IssuesController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:issues)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:issue)
38 assert assigns(:issue).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:issue)
48 end
49
50 def test_create
51 num_issues = Issue.count
52
53 post :create, :issue => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_issues + 1, Issue.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:issue)
68 assert assigns(:issue).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil Issue.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 Issue.find(1)
86 }
87 end
88 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'members_controller'
3
4 # Re-raise errors caught by the controller.
5 class MembersController; def rescue_action(e) raise e end; end
6
7 class MembersControllerTest < Test::Unit::TestCase
8 fixtures :members
9
10 def setup
11 @controller = MembersController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:members)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:member)
38 assert assigns(:member).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:member)
48 end
49
50 def test_create
51 num_members = Member.count
52
53 post :create, :member => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_members + 1, Member.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:member)
68 assert assigns(:member).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil Member.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 Member.find(1)
86 }
87 end
88 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'news_controller'
3
4 # Re-raise errors caught by the controller.
5 class NewsController; def rescue_action(e) raise e end; end
6
7 class NewsControllerTest < Test::Unit::TestCase
8 fixtures :news
9
10 def setup
11 @controller = NewsController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:news)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:news)
38 assert assigns(:news).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:news)
48 end
49
50 def test_create
51 num_news = News.count
52
53 post :create, :news => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_news + 1, News.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:news)
68 assert assigns(:news).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil News.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 News.find(1)
86 }
87 end
88 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'projects_controller'
3
4 # Re-raise errors caught by the controller.
5 class ProjectsController; def rescue_action(e) raise e end; end
6
7 class ProjectsControllerTest < Test::Unit::TestCase
8 fixtures :projects
9
10 def setup
11 @controller = ProjectsController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:projects)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:project)
38 assert assigns(:project).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:project)
48 end
49
50 def test_create
51 num_projects = Project.count
52
53 post :create, :project => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_projects + 1, Project.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:project)
68 assert assigns(:project).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil Project.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 Project.find(1)
86 }
87 end
88 end
@@ -0,0 +1,18
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'reports_controller'
3
4 # Re-raise errors caught by the controller.
5 class ReportsController; def rescue_action(e) raise e end; end
6
7 class ReportsControllerTest < Test::Unit::TestCase
8 def setup
9 @controller = ReportsController.new
10 @request = ActionController::TestRequest.new
11 @response = ActionController::TestResponse.new
12 end
13
14 # Replace this with your real tests.
15 def test_truth
16 assert true
17 end
18 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'roles_controller'
3
4 # Re-raise errors caught by the controller.
5 class RolesController; def rescue_action(e) raise e end; end
6
7 class RolesControllerTest < Test::Unit::TestCase
8 fixtures :roles
9
10 def setup
11 @controller = RolesController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:roles)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:role)
38 assert assigns(:role).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:role)
48 end
49
50 def test_create
51 num_roles = Role.count
52
53 post :create, :role => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_roles + 1, Role.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:role)
68 assert assigns(:role).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil Role.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 Role.find(1)
86 }
87 end
88 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'trackers_controller'
3
4 # Re-raise errors caught by the controller.
5 class TrackersController; def rescue_action(e) raise e end; end
6
7 class TrackersControllerTest < Test::Unit::TestCase
8 fixtures :trackers
9
10 def setup
11 @controller = TrackersController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:trackers)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:tracker)
38 assert assigns(:tracker).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:tracker)
48 end
49
50 def test_create
51 num_trackers = Tracker.count
52
53 post :create, :tracker => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_trackers + 1, Tracker.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:tracker)
68 assert assigns(:tracker).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil Tracker.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 Tracker.find(1)
86 }
87 end
88 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'users_controller'
3
4 # Re-raise errors caught by the controller.
5 class UsersController; def rescue_action(e) raise e end; end
6
7 class UsersControllerTest < Test::Unit::TestCase
8 fixtures :users
9
10 def setup
11 @controller = UsersController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:users)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:user)
38 assert assigns(:user).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:user)
48 end
49
50 def test_create
51 num_users = User.count
52
53 post :create, :user => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_users + 1, User.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:user)
68 assert assigns(:user).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil User.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 User.find(1)
86 }
87 end
88 end
@@ -0,0 +1,88
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'versions_controller'
3
4 # Re-raise errors caught by the controller.
5 class VersionsController; def rescue_action(e) raise e end; end
6
7 class VersionsControllerTest < Test::Unit::TestCase
8 fixtures :versions
9
10 def setup
11 @controller = VersionsController.new
12 @request = ActionController::TestRequest.new
13 @response = ActionController::TestResponse.new
14 end
15
16 def test_index
17 get :index
18 assert_response :success
19 assert_template 'list'
20 end
21
22 def test_list
23 get :list
24
25 assert_response :success
26 assert_template 'list'
27
28 assert_not_nil assigns(:versions)
29 end
30
31 def test_show
32 get :show, :id => 1
33
34 assert_response :success
35 assert_template 'show'
36
37 assert_not_nil assigns(:version)
38 assert assigns(:version).valid?
39 end
40
41 def test_new
42 get :new
43
44 assert_response :success
45 assert_template 'new'
46
47 assert_not_nil assigns(:version)
48 end
49
50 def test_create
51 num_versions = Version.count
52
53 post :create, :version => {}
54
55 assert_response :redirect
56 assert_redirected_to :action => 'list'
57
58 assert_equal num_versions + 1, Version.count
59 end
60
61 def test_edit
62 get :edit, :id => 1
63
64 assert_response :success
65 assert_template 'edit'
66
67 assert_not_nil assigns(:version)
68 assert assigns(:version).valid?
69 end
70
71 def test_update
72 post :update, :id => 1
73 assert_response :redirect
74 assert_redirected_to :action => 'show', :id => 1
75 end
76
77 def test_destroy
78 assert_not_nil Version.find(1)
79
80 post :destroy, :id => 1
81 assert_response :redirect
82 assert_redirected_to :action => 'list'
83
84 assert_raise(ActiveRecord::RecordNotFound) {
85 Version.find(1)
86 }
87 end
88 end
@@ -0,0 +1,18
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'welcome_controller'
3
4 # Re-raise errors caught by the controller.
5 class WelcomeController; def rescue_action(e) raise e end; end
6
7 class WelcomeControllerTest < Test::Unit::TestCase
8 def setup
9 @controller = WelcomeController.new
10 @request = ActionController::TestRequest.new
11 @response = ActionController::TestResponse.new
12 end
13
14 # Replace this with your real tests.
15 def test_truth
16 assert true
17 end
18 end
@@ -0,0 +1,28
1 ENV["RAILS_ENV"] = "test"
2 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
3 require 'test_help'
4
5 class Test::Unit::TestCase
6 # Transactional fixtures accelerate your tests by wrapping each test method
7 # in a transaction that's rolled back on completion. This ensures that the
8 # test database remains unchanged so your fixtures don't have to be reloaded
9 # between every test method. Fewer database queries means faster tests.
10 #
11 # Read Mike Clark's excellent walkthrough at
12 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
13 #
14 # Every Active Record database supports transactions except MyISAM tables
15 # in MySQL. Turn off transactional fixtures in this case; however, if you
16 # don't care one way or the other, switching from MyISAM to InnoDB tables
17 # is recommended.
18 self.use_transactional_fixtures = true
19
20 # Instantiated fixtures are slow, but give you @david where otherwise you
21 # would need people(:david). If you don't want to migrate your existing
22 # test cases which use the @david style and don't mind the speed hit (each
23 # instantiated fixtures translates to a database query per test method),
24 # then set this back to true.
25 self.use_instantiated_fixtures = false
26
27 # Add more helper methods to be used by all tests here...
28 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class AttachmentTest < Test::Unit::TestCase
4 fixtures :attachments
5
6 # Replace this with your real tests.
7 def test_truth
8 assert true
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class CustomFieldTest < Test::Unit::TestCase
4 fixtures :custom_fields
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of CustomField, custom_fields(:first)
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class DocumentTest < Test::Unit::TestCase
4 fixtures :documents
5
6 # Replace this with your real tests.
7 def test_truth
8 assert true
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class EnumerationTest < Test::Unit::TestCase
4 fixtures :enumerations
5
6 # Replace this with your real tests.
7 def test_truth
8 assert true
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class IssueCategoryTest < Test::Unit::TestCase
4 fixtures :issue_categories
5
6 # Replace this with your real tests.
7 def test_truth
8 assert true
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class IssueCustomFieldTest < Test::Unit::TestCase
4 fixtures :issue_custom_fields
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of IssueCustomField, issue_custom_fields(:first)
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class IssueCustomValueTest < Test::Unit::TestCase
4 fixtures :issue_custom_values
5
6 # Replace this with your real tests.
7 def test_truth
8 assert true
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class IssueHistoryTest < Test::Unit::TestCase
4 fixtures :issue_histories
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of IssueHistory, issue_histories(:first)
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class IssueStatusTest < Test::Unit::TestCase
4 fixtures :issue_statuses
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of IssueStatus, issue_statuses(:first)
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class IssueTest < Test::Unit::TestCase
4 fixtures :issues
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of Issue, issues(:first)
9 end
10 end
@@ -0,0 +1,35
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'mailer'
3
4 class MailerTest < Test::Unit::TestCase
5 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures'
6 CHARSET = "utf-8"
7
8 include ActionMailer::Quoting
9
10 def setup
11 ActionMailer::Base.delivery_method = :test
12 ActionMailer::Base.perform_deliveries = true
13 ActionMailer::Base.deliveries = []
14
15 @expected = TMail::Mail.new
16 @expected.set_content_type "text", "plain", { "charset" => CHARSET }
17 end
18
19 def test_issue_closed
20 @expected.subject = 'Mailer#issue_closed'
21 @expected.body = read_fixture('issue_closed')
22 @expected.date = Time.now
23
24 assert_equal @expected.encoded, Mailer.create_issue_closed(@expected.date).encoded
25 end
26
27 private
28 def read_fixture(action)
29 IO.readlines("#{FIXTURES_PATH}/mailer/#{action}")
30 end
31
32 def encode(subject)
33 quoted_printable(subject, CHARSET)
34 end
35 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class MemberTest < Test::Unit::TestCase
4 fixtures :members
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of Member, members(:first)
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class NewsTest < Test::Unit::TestCase
4 fixtures :news
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of News, news(:first)
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class PackagesTest < Test::Unit::TestCase
4 fixtures :packages
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of Packages, packages(:first)
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class PermissionTest < Test::Unit::TestCase
4 fixtures :permissions
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of Permission, permissions(:first)
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class ProjectTest < Test::Unit::TestCase
4 fixtures :projects
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of Project, projects(:first)
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class RoleTest < Test::Unit::TestCase
4 fixtures :roles
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of Role, roles(:first)
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class TrackerTest < Test::Unit::TestCase
4 fixtures :trackers
5
6 # Replace this with your real tests.
7 def test_truth
8 assert true
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class UserTest < Test::Unit::TestCase
4 fixtures :users
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of User, users(:first)
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class VersionTest < Test::Unit::TestCase
4 fixtures :versions
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of Version, versions(:first)
9 end
10 end
@@ -0,0 +1,10
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class WorkflowTest < Test::Unit::TestCase
4 fixtures :workflows
5
6 # Replace this with your real tests.
7 def test_truth
8 assert_kind_of Workflow, workflows(:first)
9 end
10 end
@@ -0,0 +1,85
1 = Localization Plugin for Rails
2
3 This plugin provides a simple, gettext-like method to
4 provide localizations.
5
6 == Features
7
8 * Any number of languages or locales
9 * Simple method to defines singluar/plural translations
10 * Can use lambdas to provide Ruby-code based dynamic translations
11 * Customizable for different instances of the application
12
13 == Usage
14
15 If the localization plugin is installed, it is used automatically.
16
17 You need to create a /lang dir in your RAILS_ROOT.
18
19 The recommended way to use it is to create files that are named
20 like the languages you define in them (but you can put everything in
21 one big file too.)
22
23 For instance-customizable strings, add overrides in files you
24 put in /lang/custom.
25
26 === Simple example:
27
28 Create a file /lang/translations.rb:
29
30 Localization.define('de') do |l|
31 l.store 'yes', 'Ja'
32 l.store 'no', 'Nein'
33 end
34
35 Localization.define('fr') do |l|
36 l.store 'yes', 'oui'
37 l.store 'no', 'non'
38 end
39
40 In your controller or application.rb:
41
42 Localization.lang = 'de' # or 'fr'
43
44 In your view:
45
46 <%=_ 'yes' %>
47 <%=_ 'no' %>
48
49 Because the _ method is simply an extension to Object, you
50 can use it anywhere (models/controllers/views/libs).
51
52 === Extended example:
53
54 Create a file /lang/default.rb with following contents:
55
56 Localization.define do |l|
57 l.store '(time)', lambda { |t| t.strftime('%I:%M%p') }
58 end
59
60 Create a file /lang/de_DE.rb with following contents:
61
62 Localization.define('de_DE') do |l|
63 l.store '%d entries', ['Ein Eintrag', '%d Einträge']
64 l.store '(time)', lambda { |t| t.strftime('%H:%M') }
65 end
66
67 In your controller or application.rb:
68
69 Localization.lang = 'de_DE'
70
71 In your view:
72
73 <%=_ '%d entries', 1 %> # singular variant is chosen
74 <%=_ '%d entries', 4 %> # plural variant is chosen
75 <%=_ '(time)', Time.now %> # call the block with a parameter
76
77 == Translation file guesstimation
78
79 You can generate a guesstimation of all strings needed to be
80 translated in your views by first adding the _('blah') syntax
81 everywhere and then calling:
82
83 puts Localization.generate_l10n_file
84
85 in the Rails console. No newline at end of file
@@ -0,0 +1,3
1 require "#{directory}/lib/localization.rb"
2
3 Localization.load No newline at end of file
@@ -0,0 +1,57
1 # Original Localization plugin for Rails can be found at:
2 # http://mir.aculo.us/articles/2005/10/03/ruby-on-rails-i18n-revisited
3 #
4 # Slightly edited by Jean-Philippe Lang
5 # - added @@langs and modified self.define method to maintain an array of available
6 # langages with labels, eg. { 'en' => 'English, 'fr' => 'French }
7 # - modified self.generate_l10n_file method to retrieve already translated strings
8 #
9
10 module Localization
11 mattr_accessor :lang, :langs
12
13 @@l10s = { :default => {} }
14 @@lang = :default
15 @@langs = []
16
17 def self._(string_to_localize, *args)
18 translated = @@l10s[@@lang][string_to_localize] || string_to_localize
19 return translated.call(*args).to_s if translated.is_a? Proc
20 if translated.is_a? Array
21 translated = if translated.size == 3
22 translated[args[0]==0 ? 0 : (args[0]>1 ? 2 : 1)]
23 else
24 translated[args[0]>1 ? 1 : 0]
25 end
26 end
27 sprintf translated, *args
28 end
29
30 def self.define(lang = :default, name = :default)
31 @@l10s[lang] ||= {}
32 @@langs << [ name, lang ]
33 yield @@l10s[lang]
34 end
35
36 def self.load
37 Dir.glob("#{RAILS_ROOT}/lang/*.rb"){ |t| require t }
38 Dir.glob("#{RAILS_ROOT}/lang/custom/*.rb"){ |t| require t }
39 end
40
41 # Generates a best-estimate l10n file from all views by
42 # collecting calls to _() -- note: use the generated file only
43 # as a start (this method is only guesstimating)
44 def self.generate_l10n_file(lang)
45 "Localization.define('en_US') do |l|\n" <<
46 Dir.glob("#{RAILS_ROOT}/app/views/**/*.rhtml").collect do |f|
47 ["# #{f}"] << File.read(f).scan(/<%.*[^\w]_\s*[\(]+[\"\'](.*?)[\"\'][\)]+/)
48 end.uniq.flatten.collect do |g|
49 g.starts_with?('#') ? "\n #{g}" : " l.store '#{g}', '#{@@l10s[lang][g]}'"
50 end.uniq.join("\n") << "\nend"
51 end
52
53 end
54
55 class Object
56 def _(*args); Localization._(*args); end
57 end No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now