diff options
284 files changed, 12752 insertions, 0 deletions
diff --git a/redmine/Rakefile b/redmine/Rakefile new file mode 100644 index 000000000..cffd19f0c --- /dev/null +++ b/redmine/Rakefile @@ -0,0 +1,10 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails'
\ No newline at end of file diff --git a/redmine/app/controllers/account_controller.rb b/redmine/app/controllers/account_controller.rb new file mode 100644 index 000000000..d5c98f58c --- /dev/null +++ b/redmine/app/controllers/account_controller.rb @@ -0,0 +1,83 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class AccountController < ApplicationController
+ layout 'base'
+ # prevents login action to be filtered by check_if_login_required application scope filter
+ skip_before_filter :check_if_login_required, :only => :login
+ before_filter :require_login, :except => [:show, :login]
+
+ def show
+ @user = User.find(params[:id])
+ end
+
+ # Login request and validation
+ def login
+ if request.get?
+ session[:user] = nil
+ @user = User.new
+ else
+ @user = User.new(params[:user])
+ logged_in_user = @user.try_to_login
+ if logged_in_user
+ session[:user] = logged_in_user
+ redirect_back_or_default :controller => 'account', :action => 'my_page'
+ else
+ flash[:notice] = _('Invalid user/password')
+ end
+ end
+ end
+
+ # Log out current user and redirect to welcome page
+ def logout
+ session[:user] = nil
+ redirect_to(:controller => '')
+ end
+
+ def my_page
+ @user = session[:user]
+ @reported_issues = Issue.find(:all, :conditions => ["author_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')
+ @assigned_issues = Issue.find(:all, :conditions => ["assigned_to_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')
+ end
+
+ # Edit current user's account
+ def my_account
+ @user = User.find(session[:user].id)
+ if request.post? and @user.update_attributes(@params[:user])
+ flash[:notice] = 'Account was successfully updated.'
+ session[:user] = @user
+ set_localization
+ end
+ end
+
+ # Change current user's password
+ def change_password
+ @user = User.find(session[:user].id)
+ if @user.check_password?(@params[:old_password])
+ if @params[:new_password] == @params[:new_password_confirmation]
+ if @user.change_password(@params[:old_password], @params[:new_password])
+ flash[:notice] = 'Password was successfully updated.'
+ end
+ else
+ flash[:notice] = 'Password confirmation doesn\'t match!'
+ end
+ else
+ flash[:notice] = 'Wrong password'
+ end
+ render :action => 'my_account'
+ end +end diff --git a/redmine/app/controllers/admin_controller.rb b/redmine/app/controllers/admin_controller.rb new file mode 100644 index 000000000..fa34baff1 --- /dev/null +++ b/redmine/app/controllers/admin_controller.rb @@ -0,0 +1,49 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class AdminController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+
+ helper :sort
+ include SortHelper
+
+ def index
+ end
+
+ def projects
+ sort_init 'projects.name', 'asc'
+ sort_update
+ @project_pages, @projects = paginate :projects, :per_page => 15, :order => sort_clause
+ end
+
+ def mail_options
+ @actions = Permission.find(:all, :conditions => ["mail_option=?", true]) || []
+ if request.post?
+ @actions.each { |a|
+ a.mail_enabled = params[:action_ids].include? a.id.to_s
+ a.save
+ }
+ flash[:notice] = "Mail options were successfully updated."
+ end
+ end
+
+ def info
+ @adapter_name = ActiveRecord::Base.connection.adapter_name
+ end
+
+end diff --git a/redmine/app/controllers/application.rb b/redmine/app/controllers/application.rb new file mode 100644 index 000000000..a9dd6b80e --- /dev/null +++ b/redmine/app/controllers/application.rb @@ -0,0 +1,86 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class ApplicationController < ActionController::Base
+ before_filter :check_if_login_required, :set_localization
+
+ # check if login is globally required to access the application
+ def check_if_login_required
+ require_login if RDM_LOGIN_REQUIRED
+ end
+
+ def set_localization
+ Localization.lang = session[:user].nil? ? RDM_DEFAULT_LANG : (session[:user].language || RDM_DEFAULT_LANG)
+ end
+
+ def require_login
+ unless session[:user]
+ store_location
+ redirect_to(:controller => "account", :action => "login")
+ end
+ end
+
+ def require_admin
+ if session[:user].nil?
+ store_location
+ redirect_to(:controller => "account", :action => "login")
+ else
+ unless session[:user].admin?
+ flash[:notice] = "Acces not allowed"
+ redirect_to(:controller => "projects", :action => "list")
+ end
+ end
+ end
+
+ # authorizes the user for the requested action.
+ def authorize
+ # check if action is allowed on public projects
+ if @project.public? and Permission.allowed_to_public "%s/%s" % [ @params[:controller], @params[:action] ]
+ return true
+ end
+ # if user is not logged in, he is redirect to login form
+ unless session[:user]
+ store_location
+ redirect_to(:controller => "account", :action => "login")
+ return false
+ end
+ # check if user is authorized
+ if session[:user].admin? or Permission.allowed_to_role( "%s/%s" % [ @params[:controller], @params[:action] ], session[:user].role_for_project(@project.id) )
+ return true
+ end
+ flash[:notice] = "Acces denied"
+ redirect_to(:controller => "")
+ return false
+ end
+
+ # store current uri in the session. + # we can return to this location by calling redirect_back_or_default + def store_location + session[:return_to] = @request.request_uri + end + + # move to the last store_location call or to the passed default one + def redirect_back_or_default(default) + if session[:return_to].nil? + redirect_to default + else + redirect_to_url session[:return_to] + session[:return_to] = nil + end + end
+ +end
\ No newline at end of file diff --git a/redmine/app/controllers/custom_fields_controller.rb b/redmine/app/controllers/custom_fields_controller.rb new file mode 100644 index 000000000..93f6353fa --- /dev/null +++ b/redmine/app/controllers/custom_fields_controller.rb @@ -0,0 +1,58 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class CustomFieldsController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+ + def index + list + render :action => 'list' + end + + def list + @custom_field_pages, @custom_fields = paginate :custom_fields, :per_page => 10 + end + + def new
+ if request.get? + @custom_field = CustomField.new + else + @custom_field = CustomField.new(params[:custom_field]) + if @custom_field.save + flash[:notice] = 'CustomField was successfully created.' + redirect_to :action => 'list' + end
+ end + end + + def edit + @custom_field = CustomField.find(params[:id]) + if request.post? and @custom_field.update_attributes(params[:custom_field]) + flash[:notice] = 'CustomField was successfully updated.' + redirect_to :action => 'list' + end + end + + def destroy + CustomField.find(params[:id]).destroy + redirect_to :action => 'list'
+ rescue
+ flash[:notice] = "Unable to delete custom field"
+ redirect_to :action => 'list' + end +end diff --git a/redmine/app/controllers/documents_controller.rb b/redmine/app/controllers/documents_controller.rb new file mode 100644 index 000000000..3c76465c9 --- /dev/null +++ b/redmine/app/controllers/documents_controller.rb @@ -0,0 +1,65 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class DocumentsController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize
+ + def show + end + + def edit
+ @categories = Enumeration::get_values('DCAT') + if request.post? and @document.update_attributes(params[:document]) + flash[:notice] = 'Document was successfully updated.' + redirect_to :action => 'show', :id => @document + end + end
+ + def destroy + @document.destroy + redirect_to :controller => 'projects', :action => 'list_documents', :id => @project + end
+
+ def download
+ @attachment = @document.attachments.find(params[:attachment_id])
+ @attachment.increment_download
+ send_file @attachment.diskfile, :filename => @attachment.filename
+ end
+
+ def add_attachment
+ # Save the attachment
+ if params[:attachment][:file].size > 0
+ @attachment = @document.attachments.build(params[:attachment])
+ @attachment.author_id = session[:user].id unless session[:user].nil?
+ @attachment.save
+ end
+ render :action => 'show'
+ end
+
+ def destroy_attachment
+ @document.attachments.find(params[:attachment_id]).destroy
+ render :action => 'show'
+ end +
+private
+ def find_project
+ @document = Document.find(params[:id])
+ @project = @document.project
+ end + +end diff --git a/redmine/app/controllers/enumerations_controller.rb b/redmine/app/controllers/enumerations_controller.rb new file mode 100644 index 000000000..01664c85e --- /dev/null +++ b/redmine/app/controllers/enumerations_controller.rb @@ -0,0 +1,69 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class EnumerationsController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+ + def index + list + render :action => 'list' + end + + # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) + verify :method => :post, :only => [ :destroy, :create, :update ], + :redirect_to => { :action => :list } + + def list + end + + def new + @enumeration = Enumeration.new(:opt => params[:opt]) + end + + def create + @enumeration = Enumeration.new(params[:enumeration]) + if @enumeration.save + flash[:notice] = 'Enumeration was successfully created.' + redirect_to :action => 'list', :opt => @enumeration.opt + else + render :action => 'new' + end + end + + def edit + @enumeration = Enumeration.find(params[:id]) + end + + def update + @enumeration = Enumeration.find(params[:id]) + if @enumeration.update_attributes(params[:enumeration]) + flash[:notice] = 'Enumeration was successfully updated.' + redirect_to :action => 'list', :opt => @enumeration.opt + else + render :action => 'edit' + end + end + + def destroy + Enumeration.find(params[:id]).destroy + redirect_to :action => 'list'
+ rescue
+ flash[:notice] = "Unable to delete enumeration"
+ redirect_to :action => 'list' + end +end diff --git a/redmine/app/controllers/help_controller.rb b/redmine/app/controllers/help_controller.rb new file mode 100644 index 000000000..4b555d599 --- /dev/null +++ b/redmine/app/controllers/help_controller.rb @@ -0,0 +1,43 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class HelpController < ApplicationController
+
+ skip_before_filter :check_if_login_required
+ before_filter :load_help_config
+
+ def index
+ if @params[:ctrl] and @help_config[@params[:ctrl]]
+ if @params[:page] and @help_config[@params[:ctrl]][@params[:page]]
+ template = @help_config[@params[:ctrl]][@params[:page]]
+ else
+ template = @help_config[@params[:ctrl]]['index']
+ end
+ end
+
+ if template
+ redirect_to "/manual/#{template}"
+ else
+ redirect_to "/manual/"
+ end
+ end
+
+private
+ def load_help_config
+ @help_config = YAML::load(File.open("#{RAILS_ROOT}/config/help.yml"))
+ end +end diff --git a/redmine/app/controllers/issue_categories_controller.rb b/redmine/app/controllers/issue_categories_controller.rb new file mode 100644 index 000000000..cf0f9dc06 --- /dev/null +++ b/redmine/app/controllers/issue_categories_controller.rb @@ -0,0 +1,42 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class IssueCategoriesController < ApplicationController + layout 'base'
+ before_filter :find_project, :authorize
+
+ def edit + if request.post? and @category.update_attributes(params[:category]) + flash[:notice] = 'Issue category was successfully updated.' + redirect_to :controller => 'projects', :action => 'settings', :id => @project + end + end + + def destroy + @category.destroy + redirect_to :controller => 'projects', :action => 'settings', :id => @project
+ rescue
+ flash[:notice] = "Categorie can't be deleted" + redirect_to :controller => 'projects', :action => 'settings', :id => @project
+ end
+ +private
+ def find_project
+ @category = IssueCategory.find(params[:id])
+ @project = @category.project
+ end
+end diff --git a/redmine/app/controllers/issue_statuses_controller.rb b/redmine/app/controllers/issue_statuses_controller.rb new file mode 100644 index 000000000..3de8158c6 --- /dev/null +++ b/redmine/app/controllers/issue_statuses_controller.rb @@ -0,0 +1,68 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class IssueStatusesController < ApplicationController
+ layout 'base' + before_filter :require_admin
+
+ def index + list + render :action => 'list' + end + + def list + @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 10 + end + + def new + @issue_status = IssueStatus.new + end + + def create + @issue_status = IssueStatus.new(params[:issue_status]) + if @issue_status.save + flash[:notice] = 'IssueStatus was successfully created.' + redirect_to :action => 'list' + else + render :action => 'new' + end + end + + def edit + @issue_status = IssueStatus.find(params[:id]) + end + + def update + @issue_status = IssueStatus.find(params[:id]) + if @issue_status.update_attributes(params[:issue_status]) + flash[:notice] = 'IssueStatus was successfully updated.' + redirect_to :action => 'list' + else + render :action => 'edit' + end + end + + def destroy + IssueStatus.find(params[:id]).destroy + redirect_to :action => 'list'
+ rescue
+ flash[:notice] = "Unable to delete issue status"
+ redirect_to :action => 'list' + end
+
+ +end diff --git a/redmine/app/controllers/issues_controller.rb b/redmine/app/controllers/issues_controller.rb new file mode 100644 index 000000000..5d5872f39 --- /dev/null +++ b/redmine/app/controllers/issues_controller.rb @@ -0,0 +1,102 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class IssuesController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize
+
+ helper :custom_fields
+ include CustomFieldsHelper
+ + def show + @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]
+ end + + def edit + @trackers = Tracker.find(:all)
+ @priorities = Enumeration::get_values('IPRI')
+
+ if request.get?
+ @custom_values = @project.custom_fields_for_issues.collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
+ else
+ # Retrieve custom fields and values
+ @custom_values = @project.custom_fields_for_issues.collect { |x| CustomValue.new(:custom_field => x, :value => params["custom_fields"][x.id.to_s]) }
+
+ @issue.custom_values = @custom_values
+ if @issue.update_attributes(params[:issue]) + flash[:notice] = 'Issue was successfully updated.'
+ redirect_to :action => 'show', :id => @issue + end
+ end + end
+
+ def change_status
+ @history = @issue.histories.build(params[:history])
+ @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]
+
+ if params[:confirm]
+ unless session[:user].nil?
+ @history.author = session[:user]
+ end
+ if @history.save
+ @issue.status = @history.status
+ @issue.fixed_version_id = (params[:issue][:fixed_version_id])
+ @issue.assigned_to_id = (params[:issue][:assigned_to_id])
+ if @issue.save
+ flash[:notice] = 'Issue was successfully updated.'
+ Mailer.deliver_issue_change_status(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
+ redirect_to :action => 'show', :id => @issue
+ end + end
+ end
+ @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
+ + end + + def destroy + @issue.destroy + redirect_to :controller => 'projects', :action => 'list_issues', :id => @project + end
+
+ def add_attachment
+ # Save the attachment
+ if params[:attachment][:file].size > 0
+ @attachment = @issue.attachments.build(params[:attachment])
+ @attachment.author_id = session[:user].id unless session[:user].nil?
+ @attachment.save
+ end
+ redirect_to :action => 'show', :id => @issue
+ end
+
+ def destroy_attachment
+ @issue.attachments.find(params[:attachment_id]).destroy
+ redirect_to :action => 'show', :id => @issue
+ end
+
+ # Send the file in stream mode
+ def download
+ @attachment = @issue.attachments.find(params[:attachment_id])
+ send_file @attachment.diskfile, :filename => @attachment.filename
+ end
+
+private
+ def find_project
+ @issue = Issue.find(params[:id])
+ @project = @issue.project
+ end + +end diff --git a/redmine/app/controllers/members_controller.rb b/redmine/app/controllers/members_controller.rb new file mode 100644 index 000000000..ac6b08d26 --- /dev/null +++ b/redmine/app/controllers/members_controller.rb @@ -0,0 +1,41 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class MembersController < ApplicationController + layout 'base'
+ before_filter :find_project, :authorize
+ + def edit + if request.post? and @member.update_attributes(params[:member]) + flash[:notice] = 'Member was successfully updated.' + redirect_to :controller => 'projects', :action => 'settings', :id => @project
+ end + end + + def destroy + @member.destroy
+ flash[:notice] = 'Member was successfully removed.' + redirect_to :controller => 'projects', :action => 'settings', :id => @project + end
+
+private
+ def find_project
+ @member = Member.find(params[:id])
+ @project = @member.project
+ end + +end diff --git a/redmine/app/controllers/news_controller.rb b/redmine/app/controllers/news_controller.rb new file mode 100644 index 000000000..065336f26 --- /dev/null +++ b/redmine/app/controllers/news_controller.rb @@ -0,0 +1,42 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class NewsController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize
+ + def show + end + + def edit + if request.post? and @news.update_attributes(params[:news]) + flash[:notice] = 'News was successfully updated.' + redirect_to :action => 'show', :id => @news + end + end + + def destroy + @news.destroy + redirect_to :controller => 'projects', :action => 'list_news', :id => @project + end
+
+private
+ def find_project
+ @news = News.find(params[:id])
+ @project = @news.project
+ end +end diff --git a/redmine/app/controllers/projects_controller.rb b/redmine/app/controllers/projects_controller.rb new file mode 100644 index 000000000..2e74045e9 --- /dev/null +++ b/redmine/app/controllers/projects_controller.rb @@ -0,0 +1,260 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class ProjectsController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize, :except => [ :index, :list, :add ]
+ before_filter :require_admin, :only => [ :add, :destroy ]
+
+ helper :sort
+ include SortHelper
+ helper :search_filter
+ include SearchFilterHelper
+ helper :custom_fields
+ include CustomFieldsHelper
+
+ def index + list + render :action => 'list' + end +
+ # Lists public projects + def list
+ sort_init 'projects.name', 'asc'
+ sort_update
+ @project_count = Project.count(["public=?", true])
+ @project_pages = Paginator.new self, @project_count,
+ 15,
+ @params['page']
+ @projects = Project.find :all, :order => sort_clause,
+ :conditions => ["public=?", true],
+ :limit => @project_pages.items_per_page,
+ :offset => @project_pages.current.offset + end
+
+ # Add a new project + def add
+ @custom_fields = CustomField::find_all
+ @project = Project.new(params[:project])
+ if request.post?
+ @project.custom_fields = CustomField.find(@params[:custom_field_ids]) if @params[:custom_field_ids] + if @project.save + flash[:notice] = 'Project was successfully created.' + redirect_to :controller => 'admin', :action => 'projects' + end
+ end
+ end
+
+ # Show @project + def show
+ @members = @project.members.find(:all, :include => [:user, :role]) + end +
+ def settings
+ @custom_fields = CustomField::find_all
+ @issue_category ||= IssueCategory.new
+ @member ||= @project.members.new
+ @roles = Role.find_all
+ @users = User.find_all - @project.members.find(:all, :include => :user).collect{|m| m.user }
+ end
+
+ # Edit @project
+ def edit
+ if request.post?
+ @project.custom_fields = CustomField.find(@params[:custom_field_ids]) if @params[:custom_field_ids]
+ if @project.update_attributes(params[:project]) + flash[:notice] = 'Project was successfully updated.' + redirect_to :action => 'settings', :id => @project
+ else
+ settings
+ render :action => 'settings'
+ end
+ end + end
+
+ # Delete @project + def destroy
+ if request.post? and params[:confirm] + @project.destroy + redirect_to :controller => 'admin', :action => 'projects'
+ end + end
+
+ # Add a new issue category to @project
+ def add_issue_category
+ if request.post?
+ @issue_category = @project.issue_categories.build(params[:issue_category])
+ if @issue_category.save
+ redirect_to :action => 'settings', :id => @project
+ else
+ settings
+ render :action => 'settings'
+ end
+ end
+ end
+
+ # Add a new version to @project
+ def add_version
+ @version = @project.versions.build(params[:version])
+ if request.post? and @version.save
+ redirect_to :action => 'settings', :id => @project
+ end
+ end
+
+ # Add a new member to @project
+ def add_member
+ @member = @project.members.build(params[:member])
+ if request.post?
+ if @member.save
+ flash[:notice] = 'Member was successfully added.'
+ redirect_to :action => 'settings', :id => @project
+ else
+ settings
+ render :action => 'settings'
+ end
+ end
+ end
+
+ # Show members list of @project
+ def list_members
+ @members = @project.members
+ end
+
+ # Add a new document to @project
+ def add_document
+ @categories = Enumeration::get_values('DCAT')
+ @document = @project.documents.build(params[:document])
+ if request.post?
+ # Save the attachment
+ if params[:attachment][:file].size > 0
+ @attachment = @document.attachments.build(params[:attachment])
+ @attachment.author_id = session[:user].id unless session[:user].nil?
+ end
+ if @document.save
+ redirect_to :action => 'list_documents', :id => @project
+ end
+ end
+ end
+
+ # Show documents list of @project + def list_documents
+ @documents = @project.documents
+ end
+
+ # Add a new issue to @project
+ def add_issue
+ @trackers = Tracker.find(:all)
+ @priorities = Enumeration::get_values('IPRI')
+ if request.get?
+ @issue = @project.issues.build
+ @custom_values = @project.custom_fields_for_issues.collect { |x| CustomValue.new(:custom_field => x) }
+ else
+ # Create the issue and set the author
+ @issue = @project.issues.build(params[:issue])
+ @issue.author = session[:user] unless session[:user].nil?
+ # Create the document if a file was sent
+ if params[:attachment][:file].size > 0
+ @attachment = @issue.attachments.build(params[:attachment])
+ @attachment.author_id = session[:user].id unless session[:user].nil?
+ end
+ @custom_values = @project.custom_fields_for_issues.collect { |x| CustomValue.new(:custom_field => x, :value => params["custom_fields"][x.id.to_s]) }
+ @issue.custom_values = @custom_values
+ if @issue.save
+ flash[:notice] = "Issue was successfully added."
+ Mailer.deliver_issue_add(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
+ redirect_to :action => 'list_issues', :id => @project
+ end
+ end
+ end
+
+ # Show issues list of @project
+ def list_issues
+ sort_init 'issues.id', 'desc'
+ sort_update
+
+ search_filter_criteria 'issues.tracker_id', :values => "Tracker.find(:all)"
+ search_filter_criteria 'issues.priority_id', :values => "Enumeration.find(:all, :conditions => ['opt=?','IPRI'])"
+ search_filter_criteria 'issues.category_id', :values => "@project.issue_categories"
+ search_filter_criteria 'issues.status_id', :values => "IssueStatus.find(:all)"
+ search_filter_criteria 'issues.author_id', :values => "User.find(:all)", :label => "display_name"
+ search_filter_update if params[:set_filter] or request.post?
+
+ @issue_count = @project.issues.count(search_filter_clause)
+ @issue_pages = Paginator.new self, @issue_count,
+ 15,
+ @params['page']
+ @issues = @project.issues.find :all, :order => sort_clause,
+ :include => [ :author, :status, :tracker ],
+ :conditions => search_filter_clause,
+ :limit => @issue_pages.items_per_page,
+ :offset => @issue_pages.current.offset
+ end
+
+ # Add a news to @project
+ def add_news
+ @news = @project.news.build(params[:news])
+ if request.post?
+ @news.author = session[:user] unless session[:user].nil?
+ if @news.save
+ redirect_to :action => 'list_news', :id => @project
+ end
+ end
+ end
+
+ # Show news list of @project
+ def list_news
+ @news_pages, @news = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "news.created_on DESC"
+ end
+
+ def add_file
+ if request.post?
+ # Save the attachment
+ if params[:attachment][:file].size > 0
+ @attachment = @project.versions.find(params[:version_id]).attachments.build(params[:attachment])
+ @attachment.author_id = session[:user].id unless session[:user].nil?
+ if @attachment.save
+ redirect_to :controller => 'projects', :action => 'list_files', :id => @project
+ end
+ end
+ end
+ @versions = @project.versions
+ end
+
+ def list_files
+ @versions = @project.versions
+ end
+
+ # Show changelog of @project
+ def changelog
+ @fixed_issues = @project.issues.find(:all,
+ :include => [ :fixed_version, :status, :tracker ],
+ :conditions => [ "issue_statuses.is_closed=? and trackers.is_in_chlog=? and issues.fixed_version_id is not null", true, true]
+ )
+ end
+
+private
+ # Find project of id params[:id]
+ # if not found, redirect to project list
+ # used as a before_filter
+ def find_project
+ @project = Project.find(params[:id])
+ rescue
+ flash[:notice] = 'Project not found.' + redirect_to :action => 'list'
+ end
+
+end diff --git a/redmine/app/controllers/reports_controller.rb b/redmine/app/controllers/reports_controller.rb new file mode 100644 index 000000000..7dd57cc3b --- /dev/null +++ b/redmine/app/controllers/reports_controller.rb @@ -0,0 +1,71 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class ReportsController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize
+
+ def issue_report
+ @statuses = IssueStatus.find_all
+ @trackers = Tracker.find_all
+ @issues_by_tracker =
+ ActiveRecord::Base.connection.select_all("select s.id as status_id,
+ s.is_closed as closed,
+ t.id as tracker_id,
+ count(i.id) as total
+ from
+ issues i, issue_statuses s, trackers t
+ where
+ i.status_id=s.id
+ and i.tracker_id=t.id
+ and i.project_id=#{@project.id}
+ group by s.id, t.id")
+ @priorities = Enumeration::get_values('IPRI')
+ @issues_by_priority =
+ ActiveRecord::Base.connection.select_all("select s.id as status_id,
+ s.is_closed as closed,
+ p.id as priority_id,
+ count(i.id) as total
+ from
+ issues i, issue_statuses s, enumerations p
+ where
+ i.status_id=s.id
+ and i.priority_id=p.id
+ and i.project_id=#{@project.id}
+ group by s.id, p.id")
+ @categories = @project.issue_categories
+ @issues_by_category =
+ ActiveRecord::Base.connection.select_all("select s.id as status_id,
+ s.is_closed as closed,
+ c.id as category_id,
+ count(i.id) as total
+ from
+ issues i, issue_statuses s, issue_categories c
+ where
+ i.status_id=s.id
+ and i.category_id=c.id
+ and i.project_id=#{@project.id}
+ group by s.id, c.id")
+ end
+
+
+private
+ # Find project of id params[:id]
+ def find_project
+ @project = Project.find(params[:id])
+ end +end diff --git a/redmine/app/controllers/roles_controller.rb b/redmine/app/controllers/roles_controller.rb new file mode 100644 index 000000000..6e4fc74e0 --- /dev/null +++ b/redmine/app/controllers/roles_controller.rb @@ -0,0 +1,84 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class RolesController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+ + def index + list + render :action => 'list' + end + + def list + @role_pages, @roles = paginate :roles, :per_page => 10 + end + + def new + @role = Role.new(params[:role])
+ if request.post? + @role.permissions = Permission.find(@params[:permission_ids]) if @params[:permission_ids] + if @role.save + flash[:notice] = 'Role was successfully created.' + redirect_to :action => 'list' + end
+ end + @permissions = Permission.find(:all, :order => 'sort ASC') + end + + def edit + @role = Role.find(params[:id])
+ if request.post? and @role.update_attributes(params[:role]) + @role.permissions = Permission.find(@params[:permission_ids] || [])
+ Permission.allowed_to_role_expired + flash[:notice] = 'Role was successfully updated.' + redirect_to :action => 'list' + end + @permissions = Permission.find(:all, :order => 'sort ASC') + end + + def destroy + @role = Role.find(params[:id])
+ unless @role.members.empty?
+ flash[:notice] = 'Some members have this role. Can\'t delete it.'
+ else
+ @role.destroy
+ end + redirect_to :action => 'list' + end
+
+ def workflow
+ @roles = Role.find_all
+ @trackers = Tracker.find_all
+ @statuses = IssueStatus.find_all
+
+ @role = Role.find_by_id(params[:role_id])
+ @tracker = Tracker.find_by_id(params[:tracker_id])
+
+ if request.post?
+ Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
+ (params[:issue_status] || []).each { |old, news|
+ news.each { |new|
+ @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new)
+ }
+ }
+ if @role.save
+ flash[:notice] = 'Workflow was successfully updated.'
+ end
+ end
+ end +end diff --git a/redmine/app/controllers/trackers_controller.rb b/redmine/app/controllers/trackers_controller.rb new file mode 100644 index 000000000..38cdb6cde --- /dev/null +++ b/redmine/app/controllers/trackers_controller.rb @@ -0,0 +1,60 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class TrackersController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+ + def index + list + render :action => 'list' + end + + # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) + verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :list } + + def list + @tracker_pages, @trackers = paginate :trackers, :per_page => 10 + end + + def new + @tracker = Tracker.new(params[:tracker]) + if request.post? and @tracker.save + flash[:notice] = 'Tracker was successfully created.' + redirect_to :action => 'list' + end + end + + def edit + @tracker = Tracker.find(params[:id]) + if request.post? and @tracker.update_attributes(params[:tracker]) + flash[:notice] = 'Tracker was successfully updated.' + redirect_to :action => 'list' + end + end + + def destroy + @tracker = Tracker.find(params[:id])
+ unless @tracker.issues.empty?
+ flash[:notice] = "This tracker contains issues and can\'t be deleted."
+ else
+ @tracker.destroy
+ end + redirect_to :action => 'list' + end
+ +end diff --git a/redmine/app/controllers/users_controller.rb b/redmine/app/controllers/users_controller.rb new file mode 100644 index 000000000..64c9fd311 --- /dev/null +++ b/redmine/app/controllers/users_controller.rb @@ -0,0 +1,73 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class UsersController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+
+ helper :sort
+ include SortHelper
+ + def index + list + render :action => 'list' + end + + def list
+ sort_init 'users.login', 'asc'
+ sort_update
+ @user_count = User.count
+ @user_pages = Paginator.new self, @user_count,
+ 15,
+ @params['page']
+ @users = User.find :all, :order => sort_clause,
+ :limit => @user_pages.items_per_page,
+ :offset => @user_pages.current.offset + end + + def add + if request.get?
+ @user = User.new
+ else + @user = User.new(params[:user])
+ @user.admin = params[:user][:admin] + if @user.save + flash[:notice] = 'User was successfully created.' + redirect_to :action => 'list' + end
+ end + end + + def edit + @user = User.find(params[:id]) + if request.post?
+ @user.admin = params[:user][:admin] if params[:user][:admin]
+ if @user.update_attributes(params[:user]) + flash[:notice] = 'User was successfully updated.' + redirect_to :action => 'list' + end
+ end + end + + def destroy + User.find(params[:id]).destroy + redirect_to :action => 'list'
+ rescue
+ flash[:notice] = "Unable to delete user"
+ redirect_to :action => 'list' + end
+end diff --git a/redmine/app/controllers/versions_controller.rb b/redmine/app/controllers/versions_controller.rb new file mode 100644 index 000000000..c4fd241a8 --- /dev/null +++ b/redmine/app/controllers/versions_controller.rb @@ -0,0 +1,53 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class VersionsController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize
+ + def edit + if request.post? and @version.update_attributes(params[:version]) + flash[:notice] = 'Version was successfully updated.' + redirect_to :controller => 'projects', :action => 'settings', :id => @project + end + end + + def destroy
+ @version.destroy + redirect_to :controller => 'projects', :action => 'settings', :id => @project
+ rescue
+ flash[:notice] = "Unable to delete version"
+ redirect_to :controller => 'projects', :action => 'settings', :id => @project
+ end
+
+ def download
+ @attachment = @version.attachments.find(params[:attachment_id])
+ @attachment.increment_download
+ send_file @attachment.diskfile, :filename => @attachment.filename
+ end
+
+ def destroy_file
+ @version.attachments.find(params[:attachment_id]).destroy
+ redirect_to :controller => 'projects', :action => 'list_files', :id => @project
+ end
+
+private
+ def find_project
+ @version = Version.find(params[:id])
+ @project = @version.project
+ end +end diff --git a/redmine/app/controllers/welcome_controller.rb b/redmine/app/controllers/welcome_controller.rb new file mode 100644 index 000000000..b266975aa --- /dev/null +++ b/redmine/app/controllers/welcome_controller.rb @@ -0,0 +1,26 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class WelcomeController < ApplicationController
+ layout 'base'
+
+ def index
+ @news = News.latest
+ @projects = Project.latest
+ end
+ +end diff --git a/redmine/app/helpers/account_helper.rb b/redmine/app/helpers/account_helper.rb new file mode 100644 index 000000000..e18ab6ff4 --- /dev/null +++ b/redmine/app/helpers/account_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module AccountHelper +end diff --git a/redmine/app/helpers/admin_helper.rb b/redmine/app/helpers/admin_helper.rb new file mode 100644 index 000000000..db2777392 --- /dev/null +++ b/redmine/app/helpers/admin_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module AdminHelper +end diff --git a/redmine/app/helpers/application_helper.rb b/redmine/app/helpers/application_helper.rb new file mode 100644 index 000000000..4a50b949a --- /dev/null +++ b/redmine/app/helpers/application_helper.rb @@ -0,0 +1,65 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module ApplicationHelper
+
+ def loggedin?
+ session[:user]
+ end +
+ def admin_loggedin?
+ session[:user] && session[:user].admin
+ end
+
+ def authorize_for(controller, action)
+ # check if action is allowed on public projects
+ if @project.public? and Permission.allowed_to_public "%s/%s" % [ controller, action ]
+ return true
+ end
+ # check if user is authorized
+ if session[:user] and (session[:user].admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], session[:user].role_for_project(@project.id) ) )
+ return true
+ end
+ return false
+ end
+
+ def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
+ link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller], options[:action])
+ end
+
+ # Display a link to user's account page
+ def link_to_user(user)
+ link_to user.display_name, :controller => 'account', :action => 'show', :id => user
+ end
+
+ def format_date(date)
+ _('(date)', date) if date
+ end
+
+ def format_time(time)
+ _('(time)', time) if time
+ end
+
+ def pagination_links_full(paginator, options={}, html_options={})
+ html =''
+ html << link_to(('« ' + _('Previous') ), { :page => paginator.current.previous }) + ' ' if paginator.current.previous + html << (pagination_links(paginator, options, html_options) || '')
+ html << ' ' + link_to((_('Next') + ' »'), { :page => paginator.current.next }) if paginator.current.next
+ html
+ end
+ +end diff --git a/redmine/app/helpers/custom_fields_helper.rb b/redmine/app/helpers/custom_fields_helper.rb new file mode 100644 index 000000000..4e3aea50f --- /dev/null +++ b/redmine/app/helpers/custom_fields_helper.rb @@ -0,0 +1,36 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module CustomFieldsHelper
+ def custom_field_tag(custom_value)
+
+ custom_field = custom_value.custom_field
+
+ field_name = "custom_fields[#{custom_field.id}]"
+
+ case custom_field.typ
+ when 0 .. 2
+ text_field_tag field_name, custom_value.value
+ when 3
+ check_box field_name
+ when 4
+ select_tag field_name,
+ options_for_select(custom_field.possible_values.split('|'),
+ custom_value.value)
+ end
+ end +end diff --git a/redmine/app/helpers/documents_helper.rb b/redmine/app/helpers/documents_helper.rb new file mode 100644 index 000000000..c9897647d --- /dev/null +++ b/redmine/app/helpers/documents_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module DocumentsHelper +end diff --git a/redmine/app/helpers/enumerations_helper.rb b/redmine/app/helpers/enumerations_helper.rb new file mode 100644 index 000000000..11a216a82 --- /dev/null +++ b/redmine/app/helpers/enumerations_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module EnumerationsHelper +end diff --git a/redmine/app/helpers/help_helper.rb b/redmine/app/helpers/help_helper.rb new file mode 100644 index 000000000..bb629316c --- /dev/null +++ b/redmine/app/helpers/help_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module HelpHelper +end diff --git a/redmine/app/helpers/issue_categories_helper.rb b/redmine/app/helpers/issue_categories_helper.rb new file mode 100644 index 000000000..997d830d7 --- /dev/null +++ b/redmine/app/helpers/issue_categories_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module IssueCategoriesHelper +end diff --git a/redmine/app/helpers/issue_statuses_helper.rb b/redmine/app/helpers/issue_statuses_helper.rb new file mode 100644 index 000000000..17704b7ba --- /dev/null +++ b/redmine/app/helpers/issue_statuses_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module IssueStatusesHelper +end diff --git a/redmine/app/helpers/issues_helper.rb b/redmine/app/helpers/issues_helper.rb new file mode 100644 index 000000000..40c0e40ae --- /dev/null +++ b/redmine/app/helpers/issues_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module IssuesHelper +end diff --git a/redmine/app/helpers/members_helper.rb b/redmine/app/helpers/members_helper.rb new file mode 100644 index 000000000..8bf90913d --- /dev/null +++ b/redmine/app/helpers/members_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module MembersHelper +end diff --git a/redmine/app/helpers/news_helper.rb b/redmine/app/helpers/news_helper.rb new file mode 100644 index 000000000..f4a633f4b --- /dev/null +++ b/redmine/app/helpers/news_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module NewsHelper +end diff --git a/redmine/app/helpers/projects_helper.rb b/redmine/app/helpers/projects_helper.rb new file mode 100644 index 000000000..0c85ce24c --- /dev/null +++ b/redmine/app/helpers/projects_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module ProjectsHelper +end diff --git a/redmine/app/helpers/reports_helper.rb b/redmine/app/helpers/reports_helper.rb new file mode 100644 index 000000000..ed7fd7884 --- /dev/null +++ b/redmine/app/helpers/reports_helper.rb @@ -0,0 +1,32 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module ReportsHelper
+
+ def aggregate(data, criteria)
+ a = 0
+ data.each { |row|
+ match = 1
+ criteria.each { |k, v|
+ match = 0 unless row[k].to_s == v.to_s
+ } unless criteria.nil?
+ a = a + row["total"].to_i if match == 1
+ } unless data.nil?
+ a
+ end
+ +end diff --git a/redmine/app/helpers/roles_helper.rb b/redmine/app/helpers/roles_helper.rb new file mode 100644 index 000000000..8ae339053 --- /dev/null +++ b/redmine/app/helpers/roles_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module RolesHelper +end diff --git a/redmine/app/helpers/search_filter_helper.rb b/redmine/app/helpers/search_filter_helper.rb new file mode 100644 index 000000000..3a76b3f57 --- /dev/null +++ b/redmine/app/helpers/search_filter_helper.rb @@ -0,0 +1,55 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module SearchFilterHelper
+
+ def search_filter_criteria(field, options = {})
+ session[:search_filter] ||= {}
+ session[:search_filter][field] ||= options
+ # session[:search_filter][field][:values] = options[:values] unless options[:values].nil?
+ # session[:search_filter][field][:label] = options[:label] unless options[:label].nil?
+ end
+
+ def search_filter_update
+ session[:search_filter].each_key {|field| session[:search_filter][field][:value] = params[field] }
+ #@search_filter[:value] = params[@search_filter[:field]]
+ end
+
+ def search_filter_clause
+ clause = "1=1"
+ 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? }
+ clause
+ #@search_filter[:field] + "='" + @search_filter[:value] + "'" unless @search_filter[:value].nil? || @search_filter[:value].empty?
+ end
+
+ def search_filter_tag(field)
+ option_values = []
+ #values = eval @search_filter[:values_expr]
+ option_values = eval session[:search_filter][field][:values]
+
+ content_tag("select",
+ content_tag("option", "[All]", :value => "") +
+ options_from_collection_for_select(option_values,
+ "id",
+ session[:search_filter][field][:label] || "name",
+ session[:search_filter][field][:value].to_i
+ ),
+ :name => field
+ )
+ end
+
+end
\ No newline at end of file diff --git a/redmine/app/helpers/sort_helper.rb b/redmine/app/helpers/sort_helper.rb new file mode 100644 index 000000000..bec2117ec --- /dev/null +++ b/redmine/app/helpers/sort_helper.rb @@ -0,0 +1,157 @@ +# Helpers to sort tables using clickable column headers. +# +# Author: Stuart Rackham <srackham@methods.co.nz>, March 2005. +# License: This source code is released under the MIT license. +# +# - Consecutive clicks toggle the column's sort order. +# - Sort state is maintained by a session hash entry. +# - Icon image identifies sort column and state. +# - Typically used in conjunction with the Pagination module. +# +# Example code snippets: +# +# Controller: +# +# helper :sort +# include SortHelper +# +# def list +# sort_init 'last_name' +# sort_update +# @items = Contact.find_all nil, sort_clause +# end +# +# Controller (using Pagination module): +# +# helper :sort +# include SortHelper +# +# def list +# sort_init 'last_name' +# sort_update +# @contact_pages, @items = paginate :contacts, +# :order_by => sort_clause, +# :per_page => 10 +# end +# +# View (table header in list.rhtml): +# +# <thead> +# <tr> +# <%= sort_header_tag('id', :title => 'Sort by contact ID') %> +# <%= sort_header_tag('last_name', :caption => 'Name') %> +# <%= sort_header_tag('phone') %> +# <%= sort_header_tag('address', :width => 200) %> +# </tr> +# </thead> +# +# - The ascending and descending sort icon images are sort_asc.png and +# sort_desc.png and reside in the application's images directory. +# - Introduces instance variables: @sort_name, @sort_default. +# - Introduces params :sort_key and :sort_order. +# +module SortHelper + + # Initializes the default sort column (default_key) and sort order + # (default_order). + # + # - default_key is a column attribute name. + # - default_order is 'asc' or 'desc'. + # - name is the name of the session hash entry that stores the sort state, + # defaults to '<controller_name>_sort'. + # + def sort_init(default_key, default_order='asc', name=nil) + @sort_name = name || @params[:controller] + @params[:action] + '_sort' + @sort_default = {:key => default_key, :order => default_order} + end + + # Updates the sort state. Call this in the controller prior to calling + # sort_clause. + # + def sort_update() + if @params[:sort_key] + sort = {:key => @params[:sort_key], :order => @params[:sort_order]} + elsif @session[@sort_name] + sort = @session[@sort_name] # Previous sort. + else + sort = @sort_default + end + @session[@sort_name] = sort + end + + # Returns an SQL sort clause corresponding to the current sort state. + # Use this to sort the controller's table items collection. + # + def sort_clause() + @session[@sort_name][:key] + ' ' + @session[@sort_name][:order] + end + + # Returns a link which sorts by the named column. + # + # - column is the name of an attribute in the sorted record collection. + # - The optional caption explicitly specifies the displayed link text. + # - A sort icon image is positioned to the right of the sort link. + # + def sort_link(column, caption=nil) + key, order = @session[@sort_name][:key], @session[@sort_name][:order] + if key == column + if order.downcase == 'asc' + icon = 'sort_asc' + order = 'desc' + else + icon = 'sort_desc' + order = 'asc' + end + else + icon = nil + order = 'desc' # changed for desc order by default + end + caption = titleize(Inflector::humanize(column)) unless caption + params = {:params => {:sort_key => column, :sort_order => order}} + link_to(caption, params) + (icon ? nbsp(2) + image_tag(icon) : '') + end + + # Returns a table header <th> tag with a sort link for the named column + # attribute. + # + # Options: + # :caption The displayed link name (defaults to titleized column name). + # :title The tag's 'title' attribute (defaults to 'Sort by :caption'). + # + # Other options hash entries generate additional table header tag attributes. + # + # Example: + # + # <%= sort_header_tag('id', :title => 'Sort by contact ID', :width => 40) %> + # + # Renders: + # + # <th title="Sort by contact ID" width="40"> + # <a href="/contact/list?sort_order=desc&sort_key=id">Id</a> + # <img alt="Sort_asc" src="/images/sort_asc.png" /> + # </th> + # + def sort_header_tag(column, options = {}) + if options[:caption] + caption = options[:caption] + options.delete(:caption) + else + caption = titleize(Inflector::humanize(column)) + end + options[:title]= "Sort by #{caption}" unless options[:title] + content_tag('th', sort_link(column, caption), options) + end + + private + + # Return n non-breaking spaces. + def nbsp(n) + ' ' * n + end + + # Return capitalized title. + def titleize(title) + title.split.map {|w| w.capitalize }.join(' ') + end + +end diff --git a/redmine/app/helpers/trackers_helper.rb b/redmine/app/helpers/trackers_helper.rb new file mode 100644 index 000000000..839327efe --- /dev/null +++ b/redmine/app/helpers/trackers_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module TrackersHelper +end diff --git a/redmine/app/helpers/users_helper.rb b/redmine/app/helpers/users_helper.rb new file mode 100644 index 000000000..035db3d00 --- /dev/null +++ b/redmine/app/helpers/users_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module UsersHelper +end diff --git a/redmine/app/helpers/versions_helper.rb b/redmine/app/helpers/versions_helper.rb new file mode 100644 index 000000000..e2724fe84 --- /dev/null +++ b/redmine/app/helpers/versions_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module VersionsHelper +end diff --git a/redmine/app/helpers/welcome_helper.rb b/redmine/app/helpers/welcome_helper.rb new file mode 100644 index 000000000..cace5f542 --- /dev/null +++ b/redmine/app/helpers/welcome_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module WelcomeHelper +end diff --git a/redmine/app/models/attachment.rb b/redmine/app/models/attachment.rb new file mode 100644 index 000000000..bc1ff5d87 --- /dev/null +++ b/redmine/app/models/attachment.rb @@ -0,0 +1,81 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require "digest/md5"
+
+class Attachment < ActiveRecord::Base
+ belongs_to :container, :polymorphic => true
+ belongs_to :author, :class_name => "User", :foreign_key => "author_id"
+
+ validates_presence_of :filename
+
+ def file=(incomming_file)
+ unless incomming_file.nil?
+ @temp_file = incomming_file
+ if @temp_file.size > 0
+ self.filename = sanitize_filename(@temp_file.original_filename)
+ self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
+ self.content_type = @temp_file.content_type
+ self.size = @temp_file.size
+ end
+ end
+ end
+
+ # Copy temp file to its final location
+ def before_save
+ if @temp_file && (@temp_file.size > 0)
+ logger.debug("saving '#{self.diskfile}'")
+ File.open(diskfile, "wb") do |f|
+ f.write(@temp_file.read)
+ end
+ self.digest = Digest::MD5.hexdigest(File.read(diskfile))
+ end
+ end
+
+ # Deletes file on the disk
+ def after_destroy
+ if self.filename?
+ File.delete(diskfile) if File.exist?(diskfile)
+ end
+ end
+
+ # Returns file's location on disk
+ def diskfile
+ "#{RDM_STORAGE_PATH}/#{self.disk_filename}"
+ end
+
+ def increment_download
+ increment!(:downloads)
+ end
+
+ # returns last created projects
+ def self.most_downloaded
+ find(:all, :limit => 5, :order => "downloads DESC")
+ end
+
+private
+ def sanitize_filename(value)
+ # get only the filename, not the whole path
+ just_filename = value.gsub(/^.*(\\|\/)/, '')
+ # NOTE: File.basename doesn't work right with Windows paths on Unix
+ # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
+
+ # Finally, replace all non alphanumeric, underscore or periods with underscore
+ @filename = just_filename.gsub(/[^\w\.\-]/,'_')
+ end
+ +end diff --git a/redmine/app/models/custom_field.rb b/redmine/app/models/custom_field.rb new file mode 100644 index 000000000..9e817d1ef --- /dev/null +++ b/redmine/app/models/custom_field.rb @@ -0,0 +1,38 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class CustomField < ActiveRecord::Base
+
+ has_and_belongs_to_many :projects
+ has_many :custom_values, :dependent => true
+ has_many :issues, :through => :issue_custom_values
+
+ validates_presence_of :name, :typ
+ validates_uniqueness_of :name
+
+ TYPES = [
+ [ "Integer", 0 ],
+ [ "String", 1 ],
+ [ "Date", 2 ],
+ [ "Boolean", 3 ],
+ [ "List", 4 ]
+ ].freeze
+
+ def self.for_all
+ find(:all, :conditions => ["is_for_all=?", true])
+ end +end diff --git a/redmine/app/models/custom_value.rb b/redmine/app/models/custom_value.rb new file mode 100644 index 000000000..faaa8ff82 --- /dev/null +++ b/redmine/app/models/custom_value.rb @@ -0,0 +1,41 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class CustomValue < ActiveRecord::Base
+ belongs_to :issue
+ belongs_to :custom_field
+
+protected
+ def validate
+ errors.add(custom_field.name, "can't be blank") if custom_field.is_required? and value.empty?
+ errors.add(custom_field.name, "is not valid") unless custom_field.regexp.empty? or value =~ Regexp.new(custom_field.regexp)
+
+ case custom_field.typ
+ when 0
+ errors.add(custom_field.name, "must be an integer") unless value =~ /^[0-9]*$/
+ when 1
+ 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
+ errors.add(custom_field.name, "is too long") if custom_field.max_length > 0 and value.length > custom_field.max_length
+ when 2
+ errors.add(custom_field.name, "must be a valid date") unless value =~ /^(\d+)\/(\d+)\/(\d+)$/ or value.empty?
+ when 3
+
+ when 4
+ errors.add(custom_field.name, "is not a valid value") unless custom_field.possible_values.split('|').include? value or value.empty?
+ end
+ end +end diff --git a/redmine/app/models/document.rb b/redmine/app/models/document.rb new file mode 100644 index 000000000..40c3a1656 --- /dev/null +++ b/redmine/app/models/document.rb @@ -0,0 +1,24 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Document < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
+ has_many :attachments, :as => :container, :dependent => true
+
+ validates_presence_of :title +end diff --git a/redmine/app/models/enumeration.rb b/redmine/app/models/enumeration.rb new file mode 100644 index 000000000..d93db4445 --- /dev/null +++ b/redmine/app/models/enumeration.rb @@ -0,0 +1,45 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Enumeration < ActiveRecord::Base
+ before_destroy :check_integrity
+
+ validates_presence_of :opt, :name
+
+ OPTIONS = [
+ ["Issue priorities", "IPRI"],
+ ["Document categories", "DCAT"]
+ ].freeze
+
+ def self.get_values(option)
+ find(:all, :conditions => ['opt=?', option])
+ end
+
+ def name
+ _ self.attributes['name']
+ end
+
+private
+ def check_integrity
+ case self.opt
+ when "IPRI"
+ raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id])
+ when "DCAT"
+ raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id])
+ end
+ end +end diff --git a/redmine/app/models/issue.rb b/redmine/app/models/issue.rb new file mode 100644 index 000000000..4a21ac03b --- /dev/null +++ b/redmine/app/models/issue.rb @@ -0,0 +1,55 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Issue < ActiveRecord::Base
+
+ belongs_to :project
+ belongs_to :tracker
+ belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
+ belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
+ belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
+ belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
+ belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
+ belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
+
+ has_many :histories, :class_name => 'IssueHistory', :dependent => true, :order => "issue_histories.created_on DESC", :include => :status
+ has_many :attachments, :as => :container, :dependent => true
+
+ has_many :custom_values, :dependent => true
+ has_many :custom_fields, :through => :custom_values
+
+ validates_presence_of :subject, :descr, :priority, :tracker, :author
+
+ # set default status for new issues
+ def before_create
+ self.status = IssueStatus.default
+ build_history
+ end
+
+ def long_id
+ "%05d" % self.id
+ end
+
+private
+ # Creates an history for the issue
+ def build_history
+ @history = self.histories.build
+ @history.status = self.status
+ @history.author = self.author
+ end
+ +end diff --git a/redmine/app/models/issue_category.rb b/redmine/app/models/issue_category.rb new file mode 100644 index 000000000..b7d4e2623 --- /dev/null +++ b/redmine/app/models/issue_category.rb @@ -0,0 +1,28 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class IssueCategory < ActiveRecord::Base
+ before_destroy :check_integrity
+ belongs_to :project
+
+ validates_presence_of :name
+
+private
+ def check_integrity
+ raise "Can't delete category" if Issue.find(:first, :conditions => ["category_id=?", self.id])
+ end +end diff --git a/redmine/app/models/issue_history.rb b/redmine/app/models/issue_history.rb new file mode 100644 index 000000000..f410a39c1 --- /dev/null +++ b/redmine/app/models/issue_history.rb @@ -0,0 +1,23 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class IssueHistory < ActiveRecord::Base + belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
+ belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
+
+ validates_presence_of :status
+end diff --git a/redmine/app/models/issue_status.rb b/redmine/app/models/issue_status.rb new file mode 100644 index 000000000..ff34cb666 --- /dev/null +++ b/redmine/app/models/issue_status.rb @@ -0,0 +1,47 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class IssueStatus < ActiveRecord::Base
+ before_destroy :check_integrity
+ has_many :workflows, :foreign_key => "old_status_id"
+
+ validates_presence_of :name
+ validates_uniqueness_of :name
+
+ # Returns the default status for new issues
+ def self.default
+ find(:first, :conditions =>["is_default=?", true])
+ end
+
+ # Returns an array of all statuses the given role can switch to
+ def new_statuses_allowed_to(role, tracker)
+ statuses = []
+ for workflow in self.workflows.find(:all, :include => :new_status)
+ statuses << workflow.new_status if workflow.role_id == role.id and workflow.tracker_id == tracker.id
+ end unless role.nil?
+ statuses
+ end
+
+ def name
+ _ self.attributes['name']
+ end
+
+private
+ def check_integrity
+ raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id]) or IssueHistory.find(:first, :conditions => ["status_id=?", self.id])
+ end +end diff --git a/redmine/app/models/mailer.rb b/redmine/app/models/mailer.rb new file mode 100644 index 000000000..b04ec7ebc --- /dev/null +++ b/redmine/app/models/mailer.rb @@ -0,0 +1,36 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Mailer < ActionMailer::Base + + def issue_change_status(issue) + # Sends to all project members + @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification } + @from = 'redmine@somenet.foo' + @subject = "Issue ##{issue.id} has been updated" + @body['issue'] = issue
+ end
+
+ def issue_add(issue) + # Sends to all project members + @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification } + @from = 'redmine@somenet.foo' + @subject = "Issue ##{issue.id} has been reported" + @body['issue'] = issue
+ end
+ +end diff --git a/redmine/app/models/member.rb b/redmine/app/models/member.rb new file mode 100644 index 000000000..d37936561 --- /dev/null +++ b/redmine/app/models/member.rb @@ -0,0 +1,29 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Member < ActiveRecord::Base
+ belongs_to :user
+ belongs_to :role
+ belongs_to :project
+
+ validates_presence_of :role, :user, :project
+ validates_uniqueness_of :user_id, :scope => :project_id
+
+ def name
+ self.user.display_name
+ end +end diff --git a/redmine/app/models/news.rb b/redmine/app/models/news.rb new file mode 100644 index 000000000..0642a4bf5 --- /dev/null +++ b/redmine/app/models/news.rb @@ -0,0 +1,28 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class News < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
+
+ validates_presence_of :title, :shortdescr, :descr +
+ # returns last created news
+ def self.latest
+ find(:all, :limit => 5, :include => [ :author, :project ], :order => "news.created_on DESC")
+ end +end diff --git a/redmine/app/models/permission.rb b/redmine/app/models/permission.rb new file mode 100644 index 000000000..f66214119 --- /dev/null +++ b/redmine/app/models/permission.rb @@ -0,0 +1,63 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Permission < ActiveRecord::Base
+ has_and_belongs_to_many :roles
+
+ validates_presence_of :controller, :action, :descr
+
+ GROUPS = {
+ 100 => "Project",
+ 200 => "Membres",
+ 300 => "Versions",
+ 400 => "Issue categories",
+ 1000 => "Issues",
+ 1100 => "News",
+ 1200 => "Documents",
+ 1300 => "Files",
+ }.freeze
+
+ @@cached_perms_for_public = nil
+ @@cached_perms_for_roles = nil
+
+ def name
+ self.controller + "/" + self.action
+ end
+
+ def group_id
+ (self.sort / 100)*100
+ end
+
+ def self.allowed_to_public(action)
+ @@cached_perms_for_public ||= find(:all, :conditions => ["public=?", true]).collect {|p| "#{p.controller}/#{p.action}"}
+ @@cached_perms_for_public.include? action
+ end
+
+ def self.allowed_to_role(action, role)
+ @@cached_perms_for_roles ||=
+ begin
+ perms = {}
+ find(:all, :include => :roles).each {|p| perms.store "#{p.controller}/#{p.action}", p.roles.collect {|r| r.id } }
+ perms
+ end
+ @@cached_perms_for_roles[action] and @@cached_perms_for_roles[action].include? role
+ end
+
+ def self.allowed_to_role_expired
+ @@cached_perms_for_roles = nil
+ end +end diff --git a/redmine/app/models/project.rb b/redmine/app/models/project.rb new file mode 100644 index 000000000..7c50ee8cb --- /dev/null +++ b/redmine/app/models/project.rb @@ -0,0 +1,44 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Project < ActiveRecord::Base
+ has_many :versions, :dependent => true, :order => "versions.date DESC"
+ has_many :members, :dependent => true
+ has_many :issues, :dependent => true, :order => "issues.created_on DESC", :include => :status
+ has_many :documents, :dependent => true
+ has_many :news, :dependent => true, :order => "news.created_on DESC", :include => :author
+ has_many :issue_categories, :dependent => true
+ has_and_belongs_to_many :custom_fields
+
+ validates_presence_of :name, :descr
+
+ # returns 5 last created projects
+ def self.latest
+ find(:all, :limit => 5, :order => "created_on DESC")
+ end
+
+ # Returns current version of the project
+ def current_version
+ versions.find(:first, :conditions => [ "date <= ?", Date.today ], :order => "date DESC, id DESC")
+ end
+
+ # Returns an array of all custom fields enabled for project issues
+ # (explictly associated custom fields and custom fields enabled for all projects)
+ def custom_fields_for_issues
+ (CustomField.for_all + custom_fields).uniq
+ end +end diff --git a/redmine/app/models/role.rb b/redmine/app/models/role.rb new file mode 100644 index 000000000..ce880b5cc --- /dev/null +++ b/redmine/app/models/role.rb @@ -0,0 +1,31 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Role < ActiveRecord::Base
+ before_destroy :check_integrity
+ has_and_belongs_to_many :permissions
+ has_many :workflows, :dependent => true
+ has_many :members
+
+ validates_presence_of :name
+ validates_uniqueness_of :name
+
+private
+ def check_integrity
+ raise "Can't delete role" if Member.find(:first, :conditions =>["role_id=?", self.id])
+ end +end diff --git a/redmine/app/models/tracker.rb b/redmine/app/models/tracker.rb new file mode 100644 index 000000000..6b123d79c --- /dev/null +++ b/redmine/app/models/tracker.rb @@ -0,0 +1,31 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Tracker < ActiveRecord::Base
+ before_destroy :check_integrity
+ has_many :issues
+ has_many :workflows, :dependent => true
+
+ def name
+ _ self.attributes['name']
+ end
+
+private
+ def check_integrity
+ raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id])
+ end +end diff --git a/redmine/app/models/user.rb b/redmine/app/models/user.rb new file mode 100644 index 000000000..1bc1b5802 --- /dev/null +++ b/redmine/app/models/user.rb @@ -0,0 +1,89 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require "digest/sha1"
+
+class User < ActiveRecord::Base
+ has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :dependent => true
+
+ attr_accessor :password
+ attr_accessor :last_before_login_on
+ # Prevents unauthorized assignments
+ attr_protected :admin
+
+ validates_presence_of :login, :firstname, :lastname, :mail
+ validates_uniqueness_of :login, :mail
+
+ # Login must contain lettres, numbers, underscores only
+ validates_format_of :login, :with => /^[a-z0-9_]+$/i
+ validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
+
+ def before_create
+ self.hashed_password = User.hash_password(self.password)
+ end
+
+ def after_create
+ @password = nil
+ end
+
+ # Returns the user that matches user's login and password
+ def try_to_login
+ @user = User.login(self.login, self.password)
+ unless @user.nil?
+ @user.last_before_login_on = @user.last_login_on
+ @user.update_attribute(:last_login_on, DateTime.now)
+ end
+ @user
+ end
+
+ # Return user's full name for display
+ def display_name
+ firstname + " " + lastname #+ (self.admin ? " (Admin)" : "" )
+ end
+
+ # Returns the user that matches the given login and password
+ def self.login(login, password)
+ hashed_password = hash_password(password || "")
+ find(:first,
+ :conditions => ["login = ? and hashed_password = ? and locked = ?", login, hashed_password, false])
+ end
+
+ def check_password?(clear_password)
+ User.hash_password(clear_password) == self.hashed_password
+ end
+
+ def change_password(current_password, new_password)
+ self.hashed_password = User.hash_password(new_password)
+ save
+ end
+
+ def role_for_project(project_id)
+ @role_for_projects ||=
+ begin
+ roles = {}
+ self.memberships.each { |m| roles.store m.project_id, m.role_id }
+ roles
+ end
+ @role_for_projects[project_id]
+ end
+
+private
+ # Return password digest
+ def self.hash_password(clear_password)
+ Digest::SHA1.hexdigest(clear_password)
+ end +end diff --git a/redmine/app/models/version.rb b/redmine/app/models/version.rb new file mode 100644 index 000000000..02dee15c8 --- /dev/null +++ b/redmine/app/models/version.rb @@ -0,0 +1,30 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Version < ActiveRecord::Base
+ before_destroy :check_integrity
+ belongs_to :project
+ has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
+ has_many :attachments, :as => :container, :dependent => true
+
+ validates_presence_of :name, :descr
+
+private
+ def check_integrity
+ raise "Can't delete version" if self.fixed_issues.find(:first)
+ end
+end diff --git a/redmine/app/models/workflow.rb b/redmine/app/models/workflow.rb new file mode 100644 index 000000000..212e33350 --- /dev/null +++ b/redmine/app/models/workflow.rb @@ -0,0 +1,25 @@ +# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Workflow < ActiveRecord::Base
+
+ belongs_to :role
+ belongs_to :old_status, :class_name => 'IssueStatus', :foreign_key => 'old_status_id'
+ belongs_to :new_status, :class_name => 'IssueStatus', :foreign_key => 'new_status_id'
+
+ validates_presence_of :role, :old_status, :new_status
+end diff --git a/redmine/app/views/account/login.rhtml b/redmine/app/views/account/login.rhtml new file mode 100644 index 000000000..cc360ebac --- /dev/null +++ b/redmine/app/views/account/login.rhtml @@ -0,0 +1,13 @@ +<div class="box">
+<h2><%=_ 'Please login' %></h2> +
+<%= start_form_tag :action=> "login" %> +<p><label for="user_login"><%=_ 'Login' %>:</label><br/> +<input type="text" name="user[login]" id="user_login" size="30" /></p> + +<p><label for="user_password"><%=_ 'Password' %>:</label><br/> +<input type="password" name="user[password]" id="user_password" size="30"/></p> + +<p><input type="submit" name="login" value="<%=_ 'Log in' %> »" class="primary" /></p> +<%= end_form_tag %> +</div>
\ No newline at end of file diff --git a/redmine/app/views/account/my_account.rhtml b/redmine/app/views/account/my_account.rhtml new file mode 100644 index 000000000..34ae4885f --- /dev/null +++ b/redmine/app/views/account/my_account.rhtml @@ -0,0 +1,54 @@ +<h2><%=_('My account')%></h2> +
+<p><%=_('Login')%>: <strong><%= @user.login %></strong><br />
+<%=_('Created on')%>: <%= format_time(@user.created_on) %>,
+<%=_('Last update')%>: <%= format_time(@user.updated_on) %></p>
+
+<div class="splitcontentleft">
+ <div class="box">
+ <h3><%=_('Information')%></h3>
+ + <%= start_form_tag :action => 'my_account' %> + <%= error_messages_for 'user' %> +
+ <!--[form:user]--> + <p><label for="user_firstname"><%=_('Firstname')%> <span class="required">*</span></label><br/> + <%= text_field 'user', 'firstname' %></p> +
+ <p><label for="user_lastname"><%=_('Lastname')%> <span class="required">*</span></label><br/> + <%= text_field 'user', 'lastname' %></p> + + <p><label for="user_mail"><%=_('Mail')%> <span class="required">*</span></label><br/> + <%= text_field 'user', 'mail' %></p>
+
+ <p><label for="user_language"><%=_('Language')%></label><br/> + <%= select("user", "language", Localization.langs) %></p> + <!--[eoform:user]-->
+
+ <p><%= check_box 'user', 'mail_notification' %> <label for="user_mail_notification"><%=_('Mail notifications')%></label></p>
+ + <center><%= submit_tag _('Save') %></center> + <%= end_form_tag %>
+ </div>
+</div>
+
+
+<div class="splitcontentright">
+ <div class="box">
+ <h3><%=_('Password')%></h3>
+ + <%= start_form_tag :action => 'change_password' %> +
+ <p><label for="old_password"><%=_('Password')%> <span class="required">*</span></label><br/> + <%= password_field_tag 'old_password' %></p> +
+ <p><label for="new_password"><%=_('New password')%> <span class="required">*</span></label><br/> + <%= password_field_tag 'new_password' %></p>
+
+ <p><label for="new_password_confirmation"><%=_('Confirmation')%> <span class="required">*</span></label><br/> + <%= password_field_tag 'new_password_confirmation' %></p>
+ + <center><%= submit_tag _('Save') %></center> + <%= end_form_tag %>
+ </div>
+</div>
\ No newline at end of file diff --git a/redmine/app/views/account/my_page.rhtml b/redmine/app/views/account/my_page.rhtml new file mode 100644 index 000000000..7f6458262 --- /dev/null +++ b/redmine/app/views/account/my_page.rhtml @@ -0,0 +1,19 @@ +<h2><%=_('My page') %></h2>
+
+<p>
+<%=_('Welcome')%> <b><%= @user.firstname %></b><br />
+<% unless @user.last_before_login_on.nil? %>
+ <%=_('Last login')%>: <%= format_time(@user.last_before_login_on) %>
+<% end %>
+</p>
+
+<div class="splitcontentleft">
+ <h3><%=_('Reported issues')%></h3>
+ <%= render :partial => 'issues/list_simple', :locals => { :issues => @reported_issues } %>
+ <%= "<p>(Last #{@reported_issues.length} updated)</p>" if @reported_issues.length > 0 %>
+</div>
+<div class="splitcontentright">
+<h3><%=_('Assigned to me')%></h3>
+ <%= render :partial => 'issues/list_simple', :locals => { :issues => @assigned_issues } %>
+ <%= "<p>(Last #{@assigned_issues.length} updated)</p>" if @assigned_issues.length > 0 %>
+</div>
\ No newline at end of file diff --git a/redmine/app/views/account/show.rhtml b/redmine/app/views/account/show.rhtml new file mode 100644 index 000000000..df918e5bf --- /dev/null +++ b/redmine/app/views/account/show.rhtml @@ -0,0 +1,19 @@ +<h2><%= @user.display_name %></h2>
+
+<p>
+<%= mail_to @user.mail %><br />
+<%=_('Registered on')%>: <%= format_date(@user.created_on) %>
+</p>
+
+<h3><%=_('Projects')%></h3>
+<p>
+<% for membership in @user.memberships %>
+ <%= membership.project.name %> (<%= membership.role.name %>, <%= format_date(membership.created_on) %>)
+ <br />
+<% end %>
+</p>
+
+<h3><%=_('Activity')%></h3>
+<p>
+<%=_('Reported issues')%>: <%= Issue.count( [ "author_id=?", @user.id]) %>
+</p>
\ No newline at end of file diff --git a/redmine/app/views/admin/index.rhtml b/redmine/app/views/admin/index.rhtml new file mode 100644 index 000000000..b3607d813 --- /dev/null +++ b/redmine/app/views/admin/index.rhtml @@ -0,0 +1,45 @@ +<h2><%=_('Administration')%></h2>
+
+<p>
+<%= image_tag "projects" %>
+<%= link_to _('Projects'), :controller => 'admin', :action => 'projects' %> |
+<%= link_to _('New'), :controller => 'projects', :action => 'add' %>
+</p>
+
+<p>
+<%= image_tag "users" %>
+<%= link_to _('Users'), :controller => 'users' %> |
+<%= link_to _('New'), :controller => 'users', :action => 'add' %>
+</p>
+
+<p>
+<%= image_tag "role" %>
+<%= link_to _('Roles and permissions'), :controller => 'roles' %>
+</p>
+
+<p>
+<%= image_tag "tracker" %>
+<%= link_to _('Trackers'), :controller => 'trackers' %> |
+<%= link_to _('Custom fields'), :controller => 'custom_fields' %>
+</p>
+
+<p>
+<%= image_tag "workflow" %>
+<%= link_to _('Issue Statuses'), :controller => 'issue_statuses' %> |
+<%= link_to _('Workflow'), :controller => 'roles', :action => 'workflow' %>
+</p>
+
+<p>
+<%= image_tag "options" %>
+<%= link_to _('Enumerations'), :controller => 'enumerations' %>
+</p>
+
+<p>
+<%= image_tag "mailer" %>
+<%= link_to _('Mail notifications'), :controller => 'admin', :action => 'mail_options' %>
+</p>
+
+<p>
+<%= image_tag "help" %>
+<%= link_to _('Information'), :controller => 'admin', :action => 'info' %>
+</p>
\ No newline at end of file diff --git a/redmine/app/views/admin/info.rhtml b/redmine/app/views/admin/info.rhtml new file mode 100644 index 000000000..c73f59c25 --- /dev/null +++ b/redmine/app/views/admin/info.rhtml @@ -0,0 +1,4 @@ +<h2><%=_('Information')%></h2>
+
+<%=_('Version')%>: <%= RDM_APP_NAME %> <%= RDM_APP_VERSION %><br />
+<%=_('Database')%>: <%= @adapter_name %>
\ No newline at end of file diff --git a/redmine/app/views/admin/mail_options.rhtml b/redmine/app/views/admin/mail_options.rhtml new file mode 100644 index 000000000..2d1d80ebb --- /dev/null +++ b/redmine/app/views/admin/mail_options.rhtml @@ -0,0 +1,16 @@ +<h2><%=_('Mail notifications')%></h2>
+
+<p><%=_('Select actions for which mail notification should be enabled.')%></p>
+
+<%= start_form_tag ({}, :id => 'mail_options_form')%>
+<% for action in @actions %>
+ <%= check_box_tag "action_ids[]", action.id, action.mail_enabled? %>
+ <%= action.descr %><br />
+<% end %>
+<br />
+<p>
+<a href="javascript:checkAll('mail_options_form', true)"><%=_('Check all')%></a> |
+<a href="javascript:checkAll('mail_options_form', false)"><%=_('Uncheck all')%></a>
+</p>
+<%= submit_tag _('Save') %>
+<%= end_form_tag %>
\ No newline at end of file diff --git a/redmine/app/views/admin/projects.rhtml b/redmine/app/views/admin/projects.rhtml new file mode 100644 index 000000000..dd3953518 --- /dev/null +++ b/redmine/app/views/admin/projects.rhtml @@ -0,0 +1,35 @@ +<h2><%=_('Projects')%></h2> + +<table width="100%" cellspacing="1" cellpadding="2" class="listTableContent"> + <tr class="ListHead"> + <%= sort_header_tag('projects.name', :caption => _('Project')) %>
+ <th><%=_('Description')%></th>
+ <th><%=_('Public')%></th>
+ <%= sort_header_tag('projects.created_on', :caption => _('Created on')) %>
+ <th></th> + </tr> + +<% odd_or_even = 1
+ for project in @projects
+ odd_or_even = 1 - odd_or_even %> + <tr class="ListLine<%= odd_or_even %>">
+ <td><%= link_to project.name, :controller => 'projects', :action => 'settings', :id => project %>
+ <td><%= project.descr %>
+ <td align="center"><%= image_tag 'true' if project.public? %>
+ <td align="center"><%= format_date(project.created_on) %>
+ <td align="center">
+ <%= start_form_tag({:controller => 'projects', :action => 'destroy', :id => project}) %>
+ <%= submit_tag _('Delete'), :class => "button-small" %>
+ <%= end_form_tag %>
+ </td>
+ </tr> +<% end %> +</table> + +<%= link_to ('« ' + _('Previous')), { :page => @project_pages.current.previous } if @project_pages.current.previous %>
+<%= pagination_links(@project_pages) %> +<%= link_to (_('Next') + ' »'), { :page => @project_pages.current.next } if @project_pages.current.next %>
+
+<br />
+
+<%= link_to ('» ' + _('New project')), :controller => 'projects', :action => 'add' %>
\ No newline at end of file diff --git a/redmine/app/views/custom_fields/_form.rhtml b/redmine/app/views/custom_fields/_form.rhtml new file mode 100644 index 000000000..d268461fc --- /dev/null +++ b/redmine/app/views/custom_fields/_form.rhtml @@ -0,0 +1,26 @@ +<%= error_messages_for 'custom_field' %> + +<!--[form:custom_field]--> +<p><label for="custom_field_name"><%=_('Name')%></label><br/> +<%= text_field 'custom_field', 'name' %></p> +
+<p><label for="custom_field_typ"><%=_('Type')%></label><br/> +<%= select("custom_field", "typ", CustomField::TYPES) %></p>
+
+<p><%= check_box 'custom_field', 'is_required' %>
+<label for="custom_field_is_required"><%=_('Required')%></label></p> +
+<p><%= check_box 'custom_field', 'is_for_all' %>
+<label for="custom_field_is_for_all"><%=_('For all projects')%></label></p> +
+<p><label for="custom_field_min_length"><%=_('Min - max length')%></label> (<%=_('0 means no restriction')%>)<br/> +<%= text_field 'custom_field', 'min_length', :size => 5 %> - +<%= text_field 'custom_field', 'max_length', :size => 5 %></p>
+
+<p><label for="custom_field_regexp"><%=_('Regular expression pattern')%></label> (eg. ^[A-Z0-9]+$)<br/> +<%= text_field 'custom_field', 'regexp', :size => 50 %></p>
+
+<p><label for="custom_field_possible_values"><%=_('Possible values')%></label> (separator: |)<br/> +<%= text_area 'custom_field', 'possible_values', :rows => 5, :cols => 60 %></p>
+<!--[eoform:custom_field]--> + diff --git a/redmine/app/views/custom_fields/edit.rhtml b/redmine/app/views/custom_fields/edit.rhtml new file mode 100644 index 000000000..ab4ea8b24 --- /dev/null +++ b/redmine/app/views/custom_fields/edit.rhtml @@ -0,0 +1,6 @@ +<h2><%=_('Custom field')%></h2> + +<%= start_form_tag :action => 'edit', :id => @custom_field %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> diff --git a/redmine/app/views/custom_fields/list.rhtml b/redmine/app/views/custom_fields/list.rhtml new file mode 100644 index 000000000..0e29a5b37 --- /dev/null +++ b/redmine/app/views/custom_fields/list.rhtml @@ -0,0 +1,32 @@ +<h2><%=_('Custom fields')%></h2> +
+<table border="0" cellspacing="1" cellpadding="2" class="listTableContent">
+<tr class="ListHead"> + <th><%=_('Name')%></th>
+ <th><%=_('Type')%></th>
+ <th><%=_('Required')%></th>
+ <th><%=_('For all projects')%></th>
+ <th><%=_('Used by')%></th>
+ <th></th>
+</tr> +<% for custom_field in @custom_fields %> + <tr style="background-color:#CEE1ED">
+ <td><%= link_to custom_field.name, :action => 'edit', :id => custom_field %></td>
+ <td align="center"><%= CustomField::TYPES[custom_field.typ][0] %></td>
+ <td align="center"><%= image_tag 'true' if custom_field.is_required? %></td>
+ <td align="center"><%= image_tag 'true' if custom_field.is_for_all? %></td>
+ <td align="center"><%= custom_field.projects.count.to_s + ' ' + _('Project') + '(s)' unless custom_field.is_for_all? %></td> + <td align="center"> + <%= start_form_tag :action => 'destroy', :id => custom_field %>
+ <%= submit_tag _('Delete'), :class => "button-small" %>
+ <%= end_form_tag %> </td> + </tr> +<% end %> +</table>
+ +<%= link_to ('« ' + _('Previous')), { :page => @custom_field_pages.current.previous } if @custom_field_pages.current.previous %> +<%= link_to (_('Next') + ' »'), { :page => @custom_field_pages.current.next } if @custom_field_pages.current.next %> + +<br /> + +<%= link_to ('» ' + _('New custom field')), :action => 'new' %> diff --git a/redmine/app/views/custom_fields/new.rhtml b/redmine/app/views/custom_fields/new.rhtml new file mode 100644 index 000000000..0e6492a28 --- /dev/null +++ b/redmine/app/views/custom_fields/new.rhtml @@ -0,0 +1,7 @@ +<h2><%=_('New custom field')%></h2> + +<%= start_form_tag :action => 'new' %> + <%= render :partial => 'form' %> + <%= submit_tag _('Create') %> +<%= end_form_tag %> + diff --git a/redmine/app/views/documents/_form.rhtml b/redmine/app/views/documents/_form.rhtml new file mode 100644 index 000000000..4440a156e --- /dev/null +++ b/redmine/app/views/documents/_form.rhtml @@ -0,0 +1,15 @@ +<%= error_messages_for 'document' %> + +<!--[form:document]--> +<p><label for="document_category_id"><%=_('Category')%></label><br />
+<select name="document[category_id]">
+<%= options_from_collection_for_select @categories, "id", "name",@document.category_id %> +</select></p>
+
+<p><label for="document_title"><%=_('Title')%> <span class="required">*</span></label><br /> +<%= text_field 'document', 'title', :size => 60 %></p> + +<p><label for="document_descr"><%=_('Description')%> <span class="required">*</span></label><br /> +<%= text_area 'document', 'descr', :cols => 60, :rows => 5 %></p> +<!--[eoform:document]--> + diff --git a/redmine/app/views/documents/edit.rhtml b/redmine/app/views/documents/edit.rhtml new file mode 100644 index 000000000..2f1e9a94c --- /dev/null +++ b/redmine/app/views/documents/edit.rhtml @@ -0,0 +1,8 @@ +<h2><%=_('Document')%></h2> + +<%= start_form_tag :action => 'edit', :id => @document %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> + + diff --git a/redmine/app/views/documents/show.rhtml b/redmine/app/views/documents/show.rhtml new file mode 100644 index 000000000..bc9557e55 --- /dev/null +++ b/redmine/app/views/documents/show.rhtml @@ -0,0 +1,45 @@ +<h2><%= @document.title %></h2>
+
+<strong><%=_('Description')%>:</strong> <%= @document.descr %><br />
+<strong><%=_('Category')%>:</strong> <%= @document.category.name %><br />
+<br />
+
+<% if authorize_for('documents', 'edit') %>
+ <%= start_form_tag ({ :controller => 'documents', :action => 'edit', :id => @document }, :method => 'get' ) %>
+ <%= submit_tag _('Edit') %>
+ <%= end_form_tag %>
+<% end %>
+
+<% if authorize_for('documents', 'destroy') %>
+ <%= start_form_tag ({ :controller => 'documents', :action => 'destroy', :id => @document } ) %>
+ <%= submit_tag _('Delete') %>
+ <%= end_form_tag %>
+<% end %>
+
+<br /><br />
+
+<table border="0" cellspacing="1" cellpadding="2" width="100%"> +<% for attachment in @document.attachments %> + <tr style="background-color:#CEE1ED"> + <td><%= link_to attachment.filename, :action => 'download', :id => @document, :attachment_id => attachment %></td>
+ <td align="center"><%= format_date(attachment.created_on) %></td> + <td align="center"><%= attachment.author.display_name %></td> + <td><%= human_size(attachment.size) %><br /><%= attachment.downloads %> <%=_('download')%>(s)</td>
+ <% if authorize_for('documents', 'destroy_attachment') %>
+ <td align="center">
+ <%= start_form_tag :action => 'destroy_attachment', :id => @document, :attachment_id => attachment %>
+ <%= submit_tag _('Delete'), :class => "button-small" %>
+ <%= end_form_tag %> + </tr>
+ <% end %> +<% end %> +</table>
+<br />
+
+<% if authorize_for('documents', 'add_attachment') %>
+ <%= start_form_tag ({ :controller => 'documents', :action => 'add_attachment', :id => @document }, :multipart => true) %>
+ <%=_('Add file')%><br /><%= file_field 'attachment', 'file' %>
+ <%= submit_tag _('Add') %>
+ <%= end_form_tag %>
+<% end %>
+
diff --git a/redmine/app/views/enumerations/_form.rhtml b/redmine/app/views/enumerations/_form.rhtml new file mode 100644 index 000000000..d78dc358c --- /dev/null +++ b/redmine/app/views/enumerations/_form.rhtml @@ -0,0 +1,9 @@ +<%= error_messages_for 'enumeration' %> + +<!--[form:optvalue]--> +<%= hidden_field 'enumeration', 'opt' %> + +<p><label for="enumeration_name"><%=_('Name')%></label><br/> +<%= text_field 'enumeration', 'name' %></p> +<!--[eoform:optvalue]--> + diff --git a/redmine/app/views/enumerations/edit.rhtml b/redmine/app/views/enumerations/edit.rhtml new file mode 100644 index 000000000..16bd377f3 --- /dev/null +++ b/redmine/app/views/enumerations/edit.rhtml @@ -0,0 +1,10 @@ +<h2><%=_('Enumerations')%></h2> + +<%= start_form_tag :action => 'update', :id => @enumeration %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> + +<%= start_form_tag :action => 'destroy', :id => @enumeration %> + <%= submit_tag _('Delete') %> +<%= end_form_tag %>
\ No newline at end of file diff --git a/redmine/app/views/enumerations/list.rhtml b/redmine/app/views/enumerations/list.rhtml new file mode 100644 index 000000000..b5ce65c23 --- /dev/null +++ b/redmine/app/views/enumerations/list.rhtml @@ -0,0 +1,22 @@ +<h2><%=_('Enumerations')%></h2> + +<% for option in Enumeration::OPTIONS %>
+
+ <% if @params[:opt]==option[1] %>
+
+ <p><%= image_tag 'dir_open' %> <b><%=_ option[0] %></b></p>
+ <ul>
+ <% for value in Enumeration::find(:all, :conditions => [ "opt = ?", option[1]]) %>
+ <li><%= link_to value.name, :action => 'edit', :id => value %></li>
+ <% end %>
+ </ul>
+ <ul>
+ <li><%= link_to ('» ' + _('New')), :action => 'new', :opt => option[1] %></li>
+ </ul>
+
+ <% else %>
+ <p><%= image_tag 'dir' %> <%= link_to _(option[0]), :opt => option[1] %></p>
+ <% end %>
+
+<% end %> + diff --git a/redmine/app/views/enumerations/new.rhtml b/redmine/app/views/enumerations/new.rhtml new file mode 100644 index 000000000..30048f2fd --- /dev/null +++ b/redmine/app/views/enumerations/new.rhtml @@ -0,0 +1,6 @@ +<h2><%=_('New enumeration')%></h2> + +<%= start_form_tag :action => 'create' %> + <%= render :partial => 'form' %> + <%= submit_tag _('Create') %> +<%= end_form_tag %> diff --git a/redmine/app/views/issue_categories/_form.rhtml b/redmine/app/views/issue_categories/_form.rhtml new file mode 100644 index 000000000..eba2f1672 --- /dev/null +++ b/redmine/app/views/issue_categories/_form.rhtml @@ -0,0 +1,7 @@ +<%= error_messages_for 'issue_category' %> + +<!--[form:issue_category]--> +<p><label for="issue_category_name"><%=_('Name')%></label><br/> +<%= text_field 'issue_category', 'name' %></p> +<!--[eoform:issue_category]--> + diff --git a/redmine/app/views/issue_categories/edit.rhtml b/redmine/app/views/issue_categories/edit.rhtml new file mode 100644 index 000000000..3afdd1c63 --- /dev/null +++ b/redmine/app/views/issue_categories/edit.rhtml @@ -0,0 +1,6 @@ +<h2>Editing issue category</h2> + +<%= start_form_tag :action => 'edit', :id => @category %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> diff --git a/redmine/app/views/issue_statuses/_form.rhtml b/redmine/app/views/issue_statuses/_form.rhtml new file mode 100644 index 000000000..5f4b9ce3d --- /dev/null +++ b/redmine/app/views/issue_statuses/_form.rhtml @@ -0,0 +1,17 @@ +<%= error_messages_for 'issue_status' %> + +<!--[form:issue_status]--> +<p><label for="issue_status_name"><%=_('Name')%></label><br/> +<%= text_field 'issue_status', 'name' %></p> + +<p><%= check_box 'issue_status', 'is_closed' %>
+<label for="issue_status_is_closed"><%=_('Issue closed')%></label></p>
+
+<p><%= check_box 'issue_status', 'is_default' %>
+<label for="issue_status_is_default"><%=_('Default status')%></label></p>
+
+<p><label for="issue_status_html_color"><%=_('Color')%></label> +#<%= text_field 'issue_status', 'html_color', :size => 6 %></p>
+ +<!--[eoform:issue_status]--> + diff --git a/redmine/app/views/issue_statuses/edit.rhtml b/redmine/app/views/issue_statuses/edit.rhtml new file mode 100644 index 000000000..d3ed92201 --- /dev/null +++ b/redmine/app/views/issue_statuses/edit.rhtml @@ -0,0 +1,6 @@ +<h2><%=_('Issue status')%></h2> + +<%= start_form_tag :action => 'update', :id => @issue_status %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> diff --git a/redmine/app/views/issue_statuses/list.rhtml b/redmine/app/views/issue_statuses/list.rhtml new file mode 100644 index 000000000..050e08f0f --- /dev/null +++ b/redmine/app/views/issue_statuses/list.rhtml @@ -0,0 +1,30 @@ +<h2><%=_('Issue statuses')%></h2> + +<table border="0" cellspacing="1" cellpadding="2" class="listTableContent"> + <tr class="ListHead"> + <th><%=_('Status')%></th>
+ <th><%=_('Default status')%></th>
+ <th><%=_('Issue closed')%></th>
+ <th><%=_('Color')%></th>
+ <th></th> + </tr> + +<% for status in @issue_statuses %> + <tr style="background-color:#CEE1ED"> + <td><%= link_to status.name, :action => 'edit', :id => status %></td>
+ <td align="center"><%= image_tag 'true' if status.is_default %></td>
+ <td align="center"><%= image_tag 'true' if status.is_closed %></td> + <td bgcolor="#<%= status.html_color %>"> </td>
+ <td align="center">
+ <%= start_form_tag :action => 'destroy', :id => status %>
+ <%= submit_tag _('Delete'), :class => "button-small" %>
+ <%= end_form_tag %> + </td> + </tr> +<% end %> +</table> +
+<%= pagination_links_full @issue_status_pages %>
+<br /> + +<%= link_to '» ' + _('New issue status'), :action => 'new' %> diff --git a/redmine/app/views/issue_statuses/new.rhtml b/redmine/app/views/issue_statuses/new.rhtml new file mode 100644 index 000000000..f7ac082e9 --- /dev/null +++ b/redmine/app/views/issue_statuses/new.rhtml @@ -0,0 +1,6 @@ +<h2><%=_('New issue status')%></h2> + +<%= start_form_tag :action => 'create' %> + <%= render :partial => 'form' %> + <%= submit_tag _('Create') %> +<%= end_form_tag %> diff --git a/redmine/app/views/issues/_list_simple.rhtml b/redmine/app/views/issues/_list_simple.rhtml new file mode 100644 index 000000000..66b70a1da --- /dev/null +++ b/redmine/app/views/issues/_list_simple.rhtml @@ -0,0 +1,28 @@ +<% if issues.length > 0 %>
+ <table cellspacing="0" cellpadding="1" width="100%" border="0" class="listTable">
+ <tr><td>
+ <table width="100%" border="0" cellspacing="1" cellpadding="2" class="listTableContent">
+ <tr class="ListHead">
+ <th>#</th>
+ <th><%=_('Tracker')%></th>
+ <th><%=_('Subject')%></th>
+ </tr>
+ <% for issue in issues %> + <tr bgcolor="#<%= issue.status.html_color %>">
+ <td align="center">
+ <%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %><br />
+ </td>
+ <td><p class="small"><%= issue.project.name %> - <%= issue.tracker.name %><br /> + <%= issue.status.name %> - <%= format_time(issue.updated_on) %></p></td>
+ <td>
+ <p class="small"><%= link_to issue.subject, :controller => 'issues', :action => 'show', :id => issue %></p>
+ </td> + </tr> + <% end %> + </table> + </td>
+ </tr>
+ </table>
+<% else %>
+ <%=_('No issue')%>
+<% end %>
\ No newline at end of file diff --git a/redmine/app/views/issues/change_status.rhtml b/redmine/app/views/issues/change_status.rhtml new file mode 100644 index 000000000..58032cefc --- /dev/null +++ b/redmine/app/views/issues/change_status.rhtml @@ -0,0 +1,29 @@ +<h2><%=_('Issue')%> #<%= @issue.id %>: <%= @issue.subject %></h2> +
+<%= error_messages_for 'history' %>
+<%= start_form_tag :action => 'change_status', :id => @issue %>
+
+<%= hidden_field_tag 'confirm', 1 %>
+<%= hidden_field 'history', 'status_id' %> +
+<p><%=_('New status')%>: <b><%= @history.status.name %></b></p>
+
+<div>
+<p><label for="issue_assigned_to_id"><%=_('Assigned to')%></label><br/> +<select name="issue[assigned_to_id]">
+<option value=""></option>
+<%= options_from_collection_for_select @assignable_to, "id", "display_name", @issue.assigned_to_id %></p> +</select></p>
+</div>
+
+<p><label for="issue_fixed_version"><%=_('Fixed in version')%></label><br/> +<select name="issue[fixed_version_id]">
+<option value="">--none--</option>
+<%= options_from_collection_for_select @issue.project.versions, "id", "name", @issue.fixed_version_id %>
+</select></p>
+
+<p><label for="history_notes"><%=_('Notes')%></label><br />
+<%= text_area 'history', 'notes', :cols => 60, :rows => 10 %></p>
+
+<%= submit_tag _('Save') %>
+<%= end_form_tag %>
diff --git a/redmine/app/views/issues/edit.rhtml b/redmine/app/views/issues/edit.rhtml new file mode 100644 index 000000000..1249cfc8d --- /dev/null +++ b/redmine/app/views/issues/edit.rhtml @@ -0,0 +1,62 @@ +<h2><%=_('Issue')%> #<%= @issue.id %></h2> + +<%= error_messages_for 'issue' %>
+<%= start_form_tag :action => 'edit', :id => @issue %> + +<!--[form:issue]-->
+<p><%=_('Status')%>: <b><%= @issue.status.name %></b></p>
+ +<div style="float:left;margin-right:10px;"> +<p><label for="issue_tracker_id"><%=_('Tracker')%> <span class="required">*</span></label><br/> +<select name="issue[tracker_id]">
+<%= options_from_collection_for_select @trackers, "id", "name", @issue.tracker_id %></p> +</select></p>
+</div>
+
+<div style="float:left;margin-right:10px;">
+<p><label for="issue_priority_id"><%=_('Priority')%> <span class="required">*</span></label><br/> +<select name="issue[priority_id]">
+<%= options_from_collection_for_select @priorities, "id", "name", @issue.priority_id %></p> +</select></p>
+</div>
+
+<div style="float:left;margin-right:10px;">
+<p><label for="issue_assigned_to_id"><%=_('Assigned to')%></label><br/> +<select name="issue[assigned_to_id]">
+<option value=""></option>
+<%= options_from_collection_for_select @issue.project.members, "user_id", "name", @issue.assigned_to_id %></p> +</select></p>
+</div>
+
+<div>
+<p><label for="issue_category_id"><%=_('Category')%></label><br/> +<select name="issue[category_id]">
+<option value=""></option>
+<%= options_from_collection_for_select @project.issue_categories, "id", "name", @issue.category_id %></p> +</select></p>
+</div>
+
+<p><label for="issue_subject"><%=_('Subject')%></label><span class="required">*</span><br/> +<%= text_field 'issue', 'subject', :size => 60 %></p> + +<p><label for="issue_descr"><%=_('Description')%></label><span class="required">*</span><br/> +<%= text_area 'issue', 'descr', :cols => 60, :rows => 10 %></p> +
+
+<% for custom_value in @custom_values %>
+ <p><%= content_tag "label", custom_value.custom_field.name %>
+ <% if custom_value.custom_field.is_required? %><span class="required">*</span><% end %>
+ <br />
+ <%= custom_field_tag custom_value %></p>
+<% end %>
+
+
+<p><label for="issue_fixed_version"><%=_('Fixed in version')%></label><br/> +<select name="issue[fixed_version_id]">
+<option value="">--none--</option>
+<%= options_from_collection_for_select @project.versions, "id", "name", @issue.fixed_version_id %>
+</select></p> +<!--[eoform:issue]-->
+
+<center><%= submit_tag _('Save') %></center> +<%= end_form_tag %>
diff --git a/redmine/app/views/issues/show.rhtml b/redmine/app/views/issues/show.rhtml new file mode 100644 index 000000000..1c20c92dd --- /dev/null +++ b/redmine/app/views/issues/show.rhtml @@ -0,0 +1,90 @@ +
+<h2><%=_('Issue')%> #<%= @issue.id %> - <%= @issue.subject %></h2>
+ +<div class="box">
+<p><b><%=_('Tracker')%>:</b> <%= @issue.tracker.name %></p>
+<p><b><%=_('Priority')%>:</b> <%= @issue.priority.name %></p>
+<p><b><%=_('Category')%>:</b> <%= @issue.category.name unless @issue.category_id.nil? %></p>
+<p><b><%=_('Status')%>:</b> <%= @issue.status.name %></p>
+<p><b><%=_('Author')%>:</b> <%= @issue.author.display_name %></p>
+<p><b><%=_('Assigned to')%>:</b> <%= @issue.assigned_to.display_name unless @issue.assigned_to.nil? %></p>
+
+<p><b><%=_('Subject')%>:</b> <%= @issue.subject %></p>
+<p><b><%=_('Description')%>:</b> <%= @issue.descr %></p>
+<p><b><%=_('Created on')%>:</b> <%= format_date(@issue.created_on) %></p>
+
+<% if authorize_for('issues', 'edit') %>
+ <%= start_form_tag ({:controller => 'issues', :action => 'edit', :id => @issue}, :method => "get" ) %>
+ <%= submit_tag _('Edit') %>
+ <%= end_form_tag %>
+
+<% end %>
+
+<% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
+ <%= start_form_tag ({:controller => 'issues', :action => 'change_status', :id => @issue}) %>
+ <label for="history_status_id"><%=_('Change status')%>:</label>
+ <select name="history[status_id]">
+ <%= options_from_collection_for_select @status_options, "id", "name" %>
+ </select>
+ <%= submit_tag _ "Update..." %>
+ <%= end_form_tag %>
+
+<% end %>
+
+<% if authorize_for('issues', 'destroy') %>
+ <%= start_form_tag ({:controller => 'issues', :action => 'destroy', :id => @issue} ) %>
+ <%= submit_tag _ "Delete" %>
+ <%= end_form_tag %>
+
+<% end %>
+
+</div>
+
+
+<div class="splitcontentleft">
+<div class="box">
+<h3><%=_('History')%></h3>
+<table width="100%">
+<% for history in @issue.histories.find(:all, :include => :author) %>
+<tr>
+<td><%= format_date(history.created_on) %></td>
+<td><%= history.author.display_name %></td>
+<td><b><%= history.status.name %></b></td>
+</tr>
+<% if history.notes? %>
+ <tr><td colspan=3><div class="notes"><%= history.notes %></td></tr>
+<% end %>
+<% end %> +</table>
+</div>
+</div>
+
+<div class="splitcontentright">
+<div class="box">
+<h3><%=_('Attachments')%></h3>
+<table width="100%">
+<% for attachment in @issue.attachments %>
+<tr>
+<td><%= link_to attachment.filename, :action => 'download', :id => @issue, :attachment_id => attachment %> (<%= human_size(attachment.size) %>)</td>
+<td><%= format_date(attachment.created_on) %></td>
+<td><%= attachment.author.display_name %></td>
+<% if authorize_for('issues', 'destroy_attachment') %>
+ <td>
+ <%= start_form_tag :action => 'destroy_attachment', :id => @issue, :attachment_id => attachment %>
+ <%= submit_tag _('Delete'), :class => "button-small" %>
+ <%= end_form_tag %>
+ </td>
+<% end %>
+</tr>
+<% end %> +</table>
+<br />
+<% if authorize_for('issues', 'add_attachment') %>
+ <%= start_form_tag ({ :controller => 'issues', :action => 'add_attachment', :id => @issue }, :multipart => true) %>
+ <%=_('Add file')%>: <%= file_field 'attachment', 'file' %>
+ <%= submit_tag _('Add') %>
+ <%= end_form_tag %>
+<% end %>
+</div>
+</div>
+
diff --git a/redmine/app/views/layouts/base.rhtml b/redmine/app/views/layouts/base.rhtml new file mode 100644 index 000000000..731632fa4 --- /dev/null +++ b/redmine/app/views/layouts/base.rhtml @@ -0,0 +1,89 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+<title>redMine</title>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<meta name="description" content="redMine" />
+<meta name="keywords" content="issue,bug,tracker" />
+<%= stylesheet_link_tag "application" %>
+<%= stylesheet_link_tag "rails" %>
+<%= javascript_include_tag :defaults %>
+</head>
+
+<body>
+<div id="container" >
+
+ <div id="header">
+ <div style="float: left;">
+ <h1><%= RDM_APP_NAME %></h1>
+ <h2>Project management</h2>
+ </div>
+ <div style="float: right; padding-right: 1em; padding-top: 0.2em;">
+ <% unless session[:user].nil? %><small><%=_('Logged as')%> <b><%= session[:user].login %></b></small><% end %>
+ </div>
+ </div>
+
+ <div id="navigation">
+ <ul>
+ <li class="selected"><%= link_to _('Home'), { :controller => '' }, :class => "picHome" %></li>
+ <li><%= link_to _('My page'), { :controller => 'account', :action => 'my_page'}, :class => "picUserPage" %></li>
+ <li><%= link_to _('Projects'), { :controller => 'projects' }, :class => "picProject" %></li>
+
+ <% unless session[:user].nil? %>
+ <li><%= link_to _('My account'), { :controller => 'account', :action => 'my_account' }, :class => "picUser" %></li>
+ <% end %>
+
+ <% if admin_loggedin? %>
+ <li><%= link_to _('Administration'), { :controller => 'admin' }, :class => "picAdmin" %></li>
+ <% end %>
+
+ <li class="right"><%= link_to _('Help'), { :controller => 'help', :ctrl => @params[:controller], :page => @params[:action] }, :target => "new", :class => "picHelp" %></li>
+ <% if session[:user].nil? %>
+ <li class="right"><%= link_to _('Log in'), { :controller => 'account', :action => 'login' }, :class => "picUser" %></li>
+ <% else %>
+ <li class="right"><%= link_to _('Logout'), { :controller => 'account', :action => 'logout' }, :class => "picUser" %></li>
+ <% end %>
+ </ul>
+
+ </div>
+
+ <div id="subcontent">
+
+ <% unless @project.nil? || @project.id.nil? %>
+ <h2><%= @project.name %></h2>
+ <ul class="menublock">
+ <li><%= link_to _('Overview'), :controller => 'projects', :action => 'show', :id => @project %></li>
+ <li><%= link_to _('Issues'), :controller => 'projects', :action => 'list_issues', :id => @project %></li>
+ <li><%= link_to _('Reports'), :controller => 'reports', :action => 'issue_report', :id => @project %></li>
+ <li><%= link_to _('News'), :controller => 'projects', :action => 'list_news', :id => @project %></li>
+ <li><%= link_to _('Change log'), :controller => 'projects', :action => 'changelog', :id => @project %></li>
+ <li><%= link_to _('Documents'), :controller => 'projects', :action => 'list_documents', :id => @project %></li>
+ <li><%= link_to _('Members'), :controller => 'projects', :action => 'list_members', :id => @project %></li>
+ <li><%= link_to _('Files'), :controller => 'projects', :action => 'list_files', :id => @project %></li>
+ <li><%= link_to_if_authorized _('Settings'), :controller => 'projects', :action => 'settings', :id => @project %></li>
+ </ul>
+ <% end %>
+
+ <% unless session[:user].nil? %>
+ <h2><%=_('My projects') %></h2>
+ <ul class="menublock">
+ <% for membership in session[:user].memberships %>
+ <li><%= link_to membership.project.name, :controller => 'projects', :action => 'show', :id => membership.project %></li>
+ <% end %>
+ </ul>
+ <% end %>
+
+ </div>
+
+ <div id="content">
+ <% if flash[:notice] %><p style="color: green"><%= flash[:notice] %></p><% end %>
+ <%= @content_for_layout %>
+ </div>
+
+ <div id="footer">
+ <p><a href="http://redmine.sourceforge.net/" target="_new"><%= RDM_APP_NAME %></a> <%= RDM_APP_VERSION %></p>
+ </div>
+
+</div>
+</body>
+</html>
\ No newline at end of file diff --git a/redmine/app/views/mailer/_issue.rhtml b/redmine/app/views/mailer/_issue.rhtml new file mode 100644 index 000000000..1f238f513 --- /dev/null +++ b/redmine/app/views/mailer/_issue.rhtml @@ -0,0 +1,6 @@ +<%=_('Issue')%> #<%= issue.id %> - <%= issue.subject %>
+<%=_('Author')%>: <%= issue.author.display_name %>
+ +<%= issue.descr %>
+
+http://<%= RDM_HOST_NAME %>/issues/show/<%= issue.id %>
\ No newline at end of file diff --git a/redmine/app/views/mailer/issue_add.rhtml b/redmine/app/views/mailer/issue_add.rhtml new file mode 100644 index 000000000..9efec9ad9 --- /dev/null +++ b/redmine/app/views/mailer/issue_add.rhtml @@ -0,0 +1,3 @@ +Issue #<%= @issue.id %> has been reported.
+----------------------------------------
+<%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %>
\ No newline at end of file diff --git a/redmine/app/views/mailer/issue_change_status.rhtml b/redmine/app/views/mailer/issue_change_status.rhtml new file mode 100644 index 000000000..e3612ea81 --- /dev/null +++ b/redmine/app/views/mailer/issue_change_status.rhtml @@ -0,0 +1,3 @@ +Issue #<%= @issue.id %> has been updated to "<%= @issue.status.name %>" status.
+----------------------------------------
+<%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %>
\ No newline at end of file diff --git a/redmine/app/views/news/_form.rhtml b/redmine/app/views/news/_form.rhtml new file mode 100644 index 000000000..609b15cd9 --- /dev/null +++ b/redmine/app/views/news/_form.rhtml @@ -0,0 +1,13 @@ +<%= error_messages_for 'news' %> + +<!--[form:news]--> +<p><label for="news_title"><%=_('Title')%></label> <span class="required">*</span><br/> +<%= text_field 'news', 'title', :size => 60 %></p> + +<p><label for="news_shortdescr"><%=_('Summary')%> <span class="required">*</span></label><br/> +<%= text_area 'news', 'shortdescr', :cols => 60, :rows => 2 %></p> + +<p><label for="news_descr"><%=_('Description')%> <span class="required">*</span></label><br/> +<%= text_area 'news', 'descr', :cols => 60, :rows => 10 %></p> +<!--[eoform:news]--> + diff --git a/redmine/app/views/news/edit.rhtml b/redmine/app/views/news/edit.rhtml new file mode 100644 index 000000000..2e849ab1d --- /dev/null +++ b/redmine/app/views/news/edit.rhtml @@ -0,0 +1,6 @@ +<h2><%=_('News')%></h2> + +<%= start_form_tag :action => 'edit', :id => @news %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> diff --git a/redmine/app/views/news/show.rhtml b/redmine/app/views/news/show.rhtml new file mode 100644 index 000000000..30aa2cda9 --- /dev/null +++ b/redmine/app/views/news/show.rhtml @@ -0,0 +1,10 @@ +<h2><%= @news.title %></h2>
+
+<p>
+<b><%=_('Summary')%></b>: <%= @news.shortdescr %><br />
+<b><%=_('By')%></b>: <%= @news.author.display_name %><br />
+<b><%=_('Date')%></b>: <%= format_time(@news.created_on) %>
+</p>
+
+<%= @news.descr %> + diff --git a/redmine/app/views/projects/_form.rhtml b/redmine/app/views/projects/_form.rhtml new file mode 100644 index 000000000..2d38117a4 --- /dev/null +++ b/redmine/app/views/projects/_form.rhtml @@ -0,0 +1,28 @@ +<%= error_messages_for 'project' %> + +<!--[form:project]--> +<p><label for="project_name"><%=_('Name')%> <span class="required">*</span></label><br/> +<%= text_field 'project', 'name' %></p> + +<p><label for="project_descr"><%=_('Description')%> <span class="required">*</span></label><br/> +<%= text_field 'project', 'descr', :size => 60 %></p> +
+<p><label for="project_homepage"><%=_('Homepage')%></label><br/> +<%= text_field 'project', 'homepage', :size => 40 %></p> + +<p><%= check_box 'project', 'public' %>
+<label for="project_public"><%=_('Public')%></label></p> +
+<fieldset><legend><%=_('Custom fields')%></legend>
+<% for custom_field in @custom_fields %>
+ <input type="checkbox"
+
+ name="custom_field_ids[]"
+ value="<%= custom_field.id %>"
+ <%if @project.custom_fields.include? custom_field%>checked="checked"<%end%>
+ > <%= custom_field.name %>
+
+<% end %></fieldset>
+<br />
+ +<!--[eoform:project]--> diff --git a/redmine/app/views/projects/add.rhtml b/redmine/app/views/projects/add.rhtml new file mode 100644 index 000000000..6344705b0 --- /dev/null +++ b/redmine/app/views/projects/add.rhtml @@ -0,0 +1,7 @@ +<h2><%=_('New project')%></h2> + +<%= start_form_tag :action => 'add' %>
+<%= render :partial => 'form' %>
+<%= submit_tag _('Create') %> +<%= end_form_tag %> + diff --git a/redmine/app/views/projects/add_document.rhtml b/redmine/app/views/projects/add_document.rhtml new file mode 100644 index 000000000..13e75dd77 --- /dev/null +++ b/redmine/app/views/projects/add_document.rhtml @@ -0,0 +1,26 @@ +<h2><%=_('New document')%></h2> +
+<%= error_messages_for 'document' %>
+<%= start_form_tag( { :action => 'add_document', :id => @project }, :multipart => true) %>
+ +<!--[form:document]--> +<p><label for="document_category_id"><%=_('Category')%></label><br />
+<select name="document[category_id]">
+<%= options_from_collection_for_select @categories, "id", "name",@document.category_id %> +</select></p>
+
+<p><label for="document_title"><%=_('Title')%> <span class="required">*</span></label><br /> +<%= text_field 'document', 'title', :size => 60 %></p> + +<p><label for="document_descr"><%=_('Description')%> <span class="required">*</span></label><br /> +<%= text_area 'document', 'descr', :cols => 60, :rows => 5 %></p> +
+<p><label for="attachment_file"><%=_('File')%></label><br/> +<%= file_field 'attachment', 'file' %></p> +
+<!--[eoform:document]--> + +<%= submit_tag _('Create') %> +<%= end_form_tag %> +
+ diff --git a/redmine/app/views/projects/add_file.rhtml b/redmine/app/views/projects/add_file.rhtml new file mode 100644 index 000000000..4e8ce9b02 --- /dev/null +++ b/redmine/app/views/projects/add_file.rhtml @@ -0,0 +1,13 @@ +<h2><%=_('New file')%></h2>
+
+<%= start_form_tag ({ :action => 'add_file', :project => @project }, :multipart => true) %>
+
+<p><label for="version_id"><%=_('Version')%></label><br />
+<select name="version_id">
+<%= options_from_collection_for_select @versions, "id", "name" %> +</select></p>
+
+<p><b><%=_('File')%><b><br /><%= file_field 'attachment', 'file' %></p>
+<br/>
+<%= submit_tag _('Add') %>
+<%= end_form_tag %>
\ No newline at end of file diff --git a/redmine/app/views/projects/add_issue.rhtml b/redmine/app/views/projects/add_issue.rhtml new file mode 100644 index 000000000..a6b37cc46 --- /dev/null +++ b/redmine/app/views/projects/add_issue.rhtml @@ -0,0 +1,62 @@ +<h2><%=_('New issue')%></h2> +
+<%= start_form_tag( { :action => 'add_issue', :id => @project }, :multipart => true) %>
+<%= error_messages_for 'issue' %> + +<!--[form:issue]-->
+
+<div style="float:left;margin-right:10px;"> +<p><label for="issue_tracker_id"><%=_('Tracker')%> <span class="required">*</span></label><br/> +<select name="issue[tracker_id]">
+<%= options_from_collection_for_select @trackers, "id", "name", @issue.tracker_id %></p> +</select></p>
+</div>
+
+<div style="float:left;margin-right:10px;">
+<p><label for="issue_priority_id"><%=_('Priority')%> <span class="required">*</span></label><br/> +<select name="issue[priority_id]">
+<%= options_from_collection_for_select @priorities, "id", "name", @issue.priority_id %></p> +</select></p>
+</div>
+
+<div style="float:left;margin-right:10px;">
+<p><label for="issue_assigned_to_id"><%=_('Assigned to')%></label><br/> +<select name="issue[assigned_to_id]">
+<option value=""></option>
+<%= options_from_collection_for_select @issue.project.members, "user_id", "name", @issue.assigned_to_id %></p> +</select></p>
+</div>
+
+<div>
+<p><label for="issue_category_id"><%=_('Category')%></label><br/> +<select name="issue[category_id]">
+<option value=""></option>
+<%= options_from_collection_for_select @project.issue_categories, "id", "name", @issue.category_id %></p> +</select></p>
+</div>
+ +<p><label for="issue_subject"><%=_('Subject')%> <span class="required">*</span></label><br/> +<%= text_field 'issue', 'subject', :size => 80 %></p> +
+<p><label for="issue_descr"><%=_('Description')%> <span class="required">*</span></label><br/> +<%= text_area 'issue', 'descr', :cols => 60, :rows => 10 %></p>
+
+
+<% for custom_value in @custom_values %>
+ <div style="float:left;margin-right:10px;">
+ <p><%= content_tag "label", custom_value.custom_field.name %>
+ <% if custom_value.custom_field.is_required? %><span class="required">*</span><% end %>
+ <br />
+ <%= custom_field_tag custom_value %></p>
+ </div>
+<% end %>
+
+<div style="clear: both;">
+<p><label for="attachment_file"><%=_('Attachment')%></label><br/> +<%= file_field 'attachment', 'file' %></p>
+</div>
+ +<!--[eoform:issue]-->
+ +<%= submit_tag _('Create') %>
+<%= end_form_tag %>
\ No newline at end of file diff --git a/redmine/app/views/projects/add_news.rhtml b/redmine/app/views/projects/add_news.rhtml new file mode 100644 index 000000000..c106e7c21 --- /dev/null +++ b/redmine/app/views/projects/add_news.rhtml @@ -0,0 +1,7 @@ +<h2><%=('Add news')%></h2> + +<%= start_form_tag :action => 'add_news', :id => @project %> + <%= render :partial => 'news/form' %>
+ <%= submit_tag _('Create') %> +<%= end_form_tag %> + diff --git a/redmine/app/views/projects/add_version.rhtml b/redmine/app/views/projects/add_version.rhtml new file mode 100644 index 000000000..27b258fab --- /dev/null +++ b/redmine/app/views/projects/add_version.rhtml @@ -0,0 +1,7 @@ +<h2><%=_('New version')%></h2> + +<%= start_form_tag :action => 'add_version', :id => @project %>
+ <%= render :partial => 'versions/form' %>
+ <%= submit_tag _('Create') %> +<%= end_form_tag %> + diff --git a/redmine/app/views/projects/changelog.rhtml b/redmine/app/views/projects/changelog.rhtml new file mode 100644 index 000000000..3f97abccd --- /dev/null +++ b/redmine/app/views/projects/changelog.rhtml @@ -0,0 +1,12 @@ +<h2><%=_('Change log')%></h2>
+
+<% fixed_issues = @fixed_issues.group_by {|i| i.fixed_version } %>
+<% fixed_issues.each do |version, issues| %>
+ <p><strong><%= version.name %></strong> - <%= format_date(version.date) %><br />
+ <%=h version.descr %></p>
+ <ul>
+ <% issues.each do |i| %>
+ <li><%= link_to i.long_id, :controller => 'issues', :action => 'show', :id => i %> [<%= i.tracker.name %>]: <%= i.subject %></li>
+ <% end %>
+ </ul>
+<% end %>
diff --git a/redmine/app/views/projects/destroy.rhtml b/redmine/app/views/projects/destroy.rhtml new file mode 100644 index 000000000..2e26478d9 --- /dev/null +++ b/redmine/app/views/projects/destroy.rhtml @@ -0,0 +1,12 @@ +<h2><%=_('Confirmation')%></h2>
+<div class="box">
+<center>
+<p><%=_('Are you sure you want to delete project')%> <strong><%= @project.name %></strong> ?</p>
+<p>
+ <%= start_form_tag({:controller => 'projects', :action => 'destroy', :id => @project}) %>
+ <%= hidden_field_tag "confirm", 1 %>
+ <%= submit_tag _('Delete') %>
+ <%= end_form_tag %>
+</p>
+</center>
+</div>
\ No newline at end of file diff --git a/redmine/app/views/projects/list.rhtml b/redmine/app/views/projects/list.rhtml new file mode 100644 index 000000000..2b2ac2d13 --- /dev/null +++ b/redmine/app/views/projects/list.rhtml @@ -0,0 +1,22 @@ +<h2><%=_('Public projects')%></h2> + +<table width="100%" cellspacing="1" cellpadding="2" class="listTableContent"> + <tr class="ListHead"> + <%= sort_header_tag('projects.name', :caption => _('Project')) %>
+ <th>Description</th>
+ <%= sort_header_tag('projects.created_on', :caption => _('Created on')) %> + </tr> + +<% odd_or_even = 1
+ for project in @projects
+ odd_or_even = 1 - odd_or_even %> + <tr class="ListLine<%= odd_or_even %>">
+ <td><%= link_to project.name, :action => 'show', :id => project %>
+ <td><%= project.descr %>
+ <td align="center"><%= format_date(project.created_on) %>
+ </tr> +<% end %> +</table>
+
+<%= pagination_links_full @project_pages %> +[ <%= @project_pages.current.first_item %> - <%= @project_pages.current.last_item %> / <%= @project_count %> ]
\ No newline at end of file diff --git a/redmine/app/views/projects/list_documents.rhtml b/redmine/app/views/projects/list_documents.rhtml new file mode 100644 index 000000000..1c7db5ec9 --- /dev/null +++ b/redmine/app/views/projects/list_documents.rhtml @@ -0,0 +1,21 @@ +<h2><%=_('Documents')%></h2>
+
+<% documents = @documents.group_by {|d| d.category } %>
+<% documents.each do |category, docs| %>
+<h3><%= category.name %></h3>
+<ul>
+<% docs.each do |d| %>
+ <li>
+ <b><%= link_to d.title, :controller => 'documents', :action => 'show', :id => d %></b>
+ <br />
+ <%=_('Desciption')%>: <%= d.descr %><br />
+ <%= format_time(d.created_on) %>
+ </li> +
+<% end %>
+</ul>
+<% end %>
+
+<p>
+<%= link_to_if_authorized '» ' + _('New'), :controller => 'projects', :action => 'add_document', :id => @project %> +</p>
diff --git a/redmine/app/views/projects/list_files.rhtml b/redmine/app/views/projects/list_files.rhtml new file mode 100644 index 000000000..217e679b5 --- /dev/null +++ b/redmine/app/views/projects/list_files.rhtml @@ -0,0 +1,47 @@ +<h2><%=_('Files')%></h2>
+
+<% delete_allowed = authorize_for('versions', 'destroy_file') %>
+
+<table border="0" cellspacing="1" cellpadding="2" class="listTableContent">
+ <tr class="ListHead">
+ <th><%=_('Version')%></th>
+ <th><%=_('File')%></th>
+ <th><%=_('Date')%></th>
+ <th><%=_('Size')%></th>
+ <th>D/L</th>
+ <th>MD5</th>
+ <% if delete_allowed %><th></th><% end %>
+ </tr>
+
+<% for version in @versions %>
+ <tr>
+ <td colspan="7"><%= image_tag 'package' %> <b><%= version.name %></b></td>
+ </tr>
+ <% odd_or_even = 1
+ for file in version.attachments
+ odd_or_even = 1 - odd_or_even %> + <tr class="ListLine<%= odd_or_even %>">
+ <td></td>
+ <td><%= link_to file.filename, :controller => 'versions', :action => 'download', :id => version, :attachment_id => file %></td>
+ <td align="center"><%= format_date(file.created_on) %></td>
+ <td align="center"><%= human_size(file.size) %></td>
+ <td align="center"><%= file.downloads %></td> + <td align="center"><small><%= file.digest %></small></td>
+ <% if delete_allowed %>
+ <td align="center">
+ <%= start_form_tag :controller => 'versions', :action => 'destroy_file', :id => version, :attachment_id => file %>
+ <%= submit_tag _('Delete'), :class => "button-small" %>
+ <%= end_form_tag %>
+ </td>
+ <% end %> + </tr> + <% end %>
+<% end %>
+</table>
+
+<br />
+<p>
+<%= link_to_if_authorized '» ' + _('New'), :controller => 'projects', :action => 'add_file', :id => @project %> +</p>
+
+
diff --git a/redmine/app/views/projects/list_issues.rhtml b/redmine/app/views/projects/list_issues.rhtml new file mode 100644 index 000000000..5be81b11c --- /dev/null +++ b/redmine/app/views/projects/list_issues.rhtml @@ -0,0 +1,56 @@ +<h2><%=_('Issues')%></h2>
+
+<form method="post" class="noborder">
+ <table cellpadding=2>
+ <tr>
+ <td><%=_('Status')%>:<br /><%= search_filter_tag("issues.status_id") %></td>
+ <td><%=_('Tracker')%>:<br /><%= search_filter_tag("issues.tracker_id") %></td>
+ <td><%=_('Priority')%>:<br /><%= search_filter_tag("issues.priority_id") %></td>
+ <td><%=_('Category')%>:<br /><%= search_filter_tag("issues.category_id") %></td>
+ <td><%=_('Author')%>:<br /><%= search_filter_tag("issues.author_id") %></td>
+ <td valign="bottom">
+ <%= submit_tag _('Apply filter') %>
+ <%= end_form_tag %>
+
+ <%= start_form_tag %>
+ <%= submit_tag _('Reset') %>
+ <%= end_form_tag %>
+ </td>
+ </tr>
+ </table>
+
+
+
+ <table border="0" cellspacing="1" cellpadding="2" class="listTableContent">
+
+ <tr class="ListHead">
+ <%= sort_header_tag('issues.id', :caption => '#') %>
+ <%= sort_header_tag('issue_statuses.name', :caption => _('Status')) %>
+ <%= sort_header_tag('issues.tracker_id', :caption => _('Tracker')) %>
+ <th><%=_('Subject')%></th>
+ <%= sort_header_tag('users.lastname', :caption => _('Author')) %>
+ <%= sort_header_tag('issues.created_on', :caption => _('Created on')) %>
+ <%= sort_header_tag('issues.updated_on', :caption => _('Last update')) %>
+ </tr> +
+ <% for issue in @issues %> + <tr bgcolor="#<%= issue.status.html_color %>">
+ <td align="center"><%= link_to issue.long_id, :controller => 'issues', :action => 'show', :id => issue %></td>
+ <td align="center"><%= issue.status.name %></td>
+ <td align="center"><%= issue.tracker.name %></td>
+ <td><%= link_to issue.subject, :controller => 'issues', :action => 'show', :id => issue %></td> + <td align="center"><%= issue.author.display_name %></td>
+ <td align="center"><%= format_time(issue.created_on) %></td> + <td align="center"><%= format_time(issue.updated_on) %></td> + </tr> + <% end %> + </table>
+ <p>
+ <%= pagination_links_full @issue_pages %>
+ [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]
+ </p> + +
+<p> +<%= link_to_if_authorized '» ' + _('Report an issue'), :controller => 'projects', :action => 'add_issue', :id => @project %> +</p>
\ No newline at end of file diff --git a/redmine/app/views/projects/list_members.rhtml b/redmine/app/views/projects/list_members.rhtml new file mode 100644 index 000000000..6fca0d2bf --- /dev/null +++ b/redmine/app/views/projects/list_members.rhtml @@ -0,0 +1,11 @@ +<h2><%=_('Project members')%></h2>
+
+<% members = @members.group_by {|m| m.role } %>
+<% members.each do |role, member| %>
+<h3><%= role.name %></h3>
+<ul>
+<% member.each do |m| %>
+<li><%= link_to m.user.display_name, :controller => 'account', :action => 'show', :id => m.user %> (<%= format_date m.created_on %>)</li>
+<% end %>
+</ul>
+<% end %>
diff --git a/redmine/app/views/projects/list_news.rhtml b/redmine/app/views/projects/list_news.rhtml new file mode 100644 index 000000000..7db53391e --- /dev/null +++ b/redmine/app/views/projects/list_news.rhtml @@ -0,0 +1,17 @@ +<h2><%=_('News')%></h2> + +<% for news in @news %>
+ <p>
+ <b><%= news.title %></b> <small>(<%= link_to_user news.author %> <%= format_time(news.created_on) %>)</small>
+ <%= link_to_if_authorized image_tag('edit_small'), :controller => 'news', :action => 'edit', :id => news %>
+ <%= link_to_if_authorized image_tag('delete'), { :controller => 'news', :action => 'destroy', :id => news }, :confirm => 'Are you sure?' %>
+ <br />
+ <%= news.shortdescr %>
+ <small>[<%= link_to _('Read...'), :controller => 'news', :action => 'show', :id => news %>]</small>
+ </p>
+<% end %> +
+<%= pagination_links_full @news_pages %>
+<p> +<%= link_to_if_authorized '» ' + _('Add'), :controller => 'projects', :action => 'add_news', :id => @project %> +</p>
diff --git a/redmine/app/views/projects/settings.rhtml b/redmine/app/views/projects/settings.rhtml new file mode 100644 index 000000000..c130ed036 --- /dev/null +++ b/redmine/app/views/projects/settings.rhtml @@ -0,0 +1,105 @@ +<h2><%=_('Settings')%></h2> +
+<div class="box"> +<%= start_form_tag :action => 'edit', :id => @project %> +<%= render :partial => 'form' %>
+<center><%= submit_tag _('Save') %></center> +<%= end_form_tag %> +</div>
+
+<div class="box"> +<h3><%=_('Members')%></h3>
+<%= error_messages_for 'member' %>
+<table>
+<% for member in @project.members.find(:all, :include => :user) %>
+ <% unless member.new_record? %>
+ <tr>
+ <td><%= member.user.display_name %></td>
+ <td>
+ <%= start_form_tag :controller => 'members', :action => 'edit', :id => member %>
+ <select name="member[role_id]">
+ <%= options_from_collection_for_select @roles, "id", "name", member.role_id %> + </select>
+ </td>
+ <td>
+ <%= submit_tag _('Save'), :class => "button-small" %>
+ <%= end_form_tag %>
+ </td>
+ <td>
+ <%= start_form_tag :controller => 'members', :action => 'destroy', :id => member %>
+ <%= submit_tag _('Delete'), :class => "button-small" %>
+ <%= end_form_tag %>
+ </td>
+ </tr>
+ <% end %>
+<% end %>
+</table>
+<hr />
+ <label><%=_('New member')%></label><br/>
+ <%= start_form_tag :controller => 'projects', :action => 'add_member', :id => @project %>
+ <select name="member[user_id]">
+ <%= options_from_collection_for_select @users, "id", "display_name", @member.user_id %> + </select>
+ <select name="member[role_id]">
+ <%= options_from_collection_for_select @roles, "id", "name", @member.role_id %> + </select>
+ <%= submit_tag _('Add') %>
+ <%= end_form_tag %>
+</div>
+
+<div class="box">
+<h3><%=_('Versions')%></h3>
+
+<table>
+<% for version in @project.versions %>
+ <tr>
+ <td><%= link_to version.name, :controller => 'versions', :action => 'edit', :id => version %></td>
+ <td><%=h version.descr %></td>
+ <td>
+ <%= start_form_tag :controller => 'versions', :action => 'destroy', :id => version %>
+ <%= submit_tag _('Delete'), :class => "button-small" %>
+ <%= end_form_tag %>
+ </td>
+ </tr>
+<% end %>
+</table>
+<hr />
+ <%= start_form_tag ({ :controller => 'projects', :action => 'add_version', :id => @project }, :method => 'get' ) %>
+ <%= submit_tag _('New version...') %>
+ <%= end_form_tag %>
+</div>
+
+
+<div class="box">
+<h3><%=_('Issue categories')%></h3> +<table>
+<% for @category in @project.issue_categories %>
+ <% unless @category.new_record? %>
+ <tr>
+ <td>
+ <%= start_form_tag :controller => 'issue_categories', :action => 'edit', :id => @category %>
+ <%= text_field 'category', 'name', :size => 25 %>
+ </td>
+ <td>
+ <%= submit_tag _('Save'), :class => "button-small" %>
+ <%= end_form_tag %>
+ </td>
+ <td>
+ <%= start_form_tag :controller => 'issue_categories', :action => 'destroy', :id => @category %>
+ <%= submit_tag _('Delete'), :class => "button-small" %>
+ <%= end_form_tag %>
+ </td>
+ </tr>
+ <% end %>
+<% end %>
+</table>
+<hr />
+
+<%= start_form_tag :action => 'add_issue_category', :id => @project %> +<%= error_messages_for 'issue_category' %>
+<label for="issue_category_name"><%=_('New category')%></label><br/> +<%= text_field 'issue_category', 'name', :size => 25 %> +<%= submit_tag _('Create') %>
+<%= end_form_tag %>
+
+</div>
diff --git a/redmine/app/views/projects/show.rhtml b/redmine/app/views/projects/show.rhtml new file mode 100644 index 000000000..3f10654f5 --- /dev/null +++ b/redmine/app/views/projects/show.rhtml @@ -0,0 +1,53 @@ +<h2><%=_('Overview')%></h2>
+
+<div class="splitcontentleft">
+ <%= @project.descr %>
+ <ul>
+ <li><%=_('Homepage')%>: <%= link_to @project.homepage, @project.homepage %></li>
+ <li><%=_('Created on')%>: <%= format_date(@project.created_on) %></li>
+ </ul>
+
+ <div class="box">
+ <h3><%= image_tag "tracker" %> <%=_('Trackers')%></h3>
+ <ul>
+ <% for tracker in Tracker.find_all %>
+ <li><%= link_to tracker.name, :controller => 'projects', :action => 'list_issues', :id => @project,
+ :set_filter => 1,
+ "issues.tracker_id" => tracker.id %>:
+ <%= tracker.issues.count(["project_id=?", @project.id]) %> <%=_('open')%>
+ </li>
+ <% end %>
+ </ul> + <%= link_to_if_authorized '» ' + _('Report an issue'), :controller => 'projects', :action => 'add_issue', :id => @project %> + <br />
+ <center><small>[ <%= link_to _('View all issues'), :controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1 %> ]</small></center>
+ </div> +</div>
+
+<div class="splitcontentright">
+ <div class="box">
+ <h3><%= image_tag "users" %> <%=_('Members')%></h3>
+ <% for member in @members %>
+ <%= link_to_user member.user %> (<%= member.role.name %>)<br />
+ <% end %>
+ </div>
+
+ <div class="box">
+ <h3><%=_('Latest news')%></h3>
+ <% for news in @project.news %>
+ <p>
+ <b><%= news.title %></b> <small>(<%= link_to_user news.author %> <%= format_time(news.created_on) %>)</small><br />
+ <%= news.shortdescr %>
+ <small>[<%= link_to _('Read...'), :controller => 'news', :action => 'show', :id => news %>]</small>
+ </p>
+ <hr />
+ <% end %> + <center><small>[ <%= link_to _('View all news'), :controller => 'projects', :action => 'list_news', :id => @project %> ]</small></center>
+ </div>
+</div>
+
+
+
+ +
+
diff --git a/redmine/app/views/reports/_simple.rhtml b/redmine/app/views/reports/_simple.rhtml new file mode 100644 index 000000000..5816284ec --- /dev/null +++ b/redmine/app/views/reports/_simple.rhtml @@ -0,0 +1,34 @@ +<% col_width = 70 / (@statuses.length+3) %>
+
+<table border="0" cellspacing="1" cellpadding="2" width="100%">
+<tr>
+<td width="25%"></td>
+<% for status in @statuses %>
+ <td align="center" width="<%= col_width %>%" bgcolor="#<%= status.html_color %>"><small><%= status.name %></small></td>
+<% end %>
+<td align="center" width="<%= col_width %>%"><strong><%=_('Open')%></strong></td>
+<td align="center" width="<%= col_width %>%"><strong><%=_('Closed')%></strong></td>
+<td align="center" width="<%= col_width %>%"><strong><%=_('Total')%></strong></td>
+</tr>
+
+<% for row in rows %>
+<tr style="background-color:#CEE1ED">
+ <td><%= link_to row.name, :controller => 'projects', :action => 'list_issues', :id => @project,
+ :set_filter => 1,
+ "issues.#{field_name}" => row.id %></td>
+ <% for status in @statuses %>
+ <td align="center"><%= link_to (aggregate data, { field_name => row.id, "status_id" => status.id }),
+ :controller => 'projects', :action => 'list_issues', :id => @project,
+ :set_filter => 1,
+ "issues.status_id" => status.id,
+ "issues.#{field_name}" => row.id %></td>
+ <% end %>
+ <td align="center"><%= aggregate data, { field_name => row.id, "closed" => 0 } %></td>
+ <td align="center"><%= aggregate data, { field_name => row.id, "closed" => 1 } %></td>
+ <td align="center"><%= link_to (aggregate data, { field_name => row.id }),
+ :controller => 'projects', :action => 'list_issues', :id => @project,
+ :set_filter => 1,
+ "issues.#{field_name}" => row.id %></td>
+<% end %>
+</tr>
+</table>
\ No newline at end of file diff --git a/redmine/app/views/reports/issue_report.rhtml b/redmine/app/views/reports/issue_report.rhtml new file mode 100644 index 000000000..32a1f55f2 --- /dev/null +++ b/redmine/app/views/reports/issue_report.rhtml @@ -0,0 +1,13 @@ +<h2><%=_('Reports')%></h2>
+
+<strong><%=_('Issues by tracker')%></strong>
+<%= render :partial => 'simple', :locals => { :data => @issues_by_tracker, :field_name => "tracker_id", :rows => @trackers } %>
+<br />
+
+<strong><%=_('Issues by priority')%></strong>
+<%= render :partial => 'simple', :locals => { :data => @issues_by_priority, :field_name => "priority_id", :rows => @priorities } %>
+<br />
+
+<strong><%=_('Issues by category')%></strong>
+<%= render :partial => 'simple', :locals => { :data => @issues_by_category, :field_name => "category_id", :rows => @categories } %>
+
diff --git a/redmine/app/views/roles/_form.rhtml b/redmine/app/views/roles/_form.rhtml new file mode 100644 index 000000000..02eef4717 --- /dev/null +++ b/redmine/app/views/roles/_form.rhtml @@ -0,0 +1,22 @@ +<%= error_messages_for 'role' %> + +<!--[form:role]-->
+<p><label for="role_name"><%=_('Name')%> <span class="required">*</span></label><br /> +<%= text_field 'role', 'name' %></p>
+
+<strong><%=_('Permissions')%></strong>
+<% permissions = @permissions.group_by {|p| p.group_id } %>
+<% permissions.keys.sort.each do |group_id| %>
+<fieldset><legend><%= _(Permission::GROUPS[group_id]) %></legend>
+<% permissions[group_id].each do |p| %>
+ <div style="width:200px;float:left;"><%= check_box_tag "permission_ids[]", p.id, (@role.permissions.include? p) %>
+ <%= _(p.descr) %>
+ </div>
+<% end %>
+</fieldset>
+<% end %>
+<br />
+<a href="javascript:checkAll('role_form', true)"><%=_('Check all')%></a> |
+<a href="javascript:checkAll('role_form', false)"><%=_('Uncheck all')%></a><br />
+<!--[eoform:role]--> + diff --git a/redmine/app/views/roles/edit.rhtml b/redmine/app/views/roles/edit.rhtml new file mode 100644 index 000000000..cffbe0119 --- /dev/null +++ b/redmine/app/views/roles/edit.rhtml @@ -0,0 +1,10 @@ +<h2><%=_('Role')%></h2> + +<%= start_form_tag ({ :action => 'edit', :id => @role }, :id => 'role_form') %> +<%= render :partial => 'form' %>
+
+<br />
+<%= submit_tag _('Save') %> +<%= end_form_tag %>
+ +
diff --git a/redmine/app/views/roles/list.rhtml b/redmine/app/views/roles/list.rhtml new file mode 100644 index 000000000..146e45886 --- /dev/null +++ b/redmine/app/views/roles/list.rhtml @@ -0,0 +1,23 @@ +<h2><%=_('Roles')%></h2> + +<table border="0" cellspacing="1" cellpadding="2" class="listTableContent"> + <tr class="ListHead"> + <th><%=_('Role')%></th> + <th></th> + </tr> + +<% for role in @roles %> + <tr style="background-color:#CEE1ED"> + <td><%= link_to role.name, :action => 'edit', :id => role %></td> + <td align="center">
+ <%= start_form_tag :action => 'destroy', :id => role %>
+ <%= submit_tag _('Delete'), :class => "button-small" %>
+ <%= end_form_tag %> + </tr> +<% end %> +</table> +
+<%= pagination_links_full @role_pages %> +<br /> + +<%= link_to '» ' + _('New role'), :action => 'new' %> diff --git a/redmine/app/views/roles/new.rhtml b/redmine/app/views/roles/new.rhtml new file mode 100644 index 000000000..c82fb2144 --- /dev/null +++ b/redmine/app/views/roles/new.rhtml @@ -0,0 +1,8 @@ +<h2><%=_('New role')%></h2> +
+<%= start_form_tag ({ :action => 'new' }, :id => 'role_form') %> +<%= render :partial => 'form' %>
+ +<br /><%= submit_tag _('Create') %> +<%= end_form_tag %> + diff --git a/redmine/app/views/roles/workflow.rhtml b/redmine/app/views/roles/workflow.rhtml new file mode 100644 index 000000000..652f6a0f1 --- /dev/null +++ b/redmine/app/views/roles/workflow.rhtml @@ -0,0 +1,70 @@ +<h2><%=_('Workflow setup')%></h2>
+
+<p><%=_('Select a workflow to edit')%>:</p>
+
+<%= start_form_tag ({:action => 'workflow'}, :method => 'get') %>
+<div style="float:left;margin-right:10px;">
+<p><label for="role_id"><%=_('Role')%></label><br/>
+<select id="role_id" name="role_id">
+ <%= options_from_collection_for_select @roles, "id", "name", (@role.id unless @role.nil?) %>
+</select></p>
+</div>
+
+<div>
+<p><label for="tracker_id"><%=_('Tracker')%></label><br/>
+<select id="tracker_id" name="tracker_id">
+ <%= options_from_collection_for_select @trackers, "id", "name", (@tracker.id unless @tracker.nil?) %>
+</select>
+
+<%= submit_tag _('Edit') %>
+</p>
+</div>
+<%= end_form_tag %>
+
+
+
+<% unless @tracker.nil? or @role.nil? %>
+<div class="box">
+ <%= form_tag ({:action => 'workflow', :role_id => @role, :tracker_id => @tracker }, :id => 'workflow_form' ) %>
+ <table>
+ <tr>
+ <td align="center"><strong><%=_('Issue status')%></strong></td>
+ <td align="center" colspan="<%= @statuses.length %>"><strong><%=_('New statuses allowed')%></strong></td>
+ </tr>
+ <tr>
+ <td></td>
+ <% for new_status in @statuses %>
+ <td width="60" align="center"><%= new_status.name %></td>
+ <% end %>
+ </tr>
+
+ <% for old_status in @statuses %>
+ <tr>
+ <td><%= old_status.name %></td>
+
+ <% for new_status in @statuses %>
+ <td align="center">
+
+ <input type="checkbox"
+ name="issue_status[<%= old_status.id %>][]"
+ value="<%= new_status.id %>"
+ <%if old_status.new_statuses_allowed_to(@role, @tracker).include? new_status%>checked="checked"<%end%>
+ <%if old_status==new_status%>disabled<%end%>
+ >
+ </td>
+ <% end %>
+
+ </tr>
+ <% end %>
+ </table>
+<br />
+<p>
+<a href="javascript:checkAll('workflow_form', true)"><%=_('Check all')%></a> |
+<a href="javascript:checkAll('workflow_form', false)"><%=_('Uncheck all')%></a>
+</p>
+<br />
+<%= submit_tag _('Save') %> +<%= end_form_tag %>
+
+<% end %>
+</div>
\ No newline at end of file diff --git a/redmine/app/views/trackers/_form.rhtml b/redmine/app/views/trackers/_form.rhtml new file mode 100644 index 000000000..95d06d777 --- /dev/null +++ b/redmine/app/views/trackers/_form.rhtml @@ -0,0 +1,10 @@ +<%= error_messages_for 'tracker' %> + +<!--[form:tracker]--> +<p><label for="tracker_name"><%=_('Name')%></label><br/> +<%= text_field 'tracker', 'name' %></p> +
+<p><%= check_box 'tracker', 'is_in_chlog' %>
+<label for="tracker_is_in_chlog"><%=_('View issues in change log')%></label></p>
+<!--[eoform:tracker]--> + diff --git a/redmine/app/views/trackers/edit.rhtml b/redmine/app/views/trackers/edit.rhtml new file mode 100644 index 000000000..a209b3f38 --- /dev/null +++ b/redmine/app/views/trackers/edit.rhtml @@ -0,0 +1,6 @@ +<h2><%=_('Tracker')%></h2> + +<%= start_form_tag :action => 'edit', :id => @tracker %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> diff --git a/redmine/app/views/trackers/list.rhtml b/redmine/app/views/trackers/list.rhtml new file mode 100644 index 000000000..3622a40f9 --- /dev/null +++ b/redmine/app/views/trackers/list.rhtml @@ -0,0 +1,24 @@ +<h2><%=_('Trackers')%></h2> + +<table border="0" cellspacing="1" cellpadding="2" class="listTableContent"> + <tr class="ListHead"> + <th><%=_('Tracker')%></th>
+ <th></th> + </tr> + +<% for tracker in @trackers %> + <tr style="background-color:#CEE1ED"> + <td><%= link_to tracker.name, :action => 'edit', :id => tracker %></td> + <td align="center">
+ <%= start_form_tag :action => 'destroy', :id => tracker %>
+ <%= submit_tag _('Delete'), :class => "button-small" %>
+ <%= end_form_tag %>
+ </td> + </tr> +<% end %> +</table> +
+<%= pagination_links_full @tracker_pages %> +<br /> + +<%= link_to '» ' + _('New tracker'), :action => 'new' %> diff --git a/redmine/app/views/trackers/new.rhtml b/redmine/app/views/trackers/new.rhtml new file mode 100644 index 000000000..a24fca738 --- /dev/null +++ b/redmine/app/views/trackers/new.rhtml @@ -0,0 +1,7 @@ +<h2><%=_('New tracker')%></h2> + +<%= start_form_tag :action => 'new' %> + <%= render :partial => 'form' %> + <%= submit_tag _('Create') %> +<%= end_form_tag %> + diff --git a/redmine/app/views/users/_form.rhtml b/redmine/app/views/users/_form.rhtml new file mode 100644 index 000000000..92f1e0e5a --- /dev/null +++ b/redmine/app/views/users/_form.rhtml @@ -0,0 +1,28 @@ +<%= error_messages_for 'user' %> + +<!--[form:user]--> +<p><label for="user_login"><%=_('Login')%></label><br/> +<%= text_field 'user', 'login' %></p> +
+<p><label for="user_password"><%=_('Password')%></label><br/> +<%= password_field 'user', 'password' %></p> + +<p><label for="user_firstname"><%=_('Firstname')%></label><br/> +<%= text_field 'user', 'firstname' %></p> +
+<p><label for="user_lastname"><%=_('Lastname')%></label><br/> +<%= text_field 'user', 'lastname' %></p> + +<p><label for="user_mail"><%=_('Mail')%></label><br/> +<%= text_field 'user', 'mail' %></p>
+
+<p><label for="user_language"><%=_('Language')%></label><br/> +<%= select("user", "language", Localization.langs) %></p>
+ +<p><%= check_box 'user', 'admin' %> <label for="user_admin"><%=_('Administrator')%></label></p>
+
+<p><%= check_box 'user', 'mail_notification' %> <label for="user_mail_notification"><%=_('Mail notifications')%></label></p>
+
+<p><%= check_box 'user', 'locked' %> <label for="user_locked"><%=_('Locked')%></label></p> +<!--[eoform:user]--> + diff --git a/redmine/app/views/users/add.rhtml b/redmine/app/views/users/add.rhtml new file mode 100644 index 000000000..9d0ba8d3a --- /dev/null +++ b/redmine/app/views/users/add.rhtml @@ -0,0 +1,6 @@ +<h2><%=_('New user')%></h2> + +<%= start_form_tag :action => 'add' %> + <%= render :partial => 'form' %> + <%= submit_tag _('Create') %> +<%= end_form_tag %> diff --git a/redmine/app/views/users/edit.rhtml b/redmine/app/views/users/edit.rhtml new file mode 100644 index 000000000..a033a7fda --- /dev/null +++ b/redmine/app/views/users/edit.rhtml @@ -0,0 +1,7 @@ +<h2><%=_('User')%></h2> + +<%= start_form_tag :action => 'edit', :id => @user %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %>
+ +<%= end_form_tag %> diff --git a/redmine/app/views/users/list.rhtml b/redmine/app/views/users/list.rhtml new file mode 100644 index 000000000..b1bf937aa --- /dev/null +++ b/redmine/app/views/users/list.rhtml @@ -0,0 +1,46 @@ +<h2><%=_('Users')%></h2> +
+<table border="0" cellspacing="1" cellpadding="2" class="listTableContent">
+<tr class="ListHead"> + <%= sort_header_tag('users.login', :caption => _('Login')) %>
+ <%= sort_header_tag('users.firstname', :caption => _('Firstname')) %>
+ <%= sort_header_tag('users.lastname', :caption => _('Lastname')) %>
+ <th><%=_('Mail')%></th>
+ <%= sort_header_tag('users.admin', :caption => _('Admin')) %>
+ <%= sort_header_tag('users.locked', :caption => _('Locked')) %>
+ <%= sort_header_tag('users.created_on', :caption => _('Created on')) %>
+ <%= sort_header_tag('users.last_login_on', :caption => _('Last login')) %>
+ <th></th>
+</tr> +<% for user in @users %> + <tr style="background-color:#CEE1ED">
+ <td><%= link_to user.login, :action => 'edit', :id => user %></td>
+ <td><%= user.firstname %></td>
+ <td><%= user.lastname %></td>
+ <td><%= user.mail %></td>
+ <td align="center"><%= image_tag 'true' if user.admin? %></td>
+ <td align="center"><%= image_tag 'locked' if user.locked? %></td>
+ <td align="center"><%= format_time(user.created_on) %></td>
+ <td align="center"><%= format_time(user.last_login_on) unless user.last_login_on.nil? %></td>
+ <td align="center">
+ <%= start_form_tag :action => 'edit', :id => user %>
+ <% if user.locked? %>
+ <%= hidden_field_tag 'user[locked]', false %>
+ <%= submit_tag _('Unlock'), :class => "button-small" %>
+ <% else %>
+ <%= hidden_field_tag 'user[locked]', true %>
+ <%= submit_tag _('Lock'), :class => "button-small" %>
+ <% end %>
+ <%= end_form_tag %>
+ </td> + </tr> +<% end %> +</table> +
+<p><%= pagination_links_full @user_pages %>
+[ <%= @user_pages.current.first_item %> - <%= @user_pages.current.last_item %> / <%= @user_count %> ]
+</p>
+ +<p> +<%= link_to '» ' + _('New user'), :action => 'add' %> +</p>
\ No newline at end of file diff --git a/redmine/app/views/versions/_form.rhtml b/redmine/app/views/versions/_form.rhtml new file mode 100644 index 000000000..189e106cc --- /dev/null +++ b/redmine/app/views/versions/_form.rhtml @@ -0,0 +1,13 @@ +<%= error_messages_for 'version' %> + +<!--[form:version]--> +<p><label for="version_name"><%=_('Version')%></label><br/> +<%= text_field 'version', 'name', :size => 20 %></p> + +<p><label for="version_descr"><%=_('Description')%></label><br/> +<%= text_field 'version', 'descr', :size => 60 %></p> +
+<p><label for="version_date"><%=_('Date')%></label><br/> +<%= date_select 'version', 'date' %></p> +<!--[eoform:version]--> + diff --git a/redmine/app/views/versions/edit.rhtml b/redmine/app/views/versions/edit.rhtml new file mode 100644 index 000000000..6db4daa00 --- /dev/null +++ b/redmine/app/views/versions/edit.rhtml @@ -0,0 +1,8 @@ +<h2><%=_('Version')%></h2> + +<%= start_form_tag :action => 'edit', :id => @version %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> + + diff --git a/redmine/app/views/welcome/index.rhtml b/redmine/app/views/welcome/index.rhtml new file mode 100644 index 000000000..cbffa82ed --- /dev/null +++ b/redmine/app/views/welcome/index.rhtml @@ -0,0 +1,30 @@ +<div class="splitcontentleft">
+ <h2><%=_('Welcome')%> !</h2>
+
+ <div class="box">
+ <h3>Latest news</h3> + <% for news in @news %>
+ <p>
+ <b><%= news.title %></b> (<%= link_to_user news.author %> <%= format_time(news.created_on) %> - <%= news.project.name %>)<br />
+ <%= news.shortdescr %><br />
+ [<%= link_to 'Read...', :controller => 'news', :action => 'show', :id => news %>]
+ </p>
+ <hr />
+ <% end %>
+ </div>
+</div>
+
+<div class="splitcontentright">
+ <div class="box">
+ <h3>Latest projects</h3>
+ <ul> + <% for project in @projects %>
+ <li>
+ <%= link_to project.name, :controller => 'projects', :action => 'show', :id => project %> (added <%= format_time(project.created_on) %>)<br />
+ <%= project.descr %>
+ </li>
+ <% end %>
+ </ul>
+ </div>
+
+</div>
diff --git a/redmine/config/boot.rb b/redmine/config/boot.rb new file mode 100644 index 000000000..9fcd50fe3 --- /dev/null +++ b/redmine/config/boot.rb @@ -0,0 +1,19 @@ +# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb + +unless defined?(RAILS_ROOT) + root_path = File.join(File.dirname(__FILE__), '..') + unless RUBY_PLATFORM =~ /mswin32/ + require 'pathname' + root_path = Pathname.new(root_path).cleanpath(true).to_s + end + RAILS_ROOT = root_path +end + +if File.directory?("#{RAILS_ROOT}/vendor/rails") + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" +else + require 'rubygems' + require 'initializer' +end + +Rails::Initializer.run(:set_load_path) diff --git a/redmine/config/database.yml b/redmine/config/database.yml new file mode 100644 index 000000000..1702510d0 --- /dev/null +++ b/redmine/config/database.yml @@ -0,0 +1,32 @@ +# MySQL (default setup). Versions 4.1 and 5.0 are recommended. +# +# Get the fast C bindings: +# gem install mysql +# (on OS X: gem install mysql -- --include=/usr/local/lib) +# And be sure to use new-style password hashing: +# http://dev.mysql.com/doc/refman/5.0/en/old-client.html +development: + adapter: mysql + database: redmine_development
+ host: localhost + username: root + password: +
+test: + adapter: mysql + database: redmine_test
+ host: localhost + username: root + password: +
+demo: + adapter: sqlite3
+ dbfile: db/redmine_demo.db
+ +production: + adapter: mysql + database: redmine
+ host: localhost + username: root + password: +
\ No newline at end of file diff --git a/redmine/config/environment.rb b/redmine/config/environment.rb new file mode 100644 index 000000000..de12e7c34 --- /dev/null +++ b/redmine/config/environment.rb @@ -0,0 +1,85 @@ +# Be sure to restart your web server when you modify this file. + +# Uncomment below to force Rails into production mode when +# you don't control web/app server and can't set it the proper way +# ENV['RAILS_ENV'] ||= 'production'
+ +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence those specified here + + # Skip frameworks you're not going to use + # config.frameworks -= [ :action_web_service, :action_mailer ] + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/extras ) + + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug + + # Use the database for sessions instead of the file system + # (create the session table with 'rake create_sessions_table') + # config.action_controller.session_store = :active_record_store + + # Enable page/fragment caching by setting a file-based store + # (remember to create the caching directory and make it readable to the application) + # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache" + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector + + # Make Active Record use UTC-base instead of local time + # config.active_record.default_timezone = :utc + + # Use Active Record's schema dumper instead of SQL when creating the test database + # (enables use of different database adapters for development and test environments) + # config.active_record.schema_format = :ruby + + # See Rails::Configuration for more options
+
+ # SMTP server configuration
+ config.action_mailer.server_settings = {
+ :address => "127.0.0.1",
+ :port => 25,
+ :domain => "somenet.foo",
+ :authentication => :login,
+ :user_name => "redmine",
+ :password => "redmine",
+ }
+
+ config.action_mailer.perform_deliveries = true
+
+ # Tell ActionMailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + #config.action_mailer.delivery_method = :test
+ config.action_mailer.delivery_method = :smtp +end + +# Add new inflection rules using the following format +# (all these examples are active by default): +# Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# Include your application configuration below
+
+# application name
+RDM_APP_NAME = "redMine"
+# application version
+RDM_APP_VERSION = "0.1.0"
+# application host name
+RDM_HOST_NAME = "somenet.foo"
+# file storage path
+RDM_STORAGE_PATH = "#{RAILS_ROOT}/files"
+# if RDM_LOGIN_REQUIRED is set to true, login is required to access the application
+RDM_LOGIN_REQUIRED = false
+# default langage
+RDM_DEFAULT_LANG = 'en'
+
diff --git a/redmine/config/environments/demo.rb b/redmine/config/environments/demo.rb new file mode 100644 index 000000000..52aef32f1 --- /dev/null +++ b/redmine/config/environments/demo.rb @@ -0,0 +1,21 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new +config.log_level = :info + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" +
+# Disable mail delivery +config.action_mailer.perform_deliveries = false
+config.action_mailer.raise_delivery_errors = false +
diff --git a/redmine/config/environments/development.rb b/redmine/config/environments/development.rb new file mode 100644 index 000000000..04b779200 --- /dev/null +++ b/redmine/config/environments/development.rb @@ -0,0 +1,19 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Enable the breakpoint server that script/breakpointer connects to +config.breakpoint_server = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false diff --git a/redmine/config/environments/production.rb b/redmine/config/environments/production.rb new file mode 100644 index 000000000..4cd4e086b --- /dev/null +++ b/redmine/config/environments/production.rb @@ -0,0 +1,20 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" +
+# Disable delivery errors if you bad email addresses should just be ignored +config.action_mailer.raise_delivery_errors = false +
diff --git a/redmine/config/environments/test.rb b/redmine/config/environments/test.rb new file mode 100644 index 000000000..0b34ef19a --- /dev/null +++ b/redmine/config/environments/test.rb @@ -0,0 +1,15 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + diff --git a/redmine/config/help.yml b/redmine/config/help.yml new file mode 100644 index 000000000..a48798e03 --- /dev/null +++ b/redmine/config/help.yml @@ -0,0 +1,21 @@ +# administration
+admin:
+ index: administration.html
+ mail_options: administration.html#mail_notifications
+ info: administration.html#app_info
+users:
+ index: administration.html#users
+roles:
+ index: administration.html#roles
+ workflow: administration.html#workflow
+trackers:
+ index: administration.html#trackers
+issue_statuses:
+ index: administration.html#issue_statuses
+
+# projects
+projects:
+ add: projects.html#settings
+
+
+# issues
\ No newline at end of file diff --git a/redmine/config/routes.rb b/redmine/config/routes.rb new file mode 100644 index 000000000..2559159f1 --- /dev/null +++ b/redmine/config/routes.rb @@ -0,0 +1,24 @@ +ActionController::Routing::Routes.draw do |map| + # Add your own custom routes here. + # The priority is based upon order of creation: first created -> highest priority. + + # Here's a sample route: + # map.connect 'products/:id', :controller => 'catalog', :action => 'view' + # Keep in mind you can assign values other than :controller and :action + + # You can have the root of your site routed by hooking up '' + # -- just remember to delete public/index.html. + map.connect '', :controller => "welcome" +
+ map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
+ map.connect 'help/:ctrl/:page', :controller => 'help'
+ map.connect ':controller/:action/:id/:sort_key/:sort_order'
+ + # Allow downloading Web Service WSDL as a file with an extension + # instead of a file named 'wsdl' + map.connect ':controller/service.wsdl', :action => 'wsdl' +
+ + # Install the default route as the lowest priority. + map.connect ':controller/:action/:id' +end diff --git a/redmine/db/migrate/001_setup.rb b/redmine/db/migrate/001_setup.rb new file mode 100644 index 000000000..e6288f61d --- /dev/null +++ b/redmine/db/migrate/001_setup.rb @@ -0,0 +1,254 @@ +class Setup < ActiveRecord::Migration + def self.up
+ create_table "attachments", :force => true do |t|
+ t.column "container_id", :integer, :default => 0, :null => false
+ t.column "container_type", :string, :limit => 30, :default => "", :null => false
+ t.column "filename", :string, :default => "", :null => false
+ t.column "disk_filename", :string, :default => "", :null => false
+ t.column "size", :integer, :default => 0, :null => false
+ t.column "content_type", :string, :limit => 60, :default => "", :null => false
+ t.column "digest", :string, :limit => 40, :default => "", :null => false
+ t.column "downloads", :integer, :default => 0, :null => false
+ t.column "author_id", :integer, :default => 0, :null => false
+ t.column "created_on", :timestamp
+ end
+
+ create_table "custom_fields", :force => true do |t|
+ t.column "name", :string, :limit => 30, :default => "", :null => false
+ t.column "typ", :integer, :limit => 6, :default => 0, :null => false
+ t.column "is_required", :boolean, :default => false, :null => false
+ t.column "is_for_all", :boolean, :default => false, :null => false
+ t.column "possible_values", :text, :default => "", :null => false
+ t.column "regexp", :string, :default => "", :null => false
+ t.column "min_length", :integer, :limit => 4, :default => 0, :null => false
+ t.column "max_length", :integer, :limit => 4, :default => 0, :null => false
+ end
+
+ create_table "custom_fields_projects", :id => false, :force => true do |t|
+ t.column "custom_field_id", :integer, :default => 0, :null => false
+ t.column "project_id", :integer, :default => 0, :null => false
+ end
+
+ create_table "custom_values", :force => true do |t|
+ t.column "issue_id", :integer, :default => 0, :null => false
+ t.column "custom_field_id", :integer, :default => 0, :null => false
+ t.column "value", :text, :default => "", :null => false
+ end
+
+ add_index "custom_values", ["issue_id"], :name => "custom_values_issue_id"
+
+ create_table "documents", :force => true do |t|
+ t.column "project_id", :integer, :default => 0, :null => false
+ t.column "category_id", :integer, :default => 0, :null => false
+ t.column "title", :string, :limit => 60, :default => "", :null => false
+ t.column "descr", :text, :default => "", :null => false
+ t.column "created_on", :timestamp
+ end
+
+ create_table "enumerations", :force => true do |t|
+ t.column "opt", :string, :limit => 4, :default => "", :null => false
+ t.column "name", :string, :limit => 30, :default => "", :null => false
+ end
+
+ create_table "issue_categories", :force => true do |t|
+ t.column "project_id", :integer, :default => 0, :null => false
+ t.column "name", :string, :limit => 30, :default => "", :null => false
+ end
+
+ create_table "issue_histories", :force => true do |t|
+ t.column "issue_id", :integer, :default => 0, :null => false
+ t.column "status_id", :integer, :default => 0, :null => false
+ t.column "author_id", :integer, :default => 0, :null => false
+ t.column "notes", :text, :default => "", :null => false
+ t.column "created_on", :timestamp
+ end
+
+ add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id"
+
+ create_table "issue_statuses", :force => true do |t|
+ t.column "name", :string, :limit => 30, :default => "", :null => false
+ t.column "is_closed", :boolean, :default => false, :null => false
+ t.column "is_default", :boolean, :default => false, :null => false
+ t.column "html_color", :string, :limit => 6, :default => "FFFFFF", :null => false
+ end
+
+ create_table "issues", :force => true do |t|
+ t.column "tracker_id", :integer, :default => 0, :null => false
+ t.column "project_id", :integer, :default => 0, :null => false
+ t.column "subject", :string, :default => "", :null => false
+ t.column "descr", :text, :default => "", :null => false
+ t.column "category_id", :integer
+ t.column "status_id", :integer, :default => 0, :null => false
+ t.column "assigned_to_id", :integer
+ t.column "priority_id", :integer, :default => 0, :null => false
+ t.column "fixed_version_id", :integer
+ t.column "author_id", :integer, :default => 0, :null => false
+ t.column "created_on", :timestamp
+ t.column "updated_on", :timestamp
+ end
+
+ add_index "issues", ["project_id"], :name => "issues_project_id"
+
+ create_table "members", :force => true do |t|
+ t.column "user_id", :integer, :default => 0, :null => false
+ t.column "project_id", :integer, :default => 0, :null => false
+ t.column "role_id", :integer, :default => 0, :null => false
+ t.column "created_on", :timestamp
+ end
+
+ create_table "news", :force => true do |t|
+ t.column "project_id", :integer
+ t.column "title", :string, :limit => 60, :default => "", :null => false
+ t.column "shortdescr", :string, :default => "", :null => false
+ t.column "descr", :text, :default => "", :null => false
+ t.column "author_id", :integer, :default => 0, :null => false
+ t.column "created_on", :timestamp
+ end
+
+ create_table "permissions", :force => true do |t|
+ t.column "controller", :string, :limit => 30, :default => "", :null => false
+ t.column "action", :string, :limit => 30, :default => "", :null => false
+ t.column "descr", :string, :limit => 60, :default => "", :null => false
+ t.column "public", :boolean, :default => false, :null => false
+ t.column "sort", :integer, :default => 0, :null => false
+ t.column "mail_option", :boolean, :default => false, :null => false
+ t.column "mail_enabled", :boolean, :default => false, :null => false
+ end
+
+ create_table "permissions_roles", :id => false, :force => true do |t|
+ t.column "permission_id", :integer, :default => 0, :null => false
+ t.column "role_id", :integer, :default => 0, :null => false
+ end
+
+ add_index "permissions_roles", ["role_id"], :name => "permissions_roles_role_id"
+
+ create_table "projects", :force => true do |t|
+ t.column "name", :string, :limit => 30, :default => "", :null => false
+ t.column "descr", :string, :default => "", :null => false
+ t.column "homepage", :string, :limit => 60, :default => "", :null => false
+ t.column "public", :boolean, :default => true, :null => false
+ t.column "created_on", :timestamp
+ t.column "updated_on", :timestamp
+ end
+
+ create_table "roles", :force => true do |t|
+ t.column "name", :string, :limit => 30, :default => "", :null => false
+ end
+
+ create_table "trackers", :force => true do |t|
+ t.column "name", :string, :limit => 30, :default => "", :null => false
+ t.column "is_in_chlog", :boolean, :default => false, :null => false
+ end
+
+ create_table "users", :force => true do |t|
+ t.column "login", :string, :limit => 30, :default => "", :null => false
+ t.column "hashed_password", :string, :limit => 40, :default => "", :null => false
+ t.column "firstname", :string, :limit => 30, :default => "", :null => false
+ t.column "lastname", :string, :limit => 30, :default => "", :null => false
+ t.column "mail", :string, :limit => 60, :default => "", :null => false
+ t.column "mail_notification", :boolean, :default => true, :null => false
+ t.column "admin", :boolean, :default => false, :null => false
+ t.column "locked", :boolean, :default => false, :null => false
+ t.column "last_login_on", :datetime
+ t.column "language", :string, :limit => 2, :default => "", :null => false
+ t.column "created_on", :timestamp
+ t.column "updated_on", :timestamp
+ end
+
+ create_table "versions", :force => true do |t|
+ t.column "project_id", :integer, :default => 0, :null => false
+ t.column "name", :string, :limit => 30, :default => "", :null => false
+ t.column "descr", :string, :default => "", :null => false
+ t.column "date", :date, :null => false
+ t.column "created_on", :timestamp
+ t.column "updated_on", :timestamp
+ end
+
+ create_table "workflows", :force => true do |t|
+ t.column "tracker_id", :integer, :default => 0, :null => false
+ t.column "old_status_id", :integer, :default => 0, :null => false
+ t.column "new_status_id", :integer, :default => 0, :null => false
+ t.column "role_id", :integer, :default => 0, :null => false
+ end
+
+ # project
+ Permission.create :controller => "projects", :action => "show", :descr => "Overview", :sort => 100, :public => true
+ Permission.create :controller => "projects", :action => "changelog", :descr => "View change log", :sort => 105, :public => true
+ Permission.create :controller => "reports", :action => "issue_report", :descr => "View reports", :sort => 110, :public => true
+ Permission.create :controller => "projects", :action => "settings", :descr => "Settings", :sort => 150
+ Permission.create :controller => "projects", :action => "edit", :descr => "Edit", :sort => 151
+ # members
+ Permission.create :controller => "projects", :action => "list_members", :descr => "View list", :sort => 200, :public => true
+ Permission.create :controller => "projects", :action => "add_member", :descr => "New member", :sort => 220
+ Permission.create :controller => "members", :action => "edit", :descr => "Edit", :sort => 221
+ Permission.create :controller => "members", :action => "destroy", :descr => "Delete", :sort => 222
+ # versions
+ Permission.create :controller => "projects", :action => "add_version", :descr => "New version", :sort => 320
+ Permission.create :controller => "versions", :action => "edit", :descr => "Edit", :sort => 321
+ Permission.create :controller => "versions", :action => "destroy", :descr => "Delete", :sort => 322
+ # issue categories
+ Permission.create :controller => "projects", :action => "add_issue_category", :descr => "New issue category", :sort => 420
+ Permission.create :controller => "issue_categories", :action => "edit", :descr => "Edit", :sort => 421
+ Permission.create :controller => "issue_categories", :action => "destroy", :descr => "Delete", :sort => 422
+ # issues
+ Permission.create :controller => "projects", :action => "list_issues", :descr => "View list", :sort => 1000, :public => true
+ Permission.create :controller => "issues", :action => "show", :descr => "View", :sort => 1005, :public => true
+ Permission.create :controller => "issues", :action => "download", :descr => "Download file", :sort => 1010, :public => true
+ Permission.create :controller => "projects", :action => "add_issue", :descr => "Report an issue", :sort => 1050, :mail_option => 1, :mail_enabled => 1
+ Permission.create :controller => "issues", :action => "edit", :descr => "Edit", :sort => 1055
+ Permission.create :controller => "issues", :action => "change_status", :descr => "Change status", :sort => 1060, :mail_option => 1, :mail_enabled => 1
+ Permission.create :controller => "issues", :action => "destroy", :descr => "Delete", :sort => 1065
+ Permission.create :controller => "issues", :action => "add_attachment", :descr => "Add file", :sort => 1070
+ Permission.create :controller => "issues", :action => "destroy_attachment", :descr => "Delete file", :sort => 1075
+ # news
+ Permission.create :controller => "projects", :action => "list_news", :descr => "View list", :sort => 1100, :public => true
+ Permission.create :controller => "news", :action => "show", :descr => "View", :sort => 1101, :public => true
+ Permission.create :controller => "projects", :action => "add_news", :descr => "Add", :sort => 1120
+ Permission.create :controller => "news", :action => "edit", :descr => "Edit", :sort => 1121
+ Permission.create :controller => "news", :action => "destroy", :descr => "Delete", :sort => 1122
+ # documents + Permission.create :controller => "projects", :action => "list_documents", :descr => "View list", :sort => 1200, :public => true
+ Permission.create :controller => "documents", :action => "show", :descr => "View", :sort => 1201, :public => true
+ Permission.create :controller => "documents", :action => "download", :descr => "Download", :sort => 1202, :public => true
+ Permission.create :controller => "projects", :action => "add_document", :descr => "Add", :sort => 1220
+ Permission.create :controller => "documents", :action => "edit", :descr => "Edit", :sort => 1221
+ Permission.create :controller => "documents", :action => "destroy", :descr => "Delete", :sort => 1222
+ Permission.create :controller => "documents", :action => "add_attachment", :descr => "Add file", :sort => 1223
+ Permission.create :controller => "documents", :action => "destroy_attachment", :descr => "Delete file", :sort => 1224
+ # files
+ Permission.create :controller => "projects", :action => "list_files", :descr => "View list", :sort => 1300, :public => true
+ Permission.create :controller => "versions", :action => "download", :descr => "Download", :sort => 1301, :public => true
+ Permission.create :controller => "projects", :action => "add_file", :descr => "Add", :sort => 1320
+ Permission.create :controller => "versions", :action => "destroy_file", :descr => "Delete", :sort => 1322
+
+ # create default administrator account
+ user = User.create :login => "admin", :password => "admin", :firstname => "redMine", :lastname => "Admin", :mail => "admin@somenet.foo", :mail_notification => true, :language => "en"
+ user.admin = true
+ user.save
+
+
+ end + + def self.down
+ drop_table :attachments
+ drop_table :custom_fields
+ drop_table :custom_fields_projects
+ drop_table :custom_values
+ drop_table :documents
+ drop_table :enumerations
+ drop_table :issue_categories
+ drop_table :issue_histories
+ drop_table :issue_statuses
+ drop_table :issues
+ drop_table :members
+ drop_table :news
+ drop_table :permissions
+ drop_table :permissions_roles
+ drop_table :projects
+ drop_table :roles
+ drop_table :trackers
+ drop_table :users
+ drop_table :versions
+ drop_table :workflows + end +end diff --git a/redmine/db/migrate/002_default_configuration.rb b/redmine/db/migrate/002_default_configuration.rb new file mode 100644 index 000000000..8f851dfbc --- /dev/null +++ b/redmine/db/migrate/002_default_configuration.rb @@ -0,0 +1,44 @@ +class DefaultConfiguration < ActiveRecord::Migration + def self.up
+ # roles
+ r = Role.create(:name => "Manager")
+ r.permissions = Permission.find(:all)
+ r = Role.create :name => "Developer"
+ 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])
+ r = Role.create :name => "Reporter"
+ r.permissions = Permission.find([1, 2, 3, 6, 16, 17, 18, 19, 20, 21, 23, 25, 26, 30, 31, 32, 38, 39])
+ # trackers
+ Tracker.create(:name => "Bug", :is_in_chlog => true)
+ Tracker.create(:name => "Feature request", :is_in_chlog => true)
+ Tracker.create(:name => "Support request", :is_in_chlog => false)
+ # issue statuses
+ IssueStatus.create(:name => "New", :is_closed => false, :is_default => true, :html_color => 'F98787')
+ IssueStatus.create(:name => "Assigned", :is_closed => false, :is_default => false, :html_color => 'C0C0FF') + IssueStatus.create(:name => "Resolved", :is_closed => false, :is_default => false, :html_color => '88E0B3') + IssueStatus.create(:name => "Feedback", :is_closed => false, :is_default => false, :html_color => 'F3A4F4') + IssueStatus.create(:name => "Closed", :is_closed => true, :is_default => false, :html_color => 'DBDBDB') + IssueStatus.create(:name => "Rejected", :is_closed => true, :is_default => false, :html_color => 'F5C28B')
+ # workflow
+ Tracker.find(:all).each { |t|
+ Role.find(:all).each { |r|
+ IssueStatus.find(:all).each { |os|
+ IssueStatus.find(:all).each { |ns|
+ Workflow.create(:tracker_id => t.id, :role_id => r.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
+ }
+ }
+ }
+ }
+ # enumeartions
+ Enumeration.create(:opt => "DCAT", :name => 'Uncategorized')
+ Enumeration.create(:opt => "DCAT", :name => 'User documentation')
+ Enumeration.create(:opt => "DCAT", :name => 'Technical documentation')
+ Enumeration.create(:opt => "IPRI", :name => 'Low')
+ Enumeration.create(:opt => "IPRI", :name => 'Normal')
+ Enumeration.create(:opt => "IPRI", :name => 'High')
+ Enumeration.create(:opt => "IPRI", :name => 'Urgent')
+ Enumeration.create(:opt => "IPRI", :name => 'Immediate') + end + + def self.down + end +end diff --git a/redmine/db/redmine_demo.db b/redmine/db/redmine_demo.db Binary files differnew file mode 100644 index 000000000..6a19ec527 --- /dev/null +++ b/redmine/db/redmine_demo.db diff --git a/redmine/doc/CHANGELOG b/redmine/doc/CHANGELOG new file mode 100644 index 000000000..8db6fc29b --- /dev/null +++ b/redmine/doc/CHANGELOG @@ -0,0 +1,17 @@ +== redMine changelog
+
+redMine - project management software
+Copyright (C) 2006 Jean-Philippe Lang
+http://redmine.sourceforge.net/
+
+
+== 06/25/2006 - v0.1.0
+
+* multiple users/multiple projects
+* role based access control
+* issue tracking system
+* fully customizable workflow
+* documents/files repository
+* email notifications on issue creation and update
+* multilanguage support (except for error messages):english, french, spanish
+* online manual in french (unfinished)
\ No newline at end of file diff --git a/redmine/doc/COPYING b/redmine/doc/COPYING new file mode 100644 index 000000000..82fa1daad --- /dev/null +++ b/redmine/doc/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/redmine/doc/INSTALL b/redmine/doc/INSTALL new file mode 100644 index 000000000..72a993687 --- /dev/null +++ b/redmine/doc/INSTALL @@ -0,0 +1,61 @@ +== redMine installation
+
+redMine - project management software
+Copyright (C) 2006 Jean-Philippe Lang
+http://redmine.sourceforge.net/
+
+
+== Requirements
+
+* Ruby on Rails 1.1
+* Any database supported by Rails (developped using MySQL 5)
+* (recommended) Apache/Lighttpd with FCGI support
+
+
+== Installation
+
+1. Uncompress program archive:
+ tar zxvf <filename>
+
+2. Create an empty database: "redmine" for example
+
+3. Configure database parameters in config/database.yml
+ for "production" environment
+
+4. Create database structure. Under application main directory:
+ rake migrate RAILS_ENV="production"
+ It will create tables and default configuration data
+
+5. Test the installation by running WEBrick web server:
+ ruby script/server -e production
+
+ Once WEBrick has started, point your browser to http://localhost:3000/
+ You should now see the application welcome page
+
+6. Use default administrator account to log in:
+ login: admin
+ password: admin
+
+7. Setup Apache or Lighttpd with fastcgi for best performance.
+
+
+== Configuration
+
+You can setup a few things in config/environment.rb:
+Don't forget to restart the application after any change.
+
+
+config.action_mailer.server_settings: SMTP server configuration
+config.action_mailer.perform_deliveries: set to false to disable mail delivering
+
+RDM_HOST_NAME: hostname used to provide urls in notification mails
+
+RDM_STORAGE_PATH: path for all attachments storage (default: "#{RAILS_ROOT}/files")
+ "#{RAILS_ROOT}/" represents application main directory
+
+RDM_LOGIN_REQUIRED: set to true if you want to force users to login to access
+ any part of the application (default: false)
+
+RDM_DEFAULT_LANG: default language for anonymous users: 'en' (default), 'es', 'fr' available
+
+
diff --git a/redmine/doc/README b/redmine/doc/README new file mode 100644 index 000000000..1c794e45b --- /dev/null +++ b/redmine/doc/README @@ -0,0 +1,49 @@ +== redMine
+
+redMine - project management software
+Copyright (C) 2006 Jean-Philippe Lang
+http://redmine.sourceforge.net/
+
+== License
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+
+== Main features
+
+redMine is a project management software written using Ruby on Rails.
+
+* multiple users/projects
+* fully customizable role based access control
+* issue tracking system
+* fully customizable workflow
+* documents/files repository
+* email notifications
+* multilanguage support
+
+
+== Versioning
+
+redMine versioning scheme is major.minor.revision
+Versions before 1.0.0 must be considered as beta versions and upgrading support
+may not be provided for these versions.
+
+
+== Credits
+
+* Jean-Francois Boutier (spanish translation)
+* Andreas Viklund (open source XHTML layout, http://andreasviklund.com/)
+
+
diff --git a/redmine/files/delete.me b/redmine/files/delete.me new file mode 100644 index 000000000..18beddaa8 --- /dev/null +++ b/redmine/files/delete.me @@ -0,0 +1 @@ +default directory for uploaded files
\ No newline at end of file diff --git a/redmine/lang/en_US.rb b/redmine/lang/en_US.rb new file mode 100644 index 000000000..aa402421e --- /dev/null +++ b/redmine/lang/en_US.rb @@ -0,0 +1,4 @@ +Localization.define('en', 'English') do |l|
+ l.store '(date)', lambda { |t| t.strftime('%m/%d/%Y') }
+ l.store '(time)', lambda { |t| t.strftime('%m/%d/%Y %I:%M%p') }
+end
\ No newline at end of file diff --git a/redmine/lang/es_ES.rb b/redmine/lang/es_ES.rb new file mode 100644 index 000000000..ef15f9c53 --- /dev/null +++ b/redmine/lang/es_ES.rb @@ -0,0 +1,315 @@ +Localization.define('es', 'Español') do |l|
+
+ # trackers
+ l.store 'Bug', 'AnomalÃa'
+ l.store 'Feature request', 'Evolución'
+ l.store 'Support request', 'Asistencia'
+ # issue statuses
+ l.store 'New', 'Nuevo'
+ l.store 'Assigned', 'Asignada'
+ l.store 'Resolved', 'Resuelta'
+ l.store 'Closed', 'Cerrada'
+ l.store 'Rejected', 'Rechazada'
+ l.store 'Feedback', 'Comentario'
+
+ # issue priorities
+ l.store 'Issue priorities', 'Prioridad de las peticiones'
+ l.store 'Low', 'Bajo'
+ l.store 'Normal', 'Normal'
+ l.store 'High', 'Alto'
+ l.store 'Urgent', 'Urgente'
+ l.store 'Immediate', 'Ahora'
+ # document categories
+ l.store 'Document categories', 'CategorÃas del documento'
+ l.store 'Uncategorized', 'Sin categorÃas '
+ l.store 'User documentation', 'Documentación del usuario'
+ l.store 'Technical documentation', 'Documentación tecnica'
+ # dates
+ l.store '(date)', lambda { |t| t.strftime('%d/%m/%Y') }
+ l.store '(time)', lambda { |t| t.strftime('%d/%m/%Y %H:%M') }
+
+ # ./script/../config/../app/views/account/login.rhtml
+
+ # ./script/../config/../app/views/account/my_account.rhtml
+ l.store 'My account', 'Mi cuenta'
+ l.store 'Login', 'Identificador'
+ l.store 'Created on', 'Creado el'
+ l.store 'Last update', 'Actualizado'
+ l.store 'Information', 'Informaciones'
+ l.store 'Firstname', 'Nombre'
+ l.store 'Lastname', 'Apellido'
+ l.store 'Mail', 'Mail'
+ l.store 'Language', 'Lengua'
+ l.store 'Mail notifications', 'Notificación por mail'
+ l.store 'Save', 'Validar'
+ l.store 'Password', 'Contraseña'
+ l.store 'New password', 'Nueva contraseña'
+ l.store 'Confirmation', 'Confirmación'
+
+ # ./script/../config/../app/views/account/my_page.rhtml
+ l.store 'My page', 'Mi página'
+ l.store 'Welcome', 'Bienvenido'
+ l.store 'Last login', 'Última conexión'
+ l.store 'Reported issues', 'Peticiones registradas'
+ l.store 'Assigned to me', 'Peticiones que me están asignadas'
+
+ # ./script/../config/../app/views/account/show.rhtml
+ l.store 'Registered on', 'Inscrito el'
+ l.store 'Projects', 'Proyectos'
+ l.store 'Activity', 'Actividad'
+
+ # ./script/../config/../app/views/admin/index.rhtml
+ l.store 'Administration', 'Administración'
+ l.store 'Users', 'Usuarios'
+ l.store 'Roles and permissions', 'Papeles y permisos'
+ l.store 'Trackers', 'Trackers'
+ l.store 'Custom fields', 'Campos personalizados'
+ l.store 'Issue Statuses', 'Estatutos de las peticiones'
+ l.store 'Workflow', 'Workflow'
+ l.store 'Enumerations', 'Listas de valores'
+
+ # ./script/../config/../app/views/admin/info.rhtml
+ l.store 'Version', 'Versión'
+ l.store 'Database', 'Base de datos'
+
+ # ./script/../config/../app/views/admin/mail_options.rhtml
+ 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.'
+ l.store 'Check all', 'Seleccionar todo'
+ l.store 'Uncheck all', 'No seleccionar nada'
+
+ # ./script/../config/../app/views/admin/projects.rhtml
+ l.store 'Project', 'Proyecto'
+ l.store 'Description', 'Descripción'
+ l.store 'Public', 'Público'
+ l.store 'Delete', 'Suprimir'
+ l.store 'Previous', 'Precedente'
+ l.store 'Next', 'Próximo'
+
+ # ./script/../config/../app/views/custom_fields/edit.rhtml
+ l.store 'Custom field', 'Campo personalizado'
+
+ # ./script/../config/../app/views/custom_fields/list.rhtml
+ l.store 'Name', 'Nombre'
+ l.store 'Type', 'Tipo'
+ l.store 'Required', 'Obligatorio'
+ l.store 'For all projects', 'Para todos los proyectos'
+ l.store 'Used by', 'Utilizado por'
+
+ # ./script/../config/../app/views/custom_fields/new.rhtml
+ l.store 'New custom field', 'Nuevo campo personalizado'
+ l.store 'Create', 'Crear'
+
+ # ./script/../config/../app/views/custom_fields/_form.rhtml
+ l.store '0 means no restriction', '0 para ninguna restricción'
+ l.store 'Regular expression pattern', 'Expresión regular'
+ l.store 'Possible values', 'Valores posibles'
+
+ # ./script/../config/../app/views/documents/edit.rhtml
+ l.store 'Document', 'Documento'
+
+ # ./script/../config/../app/views/documents/show.rhtml
+ l.store 'Category', 'CategorÃa'
+ l.store 'Edit', 'Modificar'
+ l.store 'download', 'Telecarga'
+ l.store 'Add file', 'Añadir el fichero'
+ l.store 'Add', 'Añadir'
+
+ # ./script/../config/../app/views/documents/_form.rhtml
+ l.store 'Title', 'TÃtulo'
+
+ # ./script/../config/../app/views/enumerations/edit.rhtml
+
+ # ./script/../config/../app/views/enumerations/list.rhtml
+
+ # ./script/../config/../app/views/enumerations/new.rhtml
+ l.store 'New enumeration', 'Nuevo valor'
+
+ # ./script/../config/../app/views/enumerations/_form.rhtml
+
+ # ./script/../config/../app/views/issues/change_status.rhtml
+ l.store 'Issue', 'Petición'
+ l.store 'New status', 'Nuevo estatuto'
+ l.store 'Assigned to', 'Asignado a'
+ l.store 'Fixed in version', 'Versión corregida'
+ l.store 'Notes', 'Anotación'
+
+ # ./script/../config/../app/views/issues/edit.rhtml
+ l.store 'Status', 'Estatuto'
+ l.store 'Tracker', 'Tracker'
+ l.store 'Priority', 'Prioridad'
+ l.store 'Subject', 'Tema'
+
+ # ./script/../config/../app/views/issues/show.rhtml
+ l.store 'Author', 'Autor'
+ l.store 'Change status', 'Cambiar el estatuto'
+ l.store 'History', 'Histórico'
+ l.store 'Attachments', 'Ficheros'
+ l.store 'Update...', 'Actualizar...'
+
+ # ./script/../config/../app/views/issues/_list_simple.rhtml
+ l.store 'No issue', 'Ninguna petición'
+
+ # ./script/../config/../app/views/issue_categories/edit.rhtml
+
+ # ./script/../config/../app/views/issue_categories/_form.rhtml
+
+ # ./script/../config/../app/views/issue_statuses/edit.rhtml
+ l.store 'Issue status', 'Estatuto de petición'
+
+ # ./script/../config/../app/views/issue_statuses/list.rhtml
+ l.store 'Issue statuses', 'Estatutos de la petición'
+ l.store 'Default status', 'Estatuto por defecto'
+ l.store 'Issue closed', 'Petición resuelta'
+ l.store 'Color', 'Color'
+
+ # ./script/../config/../app/views/issue_statuses/new.rhtml
+ l.store 'New issue status', 'Nuevo estatuto'
+
+ # ./script/../config/../app/views/issue_statuses/_form.rhtml
+
+ # ./script/../config/../app/views/layouts/base.rhtml
+ l.store 'Home', 'Acogida'
+ l.store 'Help', 'Ayuda'
+ l.store 'Log in', 'Conexión'
+ l.store 'Logout', 'Desconexión'
+ l.store 'Overview', 'Vistazo'
+ l.store 'Issues', 'Peticiones'
+ l.store 'Reports', 'Rapports'
+ l.store 'News', 'Noticias'
+ l.store 'Change log', 'Cambios'
+ l.store 'Documents', 'Documentos'
+ l.store 'Members', 'Miembros'
+ l.store 'Files', 'Ficheros'
+ l.store 'Settings', 'Configuración'
+ l.store 'My projects', 'Mis proyectos'
+ l.store 'Logged as', 'Conectado como'
+
+ # ./script/../config/../app/views/mailer/issue_add.rhtml
+
+ # ./script/../config/../app/views/mailer/issue_change_status.rhtml
+
+ # ./script/../config/../app/views/mailer/_issue.rhtml
+
+ # ./script/../config/../app/views/news/edit.rhtml
+
+ # ./script/../config/../app/views/news/show.rhtml
+ l.store 'Summary', 'Resumen'
+ l.store 'By', 'Por'
+ l.store 'Date', 'Fecha'
+
+ # ./script/../config/../app/views/news/_form.rhtml
+
+ # ./script/../config/../app/views/projects/add.rhtml
+ l.store 'New project', 'Nuevo proyecto'
+
+ # ./script/../config/../app/views/projects/add_document.rhtml
+ l.store 'New document', 'Nuevo documento'
+ l.store 'File', 'Fichero'
+
+ # ./script/../config/../app/views/projects/add_issue.rhtml
+ l.store 'New issue', 'Nueva petición'
+ l.store 'Attachment', 'Fichero'
+
+ # ./script/../config/../app/views/projects/add_news.rhtml
+
+ # ./script/../config/../app/views/projects/add_version.rhtml
+ l.store 'New version', 'Nueva versión'
+
+ # ./script/../config/../app/views/projects/changelog.rhtml
+
+ # ./script/../config/../app/views/projects/destroy.rhtml
+ l.store 'Are you sure you want to delete project', '¿ Estás seguro de querer eliminar el proyecto ?'
+
+ # ./script/../config/../app/views/projects/list.rhtml
+ l.store 'Public projects', 'Proyectos publicos'
+
+ # ./script/../config/../app/views/projects/list_documents.rhtml
+ l.store 'Desciption', 'Descripción'
+
+ # ./script/../config/../app/views/projects/list_files.rhtml
+ l.store 'New file', 'Nuevo fichero'
+
+ # ./script/../config/../app/views/projects/list_issues.rhtml
+ l.store 'Apply filter', 'Aplicar'
+ l.store 'Reset', 'Anular'
+ l.store 'Report an issue', 'Nueva petición'
+
+ # ./script/../config/../app/views/projects/list_members.rhtml
+ l.store 'Project members', 'Miembros del proyecto'
+
+ # ./script/../config/../app/views/projects/list_news.rhtml
+ l.store 'Read...', 'Leer...'
+
+ # ./script/../config/../app/views/projects/settings.rhtml
+ l.store 'New member', 'Nuevo miembro'
+ l.store 'Versions', 'Versiónes'
+ l.store 'New version...', 'Nueva versión...'
+ l.store 'Issue categories', 'CategorÃas de las peticiones'
+ l.store 'New category', 'Nueva categorÃa'
+
+ # ./script/../config/../app/views/projects/show.rhtml
+ l.store 'Homepage', 'Sitio web'
+ l.store 'open', 'abierta(s)'
+ l.store 'View all issues', 'Ver todas las peticiones'
+ l.store 'View all news', 'Ver todas las noticias'
+ l.store 'Latest news', 'Últimas noticias'
+
+ # ./script/../config/../app/views/projects/_form.rhtml
+
+ # ./script/../config/../app/views/reports/issue_report.rhtml
+ l.store 'Issues by tracker', 'Peticiones por tracker'
+ l.store 'Issues by priority', 'Peticiones por prioridad'
+ l.store 'Issues by category', 'Peticiones por categorÃa'
+
+ # ./script/../config/../app/views/reports/_simple.rhtml
+ l.store 'Open', 'Abierta'
+ l.store 'Total', 'Total'
+
+ # ./script/../config/../app/views/roles/edit.rhtml
+ l.store 'Role', 'Papel'
+
+ # ./script/../config/../app/views/roles/list.rhtml
+ l.store 'Roles', 'Papeles'
+
+ # ./script/../config/../app/views/roles/new.rhtml
+ l.store 'New role', 'Nuevo papel'
+
+ # ./script/../config/../app/views/roles/workflow.rhtml
+ l.store 'Workflow setup', 'Configuración del workflow'
+ l.store 'Select a workflow to edit', 'Seleccionar un workflow para actualizar'
+ l.store 'New statuses allowed', 'Nuevos estatutos autorizados'
+
+ # ./script/../config/../app/views/roles/_form.rhtml
+ l.store 'Permissions', 'Permisos'
+
+ # ./script/../config/../app/views/trackers/edit.rhtml
+
+ # ./script/../config/../app/views/trackers/list.rhtml
+ l.store 'View issues in change log', 'Consultar las peticiones en el histórico'
+ l.store 'New tracker', 'Nuevo tracker'
+
+ # ./script/../config/../app/views/trackers/new.rhtml
+
+ # ./script/../config/../app/views/trackers/_form.rhtml
+
+ # ./script/../config/../app/views/users/add.rhtml
+ l.store 'New user', 'Nuevo usuario'
+
+ # ./script/../config/../app/views/users/edit.rhtml
+ l.store 'User', 'Usuario'
+
+ # ./script/../config/../app/views/users/list.rhtml
+ l.store 'Admin', 'Admin'
+ l.store 'Locked', 'Cerrado'
+
+ # ./script/../config/../app/views/users/_form.rhtml
+ l.store 'Administrator', 'Administrador'
+
+ # ./script/../config/../app/views/versions/edit.rhtml
+
+ # ./script/../config/../app/views/versions/_form.rhtml
+
+ # ./script/../config/../app/views/welcome/index.rhtml
+
+
+end
diff --git a/redmine/lang/fr_FR.rb b/redmine/lang/fr_FR.rb new file mode 100644 index 000000000..5dc7a59e0 --- /dev/null +++ b/redmine/lang/fr_FR.rb @@ -0,0 +1,316 @@ +Localization.define('fr', 'Français') do |l|
+
+ # trackers
+ l.store 'Bug', 'Anomalie'
+ l.store 'Feature request', 'Evolution'
+ l.store 'Support request', 'Assistance'
+ # issue statuses
+ l.store 'New', 'Nouveau'
+ l.store 'Assigned', 'Assignée'
+ l.store 'Resolved', 'Résolue'
+ l.store 'Closed', 'Fermée'
+ l.store 'Rejected', 'Rejetée'
+ l.store 'Feedback', 'Commentaire'
+
+ # issue priorities
+ l.store 'Issue priorities', 'Priorités des demandes'
+ l.store 'Low', 'Bas'
+ l.store 'Normal', 'Normal'
+ l.store 'High', 'Haut'
+ l.store 'Urgent', 'Urgent'
+ l.store 'Immediate', 'Immédiat'
+ # document categories
+ l.store 'Document categories', 'Catégories de documents'
+ l.store 'Uncategorized', 'Sans catégorie'
+ l.store 'User documentation', 'Documentation utilisateur'
+ l.store 'Technical documentation', 'Documentation technique'
+ # dates
+ l.store '(date)', lambda { |t| t.strftime('%d/%m/%Y') }
+ l.store '(time)', lambda { |t| t.strftime('%d/%m/%Y %H:%M') }
+
+ # ./script/../config/../app/views/account/login.rhtml
+
+ # ./script/../config/../app/views/account/my_account.rhtml
+ l.store 'My account', 'Mon compte'
+ l.store 'Login', 'Identifiant'
+ l.store 'Created on', 'Crée le'
+ l.store 'Last update', 'Mis à jour'
+ l.store 'Information', 'Informations'
+ l.store 'Firstname', 'Prénom'
+ l.store 'Lastname', 'Nom'
+ l.store 'Mail', 'Mail'
+ l.store 'Language', 'Langue'
+ l.store 'Mail notifications', 'Notifications par mail'
+ l.store 'Save', 'Valider'
+ l.store 'Password', 'Mot de passe'
+ l.store 'New password', 'Nouveau mot de passe'
+ l.store 'Confirmation', 'Confirmation'
+
+ # ./script/../config/../app/views/account/my_page.rhtml
+ l.store 'My page', 'Ma page'
+ l.store 'Welcome', 'Bienvenue'
+ l.store 'Last login', 'Dernière connexion'
+ l.store 'Reported issues', 'Demandes soumises'
+ l.store 'Assigned to me', 'Demandes qui me sont assignées'
+
+ # ./script/../config/../app/views/account/show.rhtml
+ l.store 'Registered on', 'Inscrit le'
+ l.store 'Projects', 'Projets'
+ l.store 'Activity', 'Activité'
+
+ # ./script/../config/../app/views/admin/index.rhtml
+ l.store 'Administration', 'Administration'
+ l.store 'Users', 'Utilisateurs'
+ l.store 'Roles and permissions', 'Rôles et permissions'
+ l.store 'Trackers', 'Trackers'
+ l.store 'Custom fields', 'Champs personnalisés'
+ l.store 'Issue Statuses', 'Statuts des demandes'
+ l.store 'Workflow', 'Workflow'
+ l.store 'Enumerations', 'Listes de valeurs'
+
+ # ./script/../config/../app/views/admin/info.rhtml
+ l.store 'Version', 'Version'
+ l.store 'Database', 'Base de données'
+
+ # ./script/../config/../app/views/admin/mail_options.rhtml
+ 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.'
+ l.store 'Check all', 'Cocher tout'
+ l.store 'Uncheck all', 'Décocher tout'
+
+ # ./script/../config/../app/views/admin/projects.rhtml
+ l.store 'Project', 'Projet'
+ l.store 'Description', 'Description'
+ l.store 'Public', 'Public'
+ l.store 'Delete', 'Supprimer'
+ l.store 'Previous', 'Précédent'
+ l.store 'Next', 'Suivant'
+
+ # ./script/../config/../app/views/custom_fields/edit.rhtml
+ l.store 'Custom field', 'Champ personnalisé'
+
+ # ./script/../config/../app/views/custom_fields/list.rhtml
+ l.store 'Name', 'Nom'
+ l.store 'Type', 'Type'
+ l.store 'Required', 'Obligatoire'
+ l.store 'For all projects', 'Pour tous les projets'
+ l.store 'Used by', 'Utilisé par'
+
+ # ./script/../config/../app/views/custom_fields/new.rhtml
+ l.store 'New custom field', 'Nouveau champ personnalisé'
+ l.store 'Create', 'Créer'
+
+ # ./script/../config/../app/views/custom_fields/_form.rhtml
+ l.store '0 means no restriction', '0 pour aucune restriction'
+ l.store 'Regular expression pattern', 'Expression régulière'
+ l.store 'Possible values', 'Valeurs possibles'
+
+ # ./script/../config/../app/views/documents/edit.rhtml
+ l.store 'Document', 'Document'
+
+ # ./script/../config/../app/views/documents/show.rhtml
+ l.store 'Category', 'Catégorie'
+ l.store 'Edit', 'Modifier'
+ l.store 'download', 'téléchargement'
+ l.store 'Add file', 'Ajouter le fichier'
+ l.store 'Add', 'Ajouter'
+
+ # ./script/../config/../app/views/documents/_form.rhtml
+ l.store 'Title', 'Titre'
+
+ # ./script/../config/../app/views/enumerations/edit.rhtml
+
+ # ./script/../config/../app/views/enumerations/list.rhtml
+
+ # ./script/../config/../app/views/enumerations/new.rhtml
+ l.store 'New enumeration', 'Nouvelle valeur'
+
+ # ./script/../config/../app/views/enumerations/_form.rhtml
+
+ # ./script/../config/../app/views/issues/change_status.rhtml
+ l.store 'Issue', 'Demande'
+ l.store 'New status', 'Nouveau statut'
+ l.store 'Assigned to', 'Assigné à '
+ l.store 'Fixed in version', 'Version corrigée'
+ l.store 'Notes', 'Remarques'
+
+ # ./script/../config/../app/views/issues/edit.rhtml
+ l.store 'Status', 'Statut'
+ l.store 'Tracker', 'Tracker'
+ l.store 'Priority', 'Priorité'
+ l.store 'Subject', 'Sujet'
+
+ # ./script/../config/../app/views/issues/show.rhtml
+ l.store 'Author', 'Auteur'
+ l.store 'Change status', 'Changer le statut'
+ l.store 'History', 'Historique'
+ l.store 'Attachments', 'Fichiers'
+ l.store 'Update...', 'Changer...'
+
+ # ./script/../config/../app/views/issues/_list_simple.rhtml
+ l.store 'No issue', 'Aucune demande'
+
+ # ./script/../config/../app/views/issue_categories/edit.rhtml
+
+ # ./script/../config/../app/views/issue_categories/_form.rhtml
+
+ # ./script/../config/../app/views/issue_statuses/edit.rhtml
+ l.store 'Issue status', 'Statut de demande'
+
+ # ./script/../config/../app/views/issue_statuses/list.rhtml
+ l.store 'Issue statuses', 'Statuts de demande'
+ l.store 'Default status', 'Statut par défaut'
+ l.store 'Issue closed', 'Demande fermée'
+ l.store 'Color', 'Couleur'
+
+ # ./script/../config/../app/views/issue_statuses/new.rhtml
+ l.store 'New issue status', 'Nouveau statut'
+
+ # ./script/../config/../app/views/issue_statuses/_form.rhtml
+
+ # ./script/../config/../app/views/layouts/base.rhtml
+ l.store 'Home', 'Accueil'
+ l.store 'Help', 'Aide'
+ l.store 'Log in', 'Connexion'
+ l.store 'Logout', 'Déconnexion'
+ l.store 'Overview', 'Aperçu'
+ l.store 'Issues', 'Demandes'
+ l.store 'Reports', 'Rapports'
+ l.store 'News', 'Annonces'
+ l.store 'Change log', 'Historique'
+ l.store 'Documents', 'Documents'
+ l.store 'Members', 'Membres'
+ l.store 'Files', 'Fichiers'
+ l.store 'Settings', 'Configuration'
+ l.store 'My projects', 'Mes projets'
+ l.store 'Logged as', 'Connecté en tant que'
+
+ # ./script/../config/../app/views/mailer/issue_add.rhtml
+
+ # ./script/../config/../app/views/mailer/issue_change_status.rhtml
+
+ # ./script/../config/../app/views/mailer/_issue.rhtml
+
+ # ./script/../config/../app/views/news/edit.rhtml
+
+ # ./script/../config/../app/views/news/show.rhtml
+ l.store 'Summary', 'Résumé'
+ l.store 'By', 'Par'
+ l.store 'Date', 'Date'
+
+ # ./script/../config/../app/views/news/_form.rhtml
+
+ # ./script/../config/../app/views/projects/add.rhtml
+ l.store 'New project', 'Nouveau projet'
+
+ # ./script/../config/../app/views/projects/add_document.rhtml
+ l.store 'New document', 'Nouveau document'
+ l.store 'File', 'Fichier'
+
+ # ./script/../config/../app/views/projects/add_issue.rhtml
+ l.store 'New issue', 'Nouvelle demande'
+ l.store 'Attachment', 'Fichier'
+
+ # ./script/../config/../app/views/projects/add_news.rhtml
+
+ # ./script/../config/../app/views/projects/add_version.rhtml
+ l.store 'New version', 'Nouvelle version'
+
+ # ./script/../config/../app/views/projects/changelog.rhtml
+
+ # ./script/../config/../app/views/projects/destroy.rhtml
+ l.store 'Are you sure you want to delete project', 'Êtes-vous sûr de vouloir supprimer le projet'
+
+ # ./script/../config/../app/views/projects/list.rhtml
+ l.store 'Public projects', 'Projets publics'
+
+ # ./script/../config/../app/views/projects/list_documents.rhtml
+ l.store 'Desciption', 'Description'
+
+ # ./script/../config/../app/views/projects/list_files.rhtml
+ l.store 'Files', 'Fichiers'
+ l.store 'New file', 'Nouveau fichier'
+
+ # ./script/../config/../app/views/projects/list_issues.rhtml
+ l.store 'Apply filter', 'Appliquer'
+ l.store 'Reset', 'Annuler'
+ l.store 'Report an issue', 'Nouvelle demande'
+
+ # ./script/../config/../app/views/projects/list_members.rhtml
+ l.store 'Project members', 'Membres du projet'
+
+ # ./script/../config/../app/views/projects/list_news.rhtml
+ l.store 'Read...', 'Lire...'
+
+ # ./script/../config/../app/views/projects/settings.rhtml
+ l.store 'New member', 'Nouveau membre'
+ l.store 'Versions', 'Versions'
+ l.store 'New version...', 'Nouvelle version...'
+ l.store 'Issue categories', 'Catégories des demandes'
+ l.store 'New category', 'Nouvelle catégorie'
+
+ # ./script/../config/../app/views/projects/show.rhtml
+ l.store 'Homepage', 'Site web'
+ l.store 'open', 'ouverte(s)'
+ l.store 'View all issues', 'Voir toutes les demandes'
+ l.store 'View all news', 'Voir toutes les annonces'
+ l.store 'Latest news', 'Dernières annonces'
+
+ # ./script/../config/../app/views/projects/_form.rhtml
+
+ # ./script/../config/../app/views/reports/issue_report.rhtml
+ l.store 'Issues by tracker', 'Demandes par tracker'
+ l.store 'Issues by priority', 'Demandes par priorité'
+ l.store 'Issues by category', 'Demandes par catégorie'
+
+ # ./script/../config/../app/views/reports/_simple.rhtml
+ l.store 'Open', 'Ouverte'
+ l.store 'Total', 'Total'
+
+ # ./script/../config/../app/views/roles/edit.rhtml
+ l.store 'Role', 'Rôle'
+
+ # ./script/../config/../app/views/roles/list.rhtml
+ l.store 'Roles', 'Rôles'
+
+ # ./script/../config/../app/views/roles/new.rhtml
+ l.store 'New role', 'Nouveau rôle'
+
+ # ./script/../config/../app/views/roles/workflow.rhtml
+ l.store 'Workflow setup', 'Configuration du workflow'
+ l.store 'Select a workflow to edit', 'Sélectionner un workflow à mettre à jour'
+ l.store 'New statuses allowed', 'Nouveaux statuts autorisés'
+
+ # ./script/../config/../app/views/roles/_form.rhtml
+ l.store 'Permissions', 'Permissions'
+
+ # ./script/../config/../app/views/trackers/edit.rhtml
+
+ # ./script/../config/../app/views/trackers/list.rhtml
+ l.store 'View issues in change log', 'Demandes affichées dans l\'historique'
+
+ # ./script/../config/../app/views/trackers/new.rhtml
+ l.store 'New tracker', 'Nouveau tracker'
+
+ # ./script/../config/../app/views/trackers/_form.rhtml
+
+ # ./script/../config/../app/views/users/add.rhtml
+ l.store 'New user', 'Nouvel utilisateur'
+
+ # ./script/../config/../app/views/users/edit.rhtml
+ l.store 'User', 'Utilisateur'
+
+ # ./script/../config/../app/views/users/list.rhtml
+ l.store 'Admin', 'Admin'
+ l.store 'Locked', 'Verrouillé'
+
+ # ./script/../config/../app/views/users/_form.rhtml
+ l.store 'Administrator', 'Administrateur'
+
+ # ./script/../config/../app/views/versions/edit.rhtml
+
+ # ./script/../config/../app/views/versions/_form.rhtml
+
+ # ./script/../config/../app/views/welcome/index.rhtml
+
+
+end
diff --git a/redmine/public/.htaccess b/redmine/public/.htaccess new file mode 100644 index 000000000..d3c998345 --- /dev/null +++ b/redmine/public/.htaccess @@ -0,0 +1,40 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
\ No newline at end of file diff --git a/redmine/public/404.html b/redmine/public/404.html new file mode 100644 index 000000000..e1c8e916f --- /dev/null +++ b/redmine/public/404.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<body> + <h1>File not found</h1> +</body> +</html>
\ No newline at end of file diff --git a/redmine/public/500.html b/redmine/public/500.html new file mode 100644 index 000000000..713ee8aa0 --- /dev/null +++ b/redmine/public/500.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<body> + <h1>Sorry, an application error occured</h1> +</body> +</html>
\ No newline at end of file diff --git a/redmine/public/dispatch.cgi b/redmine/public/dispatch.cgi new file mode 100644 index 000000000..7095803c1 --- /dev/null +++ b/redmine/public/dispatch.cgi @@ -0,0 +1,10 @@ +#!e:/ruby/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch
\ No newline at end of file diff --git a/redmine/public/dispatch.fcgi b/redmine/public/dispatch.fcgi new file mode 100644 index 000000000..418aa3a97 --- /dev/null +++ b/redmine/public/dispatch.fcgi @@ -0,0 +1,24 @@ +#!e:/ruby/bin/ruby +# +# You may specify the path to the FastCGI crash log (a log of unhandled +# exceptions which forced the FastCGI instance to exit, great for debugging) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/redmine/public/dispatch.rb b/redmine/public/dispatch.rb new file mode 100644 index 000000000..7095803c1 --- /dev/null +++ b/redmine/public/dispatch.rb @@ -0,0 +1,10 @@ +#!e:/ruby/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch
\ No newline at end of file diff --git a/redmine/public/favicon.ico b/redmine/public/favicon.ico new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/redmine/public/favicon.ico diff --git a/redmine/public/images/Copie de help.png b/redmine/public/images/Copie de help.png Binary files differnew file mode 100644 index 000000000..6dc4f684a --- /dev/null +++ b/redmine/public/images/Copie de help.png diff --git a/redmine/public/images/admin.png b/redmine/public/images/admin.png Binary files differnew file mode 100644 index 000000000..0c190984f --- /dev/null +++ b/redmine/public/images/admin.png diff --git a/redmine/public/images/bulletgreen.png b/redmine/public/images/bulletgreen.png Binary files differnew file mode 100644 index 000000000..abe41592b --- /dev/null +++ b/redmine/public/images/bulletgreen.png diff --git a/redmine/public/images/bulletred.png b/redmine/public/images/bulletred.png Binary files differnew file mode 100644 index 000000000..26a121057 --- /dev/null +++ b/redmine/public/images/bulletred.png diff --git a/redmine/public/images/delete.png b/redmine/public/images/delete.png Binary files differnew file mode 100644 index 000000000..2ed33bdf3 --- /dev/null +++ b/redmine/public/images/delete.png diff --git a/redmine/public/images/dir.png b/redmine/public/images/dir.png Binary files differnew file mode 100644 index 000000000..d078094ac --- /dev/null +++ b/redmine/public/images/dir.png diff --git a/redmine/public/images/dir_new.png b/redmine/public/images/dir_new.png Binary files differnew file mode 100644 index 000000000..2d29814f2 --- /dev/null +++ b/redmine/public/images/dir_new.png diff --git a/redmine/public/images/dir_open.png b/redmine/public/images/dir_open.png Binary files differnew file mode 100644 index 000000000..a248cba3c --- /dev/null +++ b/redmine/public/images/dir_open.png diff --git a/redmine/public/images/document.png b/redmine/public/images/document.png Binary files differnew file mode 100644 index 000000000..34fde6eb3 --- /dev/null +++ b/redmine/public/images/document.png diff --git a/redmine/public/images/edit_small.png b/redmine/public/images/edit_small.png Binary files differnew file mode 100644 index 000000000..dea7c92ea --- /dev/null +++ b/redmine/public/images/edit_small.png diff --git a/redmine/public/images/file_new.png b/redmine/public/images/file_new.png Binary files differnew file mode 100644 index 000000000..9a12ca732 --- /dev/null +++ b/redmine/public/images/file_new.png diff --git a/redmine/public/images/help.png b/redmine/public/images/help.png Binary files differnew file mode 100644 index 000000000..da8feb993 --- /dev/null +++ b/redmine/public/images/help.png diff --git a/redmine/public/images/home.png b/redmine/public/images/home.png Binary files differnew file mode 100644 index 000000000..7a12add6a --- /dev/null +++ b/redmine/public/images/home.png diff --git a/redmine/public/images/issues.png b/redmine/public/images/issues.png Binary files differnew file mode 100644 index 000000000..e6948bff7 --- /dev/null +++ b/redmine/public/images/issues.png diff --git a/redmine/public/images/locked.png b/redmine/public/images/locked.png Binary files differnew file mode 100644 index 000000000..5199dfe22 --- /dev/null +++ b/redmine/public/images/locked.png diff --git a/redmine/public/images/logout.png b/redmine/public/images/logout.png Binary files differnew file mode 100644 index 000000000..edf94abcd --- /dev/null +++ b/redmine/public/images/logout.png diff --git a/redmine/public/images/mailer.png b/redmine/public/images/mailer.png Binary files differnew file mode 100644 index 000000000..8008bb84b --- /dev/null +++ b/redmine/public/images/mailer.png diff --git a/redmine/public/images/notes.png b/redmine/public/images/notes.png Binary files differnew file mode 100644 index 000000000..d26b1d577 --- /dev/null +++ b/redmine/public/images/notes.png diff --git a/redmine/public/images/options.png b/redmine/public/images/options.png Binary files differnew file mode 100644 index 000000000..a907c20f1 --- /dev/null +++ b/redmine/public/images/options.png diff --git a/redmine/public/images/package.png b/redmine/public/images/package.png Binary files differnew file mode 100644 index 000000000..634d13d9f --- /dev/null +++ b/redmine/public/images/package.png diff --git a/redmine/public/images/projects.png b/redmine/public/images/projects.png Binary files differnew file mode 100644 index 000000000..b42347a92 --- /dev/null +++ b/redmine/public/images/projects.png diff --git a/redmine/public/images/rails.png b/redmine/public/images/rails.png Binary files differnew file mode 100644 index 000000000..b8441f182 --- /dev/null +++ b/redmine/public/images/rails.png diff --git a/redmine/public/images/rails_powered.png b/redmine/public/images/rails_powered.png Binary files differnew file mode 100644 index 000000000..5255e62de --- /dev/null +++ b/redmine/public/images/rails_powered.png diff --git a/redmine/public/images/rails_small.png b/redmine/public/images/rails_small.png Binary files differnew file mode 100644 index 000000000..aff4b7a84 --- /dev/null +++ b/redmine/public/images/rails_small.png diff --git a/redmine/public/images/role.png b/redmine/public/images/role.png Binary files differnew file mode 100644 index 000000000..ad36d1ca8 --- /dev/null +++ b/redmine/public/images/role.png diff --git a/redmine/public/images/rss.png b/redmine/public/images/rss.png Binary files differnew file mode 100644 index 000000000..89ce35f80 --- /dev/null +++ b/redmine/public/images/rss.png diff --git a/redmine/public/images/sort_asc.png b/redmine/public/images/sort_asc.png Binary files differnew file mode 100644 index 000000000..05dfa15f4 --- /dev/null +++ b/redmine/public/images/sort_asc.png diff --git a/redmine/public/images/sort_desc.png b/redmine/public/images/sort_desc.png Binary files differnew file mode 100644 index 000000000..f82d53917 --- /dev/null +++ b/redmine/public/images/sort_desc.png diff --git a/redmine/public/images/tracker.png b/redmine/public/images/tracker.png Binary files differnew file mode 100644 index 000000000..f29cfb2af --- /dev/null +++ b/redmine/public/images/tracker.png diff --git a/redmine/public/images/true.png b/redmine/public/images/true.png Binary files differnew file mode 100644 index 000000000..9afc0b52a --- /dev/null +++ b/redmine/public/images/true.png diff --git a/redmine/public/images/user.png b/redmine/public/images/user.png Binary files differnew file mode 100644 index 000000000..89d591c0b --- /dev/null +++ b/redmine/public/images/user.png diff --git a/redmine/public/images/user_page.png b/redmine/public/images/user_page.png Binary files differnew file mode 100644 index 000000000..940a7b8e1 --- /dev/null +++ b/redmine/public/images/user_page.png diff --git a/redmine/public/images/users.png b/redmine/public/images/users.png Binary files differnew file mode 100644 index 000000000..3b9fc5aa9 --- /dev/null +++ b/redmine/public/images/users.png diff --git a/redmine/public/images/workflow.png b/redmine/public/images/workflow.png Binary files differnew file mode 100644 index 000000000..868332fed --- /dev/null +++ b/redmine/public/images/workflow.png diff --git a/redmine/public/javascripts/application.js b/redmine/public/javascripts/application.js new file mode 100644 index 000000000..d8083a957 --- /dev/null +++ b/redmine/public/javascripts/application.js @@ -0,0 +1,8 @@ +function checkAll (id, checked) {
+ var el = document.getElementById(id);
+ for (var i = 0; i < el.elements.length; i++) {
+ if (el.elements[i].disabled==false) {
+ el.elements[i].checked = checked;
+ }
+ }
+}
\ No newline at end of file diff --git a/redmine/public/javascripts/controls.js b/redmine/public/javascripts/controls.js new file mode 100644 index 000000000..9742b6918 --- /dev/null +++ b/redmine/public/javascripts/controls.js @@ -0,0 +1,750 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// See scriptaculous.js for full license. + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if (this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if (typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (navigator.appVersion.indexOf('MSIE')>0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + '<iframe id="' + this.update.id + '_iefix" '+ + 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + + 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) + return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + + var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.firstChild); + + if(this.update.firstChild && this.update.firstChild.childNodes) { + this.entryCount = + this.update.firstChild.childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i<this.options.tokens.length; i++) { + var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); + if (thisTokenPos > lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + + elem.substr(entry.length) + "</li>"); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" + + elem.substr(foundPos, entry.length) + "</strong>" + elem.substr( + foundPos + entry.length) + "</li>"); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "<ul>" + ret.join('') + "</ul>"; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + okText: "ok", + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + ajaxOptions: {} + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + this.form.appendChild(okButton); + + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + this.form.appendChild(cancelLink); + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/<br/i) || string.match(/<p>/i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.type = "text"; + textField.name = "value"; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.name = "value"; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + new Ajax.Updater( + { + success: this.element, + // don't update on failure (this could be an option) + failure: null + }, + this.url, + Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions) + ); + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +};
\ No newline at end of file diff --git a/redmine/public/javascripts/dragdrop.js b/redmine/public/javascripts/dragdrop.js new file mode 100644 index 000000000..92d1f7316 --- /dev/null +++ b/redmine/public/javascripts/dragdrop.js @@ -0,0 +1,584 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// See scriptaculous.js for full license. + +/*--------------------------------------------------------------------------*/ + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + isContained: function(element, drop) { + var parentNode = element.parentNode; + return drop._containers.detect(function(c) { return parentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) { + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + if(drop.greedy) { + Droppables.activate(drop); + throw $break; + } + } + }); + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) + this.last_active.onDrop(element, this.last_active.element, event); + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + }, + + deactivate: function(draggbale) { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable.prototype = { + initialize: function(element) { + var options = Object.extend({ + handle: false, + starteffect: function(element) { + new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); + }, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + element._revert = new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur}); + }, + endeffect: function(element) { + new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); + }, + zindex: 1000, + revert: false, + snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] } + }, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) + this.handle = Element.childrenWithClassName(this.element, options.handle)[0]; + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + Element.makePositioned(this.element); // fix IE + + this.delta = this.currentDelta(); + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(this.element.style.left || '0'), + parseInt(this.element.style.top || '0')]); + }, + + initDrag: function(event) { + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + if(this.element._revert) { + this.element._revert.cancel(); + this.element._revert = null; + } + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + Draggables.notify('onStart', this, event); + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + Position.prepare(); + Droppables.show(pointer, this.element); + Draggables.notify('onDrag', this, event); + this.draw(pointer); + if(this.options.change) this.options.change(this); + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(!event.keyCode==Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1]); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + sortables: new Array(), + + options: function(element){ + element = $(element); + return this.sortables.detect(function(s) { return s.element == element }); + }, + + destroy: function(element){ + element = $(element); + this.sortables.findAll(function(s) { return s.element == element }).each(function(s){ + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + }); + this.sortables = this.sortables.reject(function(s) { return s.element == element }); + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, // fixme: unimplemented + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + hoverclass: null, + ghosting: false, + format: null, + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass, + onHover: Sortable.onHover, + greedy: !options.dropOnEmpty + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // make it so + + // drop on empty handling + if(options.dropOnEmpty) { + Droppables.add(element, + {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false}); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + Element.childrenWithClassName(e, options.handle)[0] : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + options.droppables.push(e); + }); + + // keep reference + this.sortables.push(options); + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + if(!element.hasChildNodes()) return null; + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() && + (!options.only || (Element.hasClassName(e, options.only)))) + elements.push(e); + if(options.tree) { + var grandchildren = this.findElements(e, options); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : null); + }, + + onHover: function(element, dropon, overlap) { + if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon) { + if(element.parentNode!=dropon) { + var oldParentNode = element.parentNode; + dropon.appendChild(element); + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon).onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.addClassName(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.left = offsets[0] + 'px'; + Sortable._marker.style.top = offsets[1] + 'px'; + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px'; + else + Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + + Element.show(Sortable._marker); + }, + + serialize: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format || /^[^_]*_(.*)$/ + }, arguments[1] || {}); + return $(this.findElements(element, options) || []).map( function(item) { + return (encodeURIComponent(options.name) + "[]=" + + encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : '')); + }).join("&"); + } +}
\ No newline at end of file diff --git a/redmine/public/javascripts/effects.js b/redmine/public/javascripts/effects.js new file mode 100644 index 000000000..414398ce4 --- /dev/null +++ b/redmine/public/javascripts/effects.js @@ -0,0 +1,854 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// See scriptaculous.js for full license. + +/* ------------- element ext -------------- */ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if(this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { + var children = $(element).childNodes; + var text = ''; + var classtest = new RegExp('^([^ ]+ )*' + ignoreclass+ '( [^ ]+)*$','i'); + + for (var i = 0; i < children.length; i++) { + if(children[i].nodeType==3) { + text+=children[i].nodeValue; + } else { + if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) + text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); + } + } + + return text; +} + +Element.setStyle = function(element, style) { + element = $(element); + for(k in style) element.style[k.camelize()] = style[k]; +} + +Element.setContentZoom = function(element, percent) { + Element.setStyle(element, {fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} + +Element.getOpacity = function(element){ + var opacity; + if (opacity = Element.getStyle(element, 'opacity')) + return parseFloat(opacity); + if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + Element.setStyle(element, { opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : null }); + if(/MSIE/.test(navigator.userAgent)) + Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + Element.setStyle(element, {opacity: value}); + if(/MSIE/.test(navigator.userAgent)) + Element.setStyle(element, + { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.childrenWithClassName = function(element, className) { + return $A($(element).getElementsByTagName('*')).select( + function(c) { return Element.hasClassName(c, className) }); +} + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1'; + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = {} + +Effect.Transitions.linear = function(pos) { + return pos; +} +Effect.Transitions.sinoidal = function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; +} +Effect.Transitions.reverse = function(pos) { + return 1-pos; +} +Effect.Transitions.flicker = function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; +} +Effect.Transitions.wobble = function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; +} +Effect.Transitions.pulse = function(pos) { + return (Math.floor(pos*10) % 2 == 0 ? + (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10))); +} +Effect.Transitions.none = function(pos) { + return 0; +} +Effect.Transitions.full = function(pos) { + return 1; +} + +/* ------------- core effects ------------- */ + +Effect.Queue = { + effects: [], + _each: function(iterator) { + this.effects._each(iterator); + }, + interval: null, + add: function(effect) { + var timestamp = new Date().getTime(); + + switch(effect.options.queue) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + this.effects.push(effect); + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +} +Object.extend(Effect.Queue, Enumerable); + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + setOptions: function(options) { + this.options = Object.extend({ + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, options || {}); + }, + start: function(options) { + this.setOptions(options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) Effect.Queue.add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) Effect.Queue.remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) + Element.setStyle(this.element, {zoom: 1}); + var options = Object.extend({ + from: Element.getOpacity(this.element) || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + Element.setOpacity(this.element, position); + } +}); + +Effect.MoveBy = Class.create(); +Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), { + initialize: function(element, toTop, toLeft) { + this.element = $(element); + this.toTop = toTop; + this.toLeft = toLeft; + this.start(arguments[3]); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + Element.makePositioned(this.element); + this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0'); + this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0'); + }, + update: function(position) { + Element.setStyle(this.element, { + top: this.toTop * position + this.originalTop + 'px', + left: this.toLeft * position + this.originalLeft + 'px' + }); + } +}); + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element) + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = Element.getStyle(this.element,'position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = Element.getStyle(this.element,'font-size') || '100%'; + ['em','px','%'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = width + 'px'; + if(this.options.scaleY) d.height = height + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + Element.setStyle(this.element, d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: Element.getStyle(this.element, 'background-image') }; + Element.setStyle(this.element, {backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = Element.getStyle(this.element, 'background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + 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)); + }, + update: function(position) { + Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + Element.setStyle(this.element, Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + var oldOpacity = Element.getInlineOpacity(element); + var options = Object.extend({ + from: Element.getOpacity(element) || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { with(Element) { + if(effect.options.to!=0) return; + hide(effect.element); + setStyle(effect.element, {opacity: oldOpacity}); }} + }, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + var options = Object.extend({ + from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0), + to: 1.0, + beforeSetup: function(effect) { with(Element) { + setOpacity(effect.element, effect.options.from); + show(effect.element); }} + }, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { with(Element) { + setStyle(effect.effects[0].element, {position: 'absolute'}); }}, + afterFinishInternal: function(effect) { with(Element) { + hide(effect.effects[0].element); + setStyle(effect.effects[0].element, oldStyle); }} + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + Element.makeClipping(element); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoClipping].call(effect.element); }} + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var oldHeight = Element.getStyle(element, 'height'); + var elementDimensions = Element.getDimensions(element); + return new Effect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { with(Element) { + makeClipping(effect.element); + setStyle(effect.element, {height: '0px'}); + show(effect.element); + }}, + afterFinishInternal: function(effect) { with(Element) { + undoClipping(effect.element); + setStyle(effect.element, {height: oldHeight}); + }} + }, arguments[1] || {}) + ); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = Element.getInlineOpacity(element); + return new Effect.Appear(element, { + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { with(Element) { + [makePositioned,makeClipping].call(effect.element); + }}, + afterFinishInternal: function(effect) { with(Element) { + [hide,undoClipping,undoPositioned].call(effect.element); + setStyle(effect.element, {opacity: oldOpacity}); + }} + }) + } + }); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: Element.getStyle(element, 'top'), + left: Element.getStyle(element, 'left'), + opacity: Element.getInlineOpacity(element) }; + return new Effect.Parallel( + [ new Effect.MoveBy(element, 100, 0, { sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { with(Element) { + makePositioned(effect.effects[0].element); }}, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoPositioned].call(effect.effects[0].element); + setStyle(effect.effects[0].element, oldStyle); }} + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: Element.getStyle(element, 'top'), + left: Element.getStyle(element, 'left') }; + return new Effect.MoveBy(element, 0, 20, + { duration: 0.05, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, -20, + { duration: 0.05, afterFinishInternal: function(effect) { with(Element) { + undoPositioned(effect.element); + setStyle(effect.element, oldStyle); + }}}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element); + Element.cleanWhitespace(element); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom'); + var elementDimensions = Element.getDimensions(element); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { with(Element) { + makePositioned(effect.element); + makePositioned(effect.element.firstChild); + if(window.opera) setStyle(effect.element, {top: ''}); + makeClipping(effect.element); + setStyle(effect.element, {height: '0px'}); + show(element); }}, + afterUpdateInternal: function(effect) { with(Element) { + setStyle(effect.element.firstChild, {bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }}, + afterFinishInternal: function(effect) { with(Element) { + undoClipping(effect.element); + undoPositioned(effect.element.firstChild); + undoPositioned(effect.element); + setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }} + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element); + Element.cleanWhitespace(element); + var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom'); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { with(Element) { + makePositioned(effect.element); + makePositioned(effect.element.firstChild); + if(window.opera) setStyle(effect.element, {top: ''}); + makeClipping(effect.element); + show(element); }}, + afterUpdateInternal: function(effect) { with(Element) { + setStyle(effect.element.firstChild, {bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }}, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoClipping].call(effect.element); + undoPositioned(effect.element.firstChild); + undoPositioned(effect.element); + setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }} + }, arguments[1] || {}) + ); +} + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, + { restoreAfterFinish: true, + beforeSetup: function(effect) { with(Element) { + makeClipping(effect.element); }}, + afterFinishInternal: function(effect) { with(Element) { + hide(effect.element); + undoClipping(effect.element); }} + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransistion: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: Element.getInlineOpacity(element) }; + + var dims = Element.getDimensions(element); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.MoveBy(element, initialMoveY, initialMoveX, { + duration: 0.01, + beforeSetup: function(effect) { with(Element) { + hide(effect.element); + makeClipping(effect.element); + makePositioned(effect.element); + }}, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { with(Element) { + setStyle(effect.effects[0].element, {height: '0px'}); + show(effect.effects[0].element); }}, + afterFinishInternal: function(effect) { with(Element) { + [undoClipping, undoPositioned].call(effect.effects[0].element); + setStyle(effect.effects[0].element, oldStyle); }} + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransistion: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: Element.getInlineOpacity(element) }; + + var dims = Element.getDimensions(element); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { with(Element) { + [makePositioned, makeClipping].call(effect.effects[0].element) }}, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoClipping, undoPositioned].call(effect.effects[0].element); + setStyle(effect.effects[0].element, oldStyle); }} + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = Element.getInlineOpacity(element); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, from: 0, + afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + Element.makeClipping(element); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoClipping].call(effect.element); + setStyle(effect.element, oldStyle); + }} }); + }}, arguments[1] || {})); +} diff --git a/redmine/public/javascripts/prototype.js b/redmine/public/javascripts/prototype.js new file mode 100644 index 000000000..e9ccd3c88 --- /dev/null +++ b/redmine/public/javascripts/prototype.js @@ -0,0 +1,1785 @@ +/* Prototype JavaScript framework, version 1.4.0 + * (c) 2005 Sam Stephenson <sam@conio.net> + * + * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff + * against the source tree, available from the Prototype darcs repository. + * + * Prototype is freely distributable under the terms of an MIT-style license. + * + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.4.0', + ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', + + emptyFunction: function() {}, + K: function(x) {return x} +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.inspect = function(object) { + try { + if (object == undefined) return 'undefined'; + if (object == null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } +} + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this; + return function(event) { + return __method.call(object, event || window.event); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + } +} + +/*--------------------------------------------------------------------------*/ + +function $() { + var elements = new Array(); + + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + + if (arguments.length == 1) + return element; + + elements.push(element); + } + + return elements; +} +Object.extend(String.prototype, { + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(eval); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; + }, + + toQueryParams: function() { + var pairs = this.match(/^\??(.*)$/)[1].split('&'); + return pairs.inject({}, function(params, pairString) { + var pair = pairString.split('='); + params[pair[0]] = pair[1]; + return params; + }); + }, + + toArray: function() { + return this.split(''); + }, + + camelize: function() { + var oStringList = this.split('-'); + if (oStringList.length == 1) return oStringList[0]; + + var camelizedString = this.indexOf('-') == 0 + ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) + : oStringList[0]; + + for (var i = 1, len = oStringList.length; i < len; i++) { + var s = oStringList[i]; + camelizedString += s.charAt(0).toUpperCase() + s.substring(1); + } + + return camelizedString; + }, + + inspect: function() { + return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'"; + } +}); + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = true; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function (iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.collect(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value >= (result || value)) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value <= (result || value)) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.collect(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.collect(Prototype.K); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + iterator(value = collections.pluck(index)); + return value; + }); + }, + + inspect: function() { + return '#<Enumerable:' + this.toArray().inspect() + '>'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != undefined || value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0; i < this.length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + shift: function() { + var result = this[0]; + for (var i = 0; i < this.length - 1; i++) + this[i] = this[i + 1]; + this.length--; + return result; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); +var Hash = { + _each: function(iterator) { + for (key in this) { + var value = this[key]; + if (typeof value == 'function') continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject($H(this), function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + return pair.map(encodeURIComponent).join('='); + }).join('&'); + }, + + inspect: function() { + return '#<Hash:{' + this.map(function(pair) { + return pair.map(Object.inspect).join(': '); + }).join(', ') + '}>'; + } +} + +function $H(object) { + var hash = Object.extend({}, object || {}); + Object.extend(hash, Enumerable); + Object.extend(hash, Hash); + return hash; +} +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + do { + iterator(value); + value = value.succ(); + } while (this.include(value)); + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')}, + function() {return new XMLHttpRequest()} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responderToAdd) { + if (!this.include(responderToAdd)) + this.responders.push(responderToAdd); + }, + + unregister: function(responderToRemove) { + this.responders = this.responders.without(responderToRemove); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (responder[callback] && typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + parameters: '' + } + Object.extend(this.options, options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + try { + this.url = url; + if (this.options.method == 'get' && parameters.length > 0) + this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; + + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.options.method, this.url, + this.options.asynchronous); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + } catch (e) { + this.dispatchException(e); + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version]; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', + 'application/x-www-form-urlencoded'); + + /* Force "Connection: close" for Mozilla browsers to work around + * a bug where XMLHttpReqeuest sends an incorrect Content-length + * header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + header: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) {} + }, + + evalJSON: function() { + try { + return eval(this.header('X-JSON')); + } catch (e) {} + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (event == 'Complete') { + try { + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.header('Content-type') || '').match(/^text\/javascript/i)) + this.evalResponse(); + } + + try { + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : + (container.success ? null : $(container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, object) { + this.updateContent(); + onComplete(transport, object); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + var response = this.transport.responseText; + + if (!this.options.evalScripts) + response = response.stripScripts(); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + Element.update(receiver, response); + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + return $A(children).inject([], function(elements, child) { + if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + elements.push(child); + return elements; + }); +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) { + var Element = new Object(); +} + +Object.extend(Element, { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + Element[Element.visible(element) ? 'hide' : 'show'](element); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + update: function(element, html) { + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).include(className); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).add(className); + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).remove(className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + scrollTo: function(element) { + element = $(element); + var x = element.x ? element.x : element.offsetLeft, + y = element.y ? element.y : element.offsetTop; + window.scrollTo(x, y); + }, + + getStyle: function(element, style) { + element = $(element); + var value = element.style[style.camelize()]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css.getPropertyValue(style) : null; + } else if (element.currentStyle) { + value = element.currentStyle[style.camelize()]; + } + } + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (name in style) + element.style[name.camelize()] = style[name]; + }, + + getDimensions: function(element) { + element = $(element); + if (Element.getStyle(element, 'display') != 'none') + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = ''; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = 'none'; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return; + element._overflow = element.style.overflow; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + }, + + undoClipping: function(element) { + element = $(element); + if (element._overflow) return; + element.style.overflow = element._overflow; + element._overflow = undefined; + } +}); + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + if (this.element.tagName.toLowerCase() == 'tbody') { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>'; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set(this.toArray().concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set(this.select(function(className) { + return className != classNameToRemove; + }).join(' ')); + }, + + toString: function() { + return this.toArray().join(' '); + } +} + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + focus: function(element) { + $(element).focus(); + }, + + present: function() { + for (var i = 0; i < arguments.length; i++) + if ($(arguments[i]).value == '') return false; + return true; + }, + + select: function(element) { + $(element).select(); + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select) + element.select(); + } +} + +/*--------------------------------------------------------------------------*/ + +var Form = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + form = $(form); + var elements = new Array(); + + for (tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) + return inputs; + + var matchingInputs = new Array(); + for (var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || + (name && input.name != name)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + }, + + findFirstElement: function(form) { + return Form.getElements(form).find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + Field.activate(Form.findFirstElement(form)); + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) { + var key = encodeURIComponent(parameter[0]); + if (key.length == 0) return; + + if (parameter[1].constructor != Array) + parameter[1] = [parameter[1]]; + + return parameter[1].map(function(value) { + return key + '=' + encodeURIComponent(value); + }).join('&'); + } + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'submit': + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + return Form.Element.Serializers[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var value = '', opt, index = element.selectedIndex; + if (index >= 0) { + opt = element.options[index]; + value = opt.value; + if (!value && !('value' in opt)) + value = opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = new Array(); + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) { + var optValue = opt.value; + if (!optValue && !('value' in opt)) + optValue = opt.text; + value.push(optValue); + } + } + return [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + var elements = Form.getElements(this.element); + for (var i = 0; i < elements.length; i++) + this.registerCallback(elements[i]); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + this._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } +}); + +/* prevent memory leaks in IE */ +Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px';; + element.style.left = left + 'px';; + element.style.width = width + 'px';; + element.style.height = height + 'px';; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +}
\ No newline at end of file diff --git a/redmine/public/manual/administration.html b/redmine/public/manual/administration.html new file mode 100644 index 000000000..22016c4d6 --- /dev/null +++ b/redmine/public/manual/administration.html @@ -0,0 +1,121 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+
+<head>
+<title>redMine - Aide en ligne</title>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
+<meta name="description" content="redMine" />
+<meta name="keywords" content="issue,bug,tracker" />
+<link href="stylesheets/help.css" rel="stylesheet" type="text/css" />
+</head>
+
+<body>
+<p align="right">[ <a href="index.html">Index</a> ]</p>
+<h1>Administration</h1>
+Sommaire:
+<ol>
+ <li><a href="administration.html#users">Utilisateurs</a></li>
+ <li><a href="administration.html#roles">Rôles et permissions</a></li>
+ <li><a href="administration.html#trackers">Trackers</a></li>
+ <li><a href="administration.html#custom_fields">Champs personnalisés</a></li>
+ <li><a href="administration.html#issue_statuses">Statuts de demande</a></li>
+ <li><a href="administration.html#workflow">Workflow</a></li>
+ <li><a href="administration.html#enumerations">Listes de valeurs</a></li>
+ <li><a href="administration.html#mail_notifications">Notifications par mail</a></li>
+ <li><a href="administration.html#app_info">Informations</a></li>
+</ol>
+
+
+<h2><a name="users"></a>1. Utilisateurs</h2>
+<p>Ces écrans vous permettent de gérer les utilisateurs de l'application.</p>
+<h3>1.1 Liste des utilisateurs</h3>
+<center><img src="images/users_list.png"><br />
+<i><small>Liste des utilisateurs</small></i></center>
+<h3>1.2 Création ou modification d'un utilisateur</h3>
+<ul>
+ <li><b>Administrateur</b>: déclare l'utilisateur comme administrateur de l'application.</li>
+ <li><b>Notifications par mail</b>: permet d'activer ou non l'envoi automatique de notifications par mail pour cet utilisateur</li>
+ <li><b>Verrouillé</b>: désactive le compte de l'utilisateur</li>
+</ul>
+<p>En mode modification, laissez le champ <b>Password</b> vide pour laisser le mot de passe de l'utilisateur inchangé.</p>
+<p>Un utilisateur déclaré comme administrateur dispose de toutes les permissions sur l'application et sur tous les projets.</p>
+
+
+<h2><a name="roles"></a>2. Rôles et permissions</h2>
+<p>Les rôles permettent de définir les permissions des différents membres d'un projet.<br />
+Chaque membre d'un projet dispose d'un rôle unique au sein d'un projet.
+Un utilisateur peut avoir différents rôles au sein de différents projets.</p>
+<p>Sur l'écran d'édition du rôle, cochez les actions que vous souhaitez autoriser pour le rôle.</p>
+
+
+<h2><a name="trackers"></a>3. Trackers</h2>
+<p>Les trackers permettent de typer les demandes et de définir des workflows spécifiques pour chacun de ces types.</p>
+
+<h2><a name="custom_fields"></a>4. Champs personnalisés</h2>
+<p>Les champs personnalisés vous permettent d'ajouter des informations supplémentaires sur les demandes.</p>
+Un champ personnalisé peut être de l'un des types suivants:
+<ul>
+ <li><b>Integer</b>: entier positif ou négatif</li>
+ <li><b>String</b>: chaîne de caractère</li>
+ <li><b>Date</b>: date</li>
+ <li><b>Boolean</b>: booléen (case à cocher)</li>
+ <li><b>List</b>: valeur à sélectionnée parmi une liste prédéfinie (liste déroulante)</li>
+</ul>
+Des éléments de validation peuvent être définis:
+<ul>
+ <li><b>Required</b>: champ dont la saisie est obligatoire sur les demandes</li>
+ <li><b>For all projects</b>: champ automatiquement associé à l'ensemble des projets</li>
+ <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>
+ <li><b>Regular expression</b>: expression régulière permettant de valider la valeur saisie</li>
+ <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>
+</ul>
+<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
+(voir <a href="projects.html#settings">Project settings</a>).</p>
+
+
+<h2><a name="issue_statuses"></a>5. Statuts des demandes</h2>
+<p>Cet écran vous permet de définir les différents statuts possibles des demandes.</p>
+<ul>
+ <li><b>Closed</b>: indique que le statut correspond à une demande considérée comme fermée</li>
+ <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>
+ <li><b>HTML color</b>: code de couleur HTML représentant le statut à l'affichage</li>
+</ul>
+
+
+<h2><a name="workflow"></a>6. Workflow</h2>
+<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>
+<p>Sélectionnez le rôle et le type de demande pour lesquels vous souhaitez modifier le workflow, puis cliquez sur <b>Edit</b>.
+L'écran vous permet alors de modifier, pour le rôle et le type de demande choisi, les changements autorisés.</p>
+<p>Les lignes représentent les statuts initiaux des demandes. Les colonnes représentent les statuts autorisés à être appliqués.</p>
+<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 />
+Celles au statut Assigned pourront être passées au statut Resolved.<br />
+Le statut de toutes les autres demandes de type Bug ne pourra pas être modifié par le Développeur.</p>
+<center><img src="images/workflow.png"><br />
+<i><small>Exemple de configuration d'un workflow</small></i></center>
+<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>).
+
+
+<h2><a name="enumerations"></a>7. Listes de valeurs</h2>
+<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 />
+Cet écran vous permet de définir les valeurs possibles pour chacune des listes suivantes:</p>
+<ul>
+ <li><b>Priorités des demandes</b></li>
+ <li><b>Catégories de documents</b></li>
+</ul>
+
+
+<h2><a name="mail_notifications"></a>8. Notifications par mail</h2>
+<p>Cet écran vous permet de sélectionner les actions qui donneront lieu à une notification par mail aux membres du projet.</p>
+<p><b>Remarque</b>: l'envoi de mails doit être activé dans la configuration de l'application si souhaitez effectuer des notifications.</p>
+
+
+<h2><a name="app_info"></a>9. Informations</h2>
+<p>Affiche des informations relatives à l'application</p>
+<ul>
+ <li><b>Version</b>: version de l'application</li>
+ <li><b>Database</b>: type de base de données utilisée</li>
+</ul>
+
+
+</body>
+</html>
+
diff --git a/redmine/public/manual/images/issues_list.png b/redmine/public/manual/images/issues_list.png Binary files differnew file mode 100644 index 000000000..2fa6dc1b6 --- /dev/null +++ b/redmine/public/manual/images/issues_list.png diff --git a/redmine/public/manual/images/users_edit.png b/redmine/public/manual/images/users_edit.png Binary files differnew file mode 100644 index 000000000..3a4797e9d --- /dev/null +++ b/redmine/public/manual/images/users_edit.png diff --git a/redmine/public/manual/images/users_list.png b/redmine/public/manual/images/users_list.png Binary files differnew file mode 100644 index 000000000..0c9ef86ec --- /dev/null +++ b/redmine/public/manual/images/users_list.png diff --git a/redmine/public/manual/images/workflow.png b/redmine/public/manual/images/workflow.png Binary files differnew file mode 100644 index 000000000..26071cade --- /dev/null +++ b/redmine/public/manual/images/workflow.png diff --git a/redmine/public/manual/index.html b/redmine/public/manual/index.html new file mode 100644 index 000000000..3f0cb1e6f --- /dev/null +++ b/redmine/public/manual/index.html @@ -0,0 +1,64 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+
+<head>
+<title>redMine - Aide en ligne</title>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
+<meta name="description" content="redMine" />
+<meta name="keywords" content="issue,bug,tracker" />
+<link href="stylesheets/help.css" rel="stylesheet" type="text/css" />
+</head>
+
+<body>
+<h1>Aide</h1>
+
+<p>Documentation en ligne redMine</p>
+
+Sommaire:
+<ul>
+
+ <li>
+ <a href="administration.html">Administration</a>
+ <ol>
+ <li><a href="administration.html#users">Utilisateur</a></li>
+ <li><a href="administration.html#roles">Rôles et permissions</a></li>
+ <li><a href="administration.html#trackers">Trackers</a></li>
+ <li><a href="administration.html#custom_fields">Champs personnalisés</a></li>
+ <li><a href="administration.html#issue_statuses">Statuts de demande</a></li>
+ <li><a href="administration.html#workflow">Workflow</a></li>
+ <li><a href="administration.html#enumerations">Liste de valeurs</a></li>
+ <li><a href="administration.html#mail_notifications">Notifications par mail</a></li>
+ <li><a href="administration.html#app_info">Informations</a></li>
+ </ol>
+ </li>
+
+ <li><a href="projects.html">Projets</a>
+ <ol>
+ <li><a href="projects.html#overview">Aperçu</a></li>
+ <li><a href="projects.html#issues">Demandes</a>
+ <ul>
+ <li>Liste des demandes</li>
+ <li>Nouvelle demande</li>
+ <li>Changer le statut d'une demande</li>
+ </ul>
+ </li>
+ <li><a href="projects.html#reports">Rapports</a></li>
+ <li><a href="projects.html#news">Annonces</a></li>
+ <li><a href="projects.html#changelog">Historique</a></li>
+ <li><a href="projects.html#documents">Documents</a></li>
+ <li><a href="projects.html#members">Membres</a></li>
+ <li><a href="projects.html#files">Fichiers</a></li>
+ <li><a href="projects.html#settings">Configuration</a></li>
+ <ul>
+ <li>Projet</li>
+ <li>Membres</li>
+ <li>Versions</li>
+ <li>Catégories de demande</li>
+ </ul>
+ </ol>
+ </li>
+
+
+</ul>
+
+</body>
+</html>
\ No newline at end of file diff --git a/redmine/public/manual/projects.html b/redmine/public/manual/projects.html new file mode 100644 index 000000000..13922f69d --- /dev/null +++ b/redmine/public/manual/projects.html @@ -0,0 +1,109 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+
+<head>
+<title>redMine - Aide en ligne</title>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
+<meta name="description" content="redMine" />
+<meta name="keywords" content="issue,bug,tracker" />
+<link href="stylesheets/help.css" rel="stylesheet" type="text/css" />
+</head>
+
+<body>
+<p align="right">[ <a href="index.html">Index</a> ]</p>
+<h1>Projets</h1>
+Sommaire:
+<ol>
+ <li><a href="projects.html#overview">Aperçu</a></li>
+ <li><a href="projects.html#issues">Demandes</a>
+ <ul>
+ <li>Liste des demandes</li>
+ <li>Nouvelle demande</li>
+ <li>Changer le statut d'une demande</li>
+ </ul>
+ </li>
+ <li><a href="projects.html#reports">Rapports</a></li>
+ <li><a href="projects.html#news">Annonces</a></li>
+ <li><a href="projects.html#changelog">Historique</a></li>
+ <li><a href="projects.html#documents">Documents</a></li>
+ <li><a href="projects.html#members">Membres</a></li>
+ <li><a href="projects.html#files">Fichiers</a></li>
+ <li><a href="projects.html#settings">Configuration</a></li>
+ <ul>
+ <li>Projet</li>
+ <li>Membres</li>
+ <li>Versions</li>
+ <li>Catégories de demande</li>
+ </ul>
+</ol>
+
+
+<h2><a name="overview"></a>1. Aperçu</h2>
+<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.
+
+
+<h2><a name="issues"></a>2. Demandes</h2>
+<h3>2.1 Liste des demandes</h3>
+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 />
+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>.
+<center><img src="images/issues_list.png"><br />
+<i><small>Liste des demandes</small></i></center>
+
+<h3>2.2 Nouvelle demande</h3>
+<p>TODO</p>
+
+<h3>2.3 Changer le statut d'une demande</h3>
+<p>TODO</p>
+
+
+<h2><a name="reports"></a>3. Rapports</h2>
+<p>Synthèse du nombre de demandes par statut et selon différents critères (tracker, priorité, catégorie).
+Des liens directs permettent d'accéder à la liste détaillée des demandes pour chaque critère.</p>
+
+
+<h2><a name="news"></a>4. Annonces</h2>
+<p>Les nouvelles vous permettent d'informer les utilisateurs sur l'activité du projet.</p>
+
+
+<h2><a name="changelog"></a>5. Historique</h2>
+<p>Cette page présente l'ensemble des demandes résolues dans chacune des versions du projet.
+Certains types de demande peuvent être exclus de cet affichage (voir <a href="administration.html#trackers">Trackers</a>).</p>
+
+
+<h2><a name="documents"></a>6. Documents</h2>
+<p>Les documents sont groupés par catégories (voir <a href="administration.html#enumerations">Listes de valeurs</a>).<br />
+Un document peut contenir plusieurs fichiers (exemple: révisions ou versions successives)</p>
+
+
+<h2><a name="members"></a>7. Membres</h2>
+<p>Affichage de l'ensemble des membres du projet, par rôle</p>
+
+
+<h2><a name="files"></a>8. Fichiers</h2>
+<p>Ce module vous permet de publier les fichiers de l'application (sources, binaires, ...) pour chaque version de l'application .</p>
+
+<h2><a name="settings"></a>9. Configuration</h2>
+<h3>9.1 Projet</h3>
+<ul>
+ <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 />
+ Si le projet n'est pas public, seuls les membres du projet y ont accès, en fonction de leur rôle.</li>
+ <li><b>Champs personnalisés</b>: sélectionner les champs personnalisés que vous souhaitez utiliser au sein du projet.<br />
+ Seul l'administrateur peut ajouter de nouveaux champs personnalisés.</li>
+</ul>
+<h3>9.2 Membres</h3>
+<p>Cette section vous permet de définir les membres du projet ainsi que leurs rôles respectifs.<br />
+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>
+
+<h3>9.3 Versions</h3>
+<p>Les versions vous permettent de suivre les changements survenus tout au long du projet.
+A la fermeture d'une demande, vous pouvez indiquer quelle version la prend en compte.<br />
+Vous pouvez par ailleurs publier les différentes versions de l'application (voir <a href="projects.html#files">Fichiers</a>).
+</p>
+
+
+<h3>9.4 Catégories de demande</h3>
+<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>
+
+
+</body>
+</html>
+
diff --git a/redmine/public/manual/stylesheets/help.css b/redmine/public/manual/stylesheets/help.css new file mode 100644 index 000000000..f67bc72bf --- /dev/null +++ b/redmine/public/manual/stylesheets/help.css @@ -0,0 +1,70 @@ +/* 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 */
+
+/**************** Body and tag styles ****************/
+
+
+
+body{
+font:76% Verdana,Tahoma,Arial,sans-serif;
+line-height:1.4em;
+color:#303030;
+margin: 20px;
+}
+
+a{
+color:#467aa7;
+font-weight:bold;
+text-decoration:none;
+background-color:inherit;
+}
+
+a:hover{color:#2a5a8a; text-decoration:none; background-color:inherit;}
+a img{border:none;}
+
+p{padding:0 0 0.2em 0;}
+p form{margin-top:0; margin-bottom:20px;}
+
+h1 {
+display:block;
+
+font-size:1.7em;
+font-weight:normal;
+letter-spacing:-1px;
+color:#505050;
+background-color:inherit;
+}
+
+h2 {
+display:block;
+margin: 30px 0 0 0;
+font-size:1.5em;
+font-weight:normal;
+letter-spacing:-1px;
+color:#505050;
+background-color:inherit;
+}
+
+hr { border:0px; border-bottom:1px dashed #000000; }
+
+
+/**************** Misc classes and styles ****************/
+
+.splitcontentleft{float:left; width:49%;}
+.splitcontentright{float:right; width:49%;}
+.clear{clear:both;}
+.small{font-size:0.8em;line-height:1.4em;padding:0 0 0 0;}
+.hide{display:none;}
+.textcenter{text-align:center;}
+.textright{text-align:right;}
+.important{color:#f02025; background-color:inherit; }
+
+.box{
+margin:0 0 20px 0;
+padding:10px;
+border:1px solid #c0c0c0;
+background-color:#fafbfc;
+color:#505050;
+line-height:1.5em;
+}
+
+
diff --git a/redmine/public/robots.txt b/redmine/public/robots.txt new file mode 100644 index 000000000..4ab9e89fe --- /dev/null +++ b/redmine/public/robots.txt @@ -0,0 +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 diff --git a/redmine/public/stylesheets/application.css b/redmine/public/stylesheets/application.css new file mode 100644 index 000000000..ac6610520 --- /dev/null +++ b/redmine/public/stylesheets/application.css @@ -0,0 +1,322 @@ +/* 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 */
+/* Edited by Jean-Philippe Lang *>
+/**************** Body and tag styles ****************/
+
+
+#header * {margin:0; padding:0;}
+p, ul, ol, li {margin:0; padding:0;}
+
+
+body{
+font:76% Verdana,Tahoma,Arial,sans-serif;
+line-height:1.4em;
+text-align:center;
+color:#303030;
+background:#e8eaec;
+}
+
+a{
+color:#467aa7;
+font-weight:bold;
+text-decoration:none;
+background-color:inherit;
+}
+
+a:hover{color:#2a5a8a; text-decoration:none; background-color:inherit;}
+a img{border:none;}
+
+p{padding:0 0 1em 0;}
+p form{margin-top:0; margin-bottom:20px;}
+
+img.left,img.center,img.right{padding:4px; border:1px solid #a0a0a0;}
+img.left{float:left; margin:0 12px 5px 0;}
+img.center{display:block; margin:0 auto 5px auto;}
+img.right{float:right; margin:0 0 5px 12px;}
+
+/**************** Header and navigation styles ****************/
+
+#container{
+width:100%;
+min-width: 800px;
+margin:5px auto;
+padding:1px 0;
+text-align:left;
+background:#ffffff;
+color:#303030;
+border:2px solid #a0a0a0;
+}
+
+#header{
+height:5.5em;
+/*width:758px;*/
+margin:0 1px 1px 1px;
+background:#467aa7;
+color:#ffffff;
+}
+
+#header h1{
+padding:14px 0 0 20px;
+font-size:2.4em;
+background-color:inherit;
+color:#fff; /*rgb(152, 26, 33);*/
+letter-spacing:-2px;
+font-weight:normal;
+}
+
+#header h2{
+margin:10px 0 0 40px;
+font-size:1.4em;
+background-color:inherit;
+color:#f0f2f4;
+letter-spacing:-1px;
+font-weight:normal;
+}
+
+#navigation{
+height:2.2em;
+line-height:2.2em;
+/*width:758px;*/
+margin:0 1px;
+background:#578bb8;
+color:#ffffff;
+}
+
+#navigation li{
+float:left;
+list-style-type:none;
+border-right:1px solid #ffffff;
+white-space:nowrap;
+}
+
+#navigation li.right {
+ float:right;
+list-style-type:none;
+border-right:0;
+border-left:1px solid #ffffff;
+white-space:nowrap;
+}
+
+#navigation li a{
+display:block;
+padding:0px 10px 0px 22px;
+font-size:0.8em;
+font-weight:normal;
+/*text-transform:uppercase;*/
+text-decoration:none;
+background-color:inherit;
+color: #ffffff;
+}
+
+* html #navigation a {width:1%;}
+
+#navigation .selected,#navigation a:hover{
+color:#ffffff;
+text-decoration:none;
+background-color: #80b0da;
+}
+
+/**************** Icons links *******************/
+.picHome { background: url(../images/home.png) no-repeat 4px 50%; }
+.picUser { background: url(../images/user.png) no-repeat 4px 50%; }
+.picUserPage { background: url(../images/user_page.png) no-repeat 4px 50%; }
+.picAdmin { background: url(../images/admin.png) no-repeat 4px 50%; }
+.picProject { background: url(../images/projects.png) no-repeat 4px 50%; }
+.picLogout { background: url(../images/logout.png) no-repeat 4px 50%; }
+.picHelp { background: url(../images/help.png) no-repeat 4px 50%; }
+
+/**************** Content styles ****************/
+
+#content{
+/*float:right;*/
+/*width:530px;*/
+width: auto;
+min-height: 500px;
+font-size:0.9em;
+padding:20px 10px 10px 20px;
+/*position: absolute;*/
+margin: 0 0 0 140px;
+border-left: 1px dashed #c0c0c0;
+
+}
+
+#content h2{
+display:block;
+margin:0 0 16px 0;
+font-size:1.7em;
+font-weight:normal;
+letter-spacing:-1px;
+color:#505050;
+background-color:inherit;
+}
+
+#content h2 a{font-weight:normal;}
+#content h3{margin:0 0 5px 0; font-size:1.4em; letter-spacing:-1px;}
+#content a:hover,#subcontent a:hover{text-decoration:underline;}
+#content ul,#content ol{margin:0 5px 16px 35px;}
+#content dl{margin:0 5px 10px 25px;}
+#content dt{font-weight:bold; margin-bottom:5px;}
+#content dd{margin:0 0 10px 15px;}
+
+
+/***********************************************/
+
+/*
+form{
+ padding:15px;
+ margin:0 0 20px 0;
+ border:1px solid #c0c0c0;
+ background-color:#CEE1ED;
+ width:600px;
+}
+*/
+
+form {
+ display: inline;
+}
+
+.noborder {
+ border:0px;
+ background-color:#fff;
+ width:100%;
+}
+
+input {
+ vertical-align: top;
+}
+
+input.button-small
+{
+ font-size: 0.8em;
+}
+
+label {
+ font-weight: bold;
+ font-size: 1em;
+}
+
+.required {
+ color: #bb0000;
+}
+
+table.listTableContent {
+ /*margin: 2em 2em 2em 0; */
+ border:1px solid #c0c0c0;
+ width:99%;
+}
+
+table.listTableContent td {
+ margin: 2px;
+
+}
+
+tr.ListHead {
+ background-color:#467aa7;
+ color:#FFFFFF;
+ text-align:center;
+}
+
+tr.ListHead a {
+ color:#FFFFFF;
+ text-decoration:underline;
+}
+
+tr.ListLine0 {
+ background-color: #C1E2F7;
+}
+tr.ListLine1 {
+ background-color:#CEE1ED;
+}
+
+hr { border:0px; border-bottom:1px dashed #000000; }
+
+
+/**************** Sidebar styles ****************/
+
+#subcontent{
+float:left;
+clear:both;
+width:130px;
+padding:20px 20px 10px 5px;
+line-height:1.4em;
+}
+
+#subcontent h2{
+display:block;
+margin:0 0 15px 0;
+font-size:1.6em;
+font-weight:normal;
+text-align:left;
+letter-spacing:-1px;
+color:#505050;
+background-color:inherit;
+}
+
+#subcontent p{margin:0 0 16px 0; font-size:0.9em;}
+
+/**************** Menublock styles ****************/
+
+.menublock{margin:0 0 20px 8px; font-size:0.9em;}
+.menublock li{list-style:none; display:block; padding:1px; margin-bottom:0px;}
+.menublock li a{font-weight:bold; text-decoration:none;}
+.menublock li a:hover{text-decoration:none;}
+.menublock li ul{margin:3px 0 3px 15px; font-size:1em; font-weight:normal;}
+.menublock li ul li{margin-bottom:0;}
+.menublock li ul a{font-weight:normal;}
+
+/**************** Searchbar styles ****************/
+
+#searchbar{margin:0 0 20px 0;}
+#searchbar form fieldset{margin-left:10px; border:0 solid;}
+
+#searchbar #s{
+height:1.2em;
+width:110px;
+margin:0 5px 0 0;
+border:1px solid #a0a0a0;
+}
+
+#searchbar #searchbutton{
+width:auto;
+padding:0 1px;
+border:1px solid #808080;
+font-size:0.9em;
+text-align:center;
+}
+
+/**************** Footer styles ****************/
+
+#footer{
+clear:both;
+/*width:758px;*/
+padding:5px 0;
+margin:0 1px;
+font-size:0.9em;
+color:#f0f0f0;
+background:#467aa7;
+}
+
+#footer p{padding:0; margin:0; text-align:center;}
+#footer a{color:#f0f0f0; background-color:inherit; font-weight:bold;}
+#footer a:hover{color:#ffffff; background-color:inherit; text-decoration: underline;}
+
+/**************** Misc classes and styles ****************/
+
+.splitcontentleft{float:left; width:49%;}
+.splitcontentright{float:right; width:49%;}
+.clear{clear:both;}
+.small{font-size:0.8em;line-height:1.4em;padding:0 0 0 0;}
+.hide{display:none;}
+.textcenter{text-align:center;}
+.textright{text-align:right;}
+.important{color:#f02025; background-color:inherit; font-weight:bold;}
+
+.box{
+margin:0 0 20px 0;
+padding:10px;
+border:1px solid #c0c0c0;
+background-color:#fafbfc;
+color:#505050;
+line-height:1.5em;
+}
+
+
diff --git a/redmine/public/stylesheets/rails.css b/redmine/public/stylesheets/rails.css new file mode 100644 index 000000000..e2954c9a7 --- /dev/null +++ b/redmine/public/stylesheets/rails.css @@ -0,0 +1,56 @@ + +.fieldWithErrors { + padding: 2px; + background-color: red; + display: table; +} + +#errorExplanation { + width: 400px; + border: 2px solid red; + padding: 7px; + padding-bottom: 12px; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#errorExplanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + background-color: #c00; + color: #fff; +} + +#errorExplanation p { + color: #333; + margin-bottom: 0; + padding: 5px; +} + +#errorExplanation ul li { + font-size: 12px; + list-style: square; +} + +div.uploadStatus { + margin: 5px; +} + +div.progressBar { + margin: 5px; +} + +div.progressBar div.border { + background-color: #fff; + border: 1px solid grey; + width: 100%; +} + +div.progressBar div.background { + background-color: #333; + height: 18px; + width: 0%; +} diff --git a/redmine/script/about b/redmine/script/about new file mode 100644 index 000000000..7b07d46a3 --- /dev/null +++ b/redmine/script/about @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/about'
\ No newline at end of file diff --git a/redmine/script/breakpointer b/redmine/script/breakpointer new file mode 100644 index 000000000..64af76edd --- /dev/null +++ b/redmine/script/breakpointer @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/breakpointer'
\ No newline at end of file diff --git a/redmine/script/console b/redmine/script/console new file mode 100644 index 000000000..42f28f7d6 --- /dev/null +++ b/redmine/script/console @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/console'
\ No newline at end of file diff --git a/redmine/script/destroy b/redmine/script/destroy new file mode 100644 index 000000000..fa0e6fcd0 --- /dev/null +++ b/redmine/script/destroy @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/destroy'
\ No newline at end of file diff --git a/redmine/script/generate b/redmine/script/generate new file mode 100644 index 000000000..ef976e09f --- /dev/null +++ b/redmine/script/generate @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/generate'
\ No newline at end of file diff --git a/redmine/script/performance/benchmarker b/redmine/script/performance/benchmarker new file mode 100644 index 000000000..c842d35d3 --- /dev/null +++ b/redmine/script/performance/benchmarker @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/benchmarker' diff --git a/redmine/script/performance/profiler b/redmine/script/performance/profiler new file mode 100644 index 000000000..d855ac8b1 --- /dev/null +++ b/redmine/script/performance/profiler @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/profiler' diff --git a/redmine/script/plugin b/redmine/script/plugin new file mode 100644 index 000000000..26ca64c06 --- /dev/null +++ b/redmine/script/plugin @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/plugin'
\ No newline at end of file diff --git a/redmine/script/process/reaper b/redmine/script/process/reaper new file mode 100644 index 000000000..c77f04535 --- /dev/null +++ b/redmine/script/process/reaper @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/reaper' diff --git a/redmine/script/process/spawner b/redmine/script/process/spawner new file mode 100644 index 000000000..7118f3983 --- /dev/null +++ b/redmine/script/process/spawner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spawner' diff --git a/redmine/script/process/spinner b/redmine/script/process/spinner new file mode 100644 index 000000000..6816b32ef --- /dev/null +++ b/redmine/script/process/spinner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spinner' diff --git a/redmine/script/runner b/redmine/script/runner new file mode 100644 index 000000000..ccc30f9d2 --- /dev/null +++ b/redmine/script/runner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner'
\ No newline at end of file diff --git a/redmine/script/server b/redmine/script/server new file mode 100644 index 000000000..dfabcb881 --- /dev/null +++ b/redmine/script/server @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server'
\ No newline at end of file diff --git a/redmine/test/fixtures/attachments.yml b/redmine/test/fixtures/attachments.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/attachments.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/custom_fields.yml b/redmine/test/fixtures/custom_fields.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/custom_fields.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/documents.yml b/redmine/test/fixtures/documents.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/documents.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/enumerations.yml b/redmine/test/fixtures/enumerations.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/enumerations.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/issue_categories.yml b/redmine/test/fixtures/issue_categories.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/issue_categories.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/issue_custom_fields.yml b/redmine/test/fixtures/issue_custom_fields.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/issue_custom_fields.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/issue_custom_values.yml b/redmine/test/fixtures/issue_custom_values.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/issue_custom_values.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/issue_histories.yml b/redmine/test/fixtures/issue_histories.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/issue_histories.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/issue_statuses.yml b/redmine/test/fixtures/issue_statuses.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/issue_statuses.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/issues.yml b/redmine/test/fixtures/issues.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/issues.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/mailer/issue_closed b/redmine/test/fixtures/mailer/issue_closed new file mode 100644 index 000000000..bb5e51d95 --- /dev/null +++ b/redmine/test/fixtures/mailer/issue_closed @@ -0,0 +1,3 @@ +Mailer#issue_closed + +Find me in app/views/mailer/issue_closed.rhtml diff --git a/redmine/test/fixtures/members.yml b/redmine/test/fixtures/members.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/members.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/news.yml b/redmine/test/fixtures/news.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/news.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/permissions.yml b/redmine/test/fixtures/permissions.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/permissions.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/projects.yml b/redmine/test/fixtures/projects.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/projects.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/roles.yml b/redmine/test/fixtures/roles.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/roles.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/trackers.yml b/redmine/test/fixtures/trackers.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/trackers.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/users.yml b/redmine/test/fixtures/users.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/users.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/versions.yml b/redmine/test/fixtures/versions.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/versions.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/workflow.yml b/redmine/test/fixtures/workflow.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/workflow.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/functional/account_controller_test.rb b/redmine/test/functional/account_controller_test.rb new file mode 100644 index 000000000..537eb8ffc --- /dev/null +++ b/redmine/test/functional/account_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'account_controller' + +# Re-raise errors caught by the controller. +class AccountController; def rescue_action(e) raise e end; end + +class AccountControllerTest < Test::Unit::TestCase + def setup + @controller = AccountController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/functional/admin_controller_test.rb b/redmine/test/functional/admin_controller_test.rb new file mode 100644 index 000000000..e44ac9423 --- /dev/null +++ b/redmine/test/functional/admin_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'admin_controller' + +# Re-raise errors caught by the controller. +class AdminController; def rescue_action(e) raise e end; end + +class AdminControllerTest < Test::Unit::TestCase + def setup + @controller = AdminController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/functional/custom_fields_controller_test.rb b/redmine/test/functional/custom_fields_controller_test.rb new file mode 100644 index 000000000..f86e32569 --- /dev/null +++ b/redmine/test/functional/custom_fields_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'custom_fields_controller' + +# Re-raise errors caught by the controller. +class CustomFieldsController; def rescue_action(e) raise e end; end + +class CustomFieldsControllerTest < Test::Unit::TestCase + fixtures :custom_fields + + def setup + @controller = CustomFieldsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:custom_fields) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:custom_field) + assert assigns(:custom_field).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:custom_field) + end + + def test_create + num_custom_fields = CustomField.count + + post :create, :custom_field => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_custom_fields + 1, CustomField.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:custom_field) + assert assigns(:custom_field).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil CustomField.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + CustomField.find(1) + } + end +end diff --git a/redmine/test/functional/documents_controller_test.rb b/redmine/test/functional/documents_controller_test.rb new file mode 100644 index 000000000..c9bd463d3 --- /dev/null +++ b/redmine/test/functional/documents_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'documents_controller' + +# Re-raise errors caught by the controller. +class DocumentsController; def rescue_action(e) raise e end; end + +class DocumentsControllerTest < Test::Unit::TestCase + fixtures :documents + + def setup + @controller = DocumentsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:documents) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:document) + assert assigns(:document).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:document) + end + + def test_create + num_documents = Document.count + + post :create, :document => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_documents + 1, Document.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:document) + assert assigns(:document).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Document.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Document.find(1) + } + end +end diff --git a/redmine/test/functional/enumerations_controller_test.rb b/redmine/test/functional/enumerations_controller_test.rb new file mode 100644 index 000000000..e9f4b7660 --- /dev/null +++ b/redmine/test/functional/enumerations_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'enumerations_controller' + +# Re-raise errors caught by the controller. +class EnumerationsController; def rescue_action(e) raise e end; end + +class EnumerationsControllerTest < Test::Unit::TestCase + fixtures :enumerations + + def setup + @controller = EnumerationsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:enumerations) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:enumeration) + assert assigns(:enumeration).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:enumeration) + end + + def test_create + num_enumerations = Enumeration.count + + post :create, :enumeration => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_enumerations + 1, Enumeration.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:enumeration) + assert assigns(:enumeration).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Enumeration.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Enumeration.find(1) + } + end +end diff --git a/redmine/test/functional/help_controller_test.rb b/redmine/test/functional/help_controller_test.rb new file mode 100644 index 000000000..291838b8f --- /dev/null +++ b/redmine/test/functional/help_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'help_controller' + +# Re-raise errors caught by the controller. +class HelpController; def rescue_action(e) raise e end; end + +class HelpControllerTest < Test::Unit::TestCase + def setup + @controller = HelpController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/functional/issue_categories_controller_test.rb b/redmine/test/functional/issue_categories_controller_test.rb new file mode 100644 index 000000000..4ea4e1c55 --- /dev/null +++ b/redmine/test/functional/issue_categories_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'issue_categories_controller' + +# Re-raise errors caught by the controller. +class IssueCategoriesController; def rescue_action(e) raise e end; end + +class IssueCategoriesControllerTest < Test::Unit::TestCase + fixtures :issue_categories + + def setup + @controller = IssueCategoriesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:issue_categories) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:issue_category) + assert assigns(:issue_category).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:issue_category) + end + + def test_create + num_issue_categories = IssueCategory.count + + post :create, :issue_category => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_issue_categories + 1, IssueCategory.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:issue_category) + assert assigns(:issue_category).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil IssueCategory.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + IssueCategory.find(1) + } + end +end diff --git a/redmine/test/functional/issue_statuses_controller_test.rb b/redmine/test/functional/issue_statuses_controller_test.rb new file mode 100644 index 000000000..17f11ef55 --- /dev/null +++ b/redmine/test/functional/issue_statuses_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'issue_statuses_controller' + +# Re-raise errors caught by the controller. +class IssueStatusesController; def rescue_action(e) raise e end; end + +class IssueStatusesControllerTest < Test::Unit::TestCase + fixtures :issue_statuses + + def setup + @controller = IssueStatusesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:issue_statuses) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:issue_status) + assert assigns(:issue_status).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:issue_status) + end + + def test_create + num_issue_statuses = IssueStatus.count + + post :create, :issue_status => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_issue_statuses + 1, IssueStatus.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:issue_status) + assert assigns(:issue_status).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil IssueStatus.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + IssueStatus.find(1) + } + end +end diff --git a/redmine/test/functional/issues_controller_test.rb b/redmine/test/functional/issues_controller_test.rb new file mode 100644 index 000000000..1be41f86a --- /dev/null +++ b/redmine/test/functional/issues_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'issues_controller' + +# Re-raise errors caught by the controller. +class IssuesController; def rescue_action(e) raise e end; end + +class IssuesControllerTest < Test::Unit::TestCase + fixtures :issues + + def setup + @controller = IssuesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:issues) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:issue) + assert assigns(:issue).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:issue) + end + + def test_create + num_issues = Issue.count + + post :create, :issue => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_issues + 1, Issue.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:issue) + assert assigns(:issue).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Issue.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Issue.find(1) + } + end +end diff --git a/redmine/test/functional/members_controller_test.rb b/redmine/test/functional/members_controller_test.rb new file mode 100644 index 000000000..5f47c358d --- /dev/null +++ b/redmine/test/functional/members_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'members_controller' + +# Re-raise errors caught by the controller. +class MembersController; def rescue_action(e) raise e end; end + +class MembersControllerTest < Test::Unit::TestCase + fixtures :members + + def setup + @controller = MembersController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:members) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:member) + assert assigns(:member).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:member) + end + + def test_create + num_members = Member.count + + post :create, :member => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_members + 1, Member.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:member) + assert assigns(:member).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Member.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Member.find(1) + } + end +end diff --git a/redmine/test/functional/news_controller_test.rb b/redmine/test/functional/news_controller_test.rb new file mode 100644 index 000000000..b360c6cb3 --- /dev/null +++ b/redmine/test/functional/news_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'news_controller' + +# Re-raise errors caught by the controller. +class NewsController; def rescue_action(e) raise e end; end + +class NewsControllerTest < Test::Unit::TestCase + fixtures :news + + def setup + @controller = NewsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:news) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:news) + assert assigns(:news).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:news) + end + + def test_create + num_news = News.count + + post :create, :news => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_news + 1, News.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:news) + assert assigns(:news).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil News.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + News.find(1) + } + end +end diff --git a/redmine/test/functional/projects_controller_test.rb b/redmine/test/functional/projects_controller_test.rb new file mode 100644 index 000000000..8da34ec7e --- /dev/null +++ b/redmine/test/functional/projects_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'projects_controller' + +# Re-raise errors caught by the controller. +class ProjectsController; def rescue_action(e) raise e end; end + +class ProjectsControllerTest < Test::Unit::TestCase + fixtures :projects + + def setup + @controller = ProjectsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:projects) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:project) + assert assigns(:project).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:project) + end + + def test_create + num_projects = Project.count + + post :create, :project => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_projects + 1, Project.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:project) + assert assigns(:project).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Project.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Project.find(1) + } + end +end diff --git a/redmine/test/functional/reports_controller_test.rb b/redmine/test/functional/reports_controller_test.rb new file mode 100644 index 000000000..4b52ffab3 --- /dev/null +++ b/redmine/test/functional/reports_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'reports_controller' + +# Re-raise errors caught by the controller. +class ReportsController; def rescue_action(e) raise e end; end + +class ReportsControllerTest < Test::Unit::TestCase + def setup + @controller = ReportsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/functional/roles_controller_test.rb b/redmine/test/functional/roles_controller_test.rb new file mode 100644 index 000000000..299aef211 --- /dev/null +++ b/redmine/test/functional/roles_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'roles_controller' + +# Re-raise errors caught by the controller. +class RolesController; def rescue_action(e) raise e end; end + +class RolesControllerTest < Test::Unit::TestCase + fixtures :roles + + def setup + @controller = RolesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:roles) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:role) + assert assigns(:role).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:role) + end + + def test_create + num_roles = Role.count + + post :create, :role => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_roles + 1, Role.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:role) + assert assigns(:role).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Role.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Role.find(1) + } + end +end diff --git a/redmine/test/functional/trackers_controller_test.rb b/redmine/test/functional/trackers_controller_test.rb new file mode 100644 index 000000000..75063c6c5 --- /dev/null +++ b/redmine/test/functional/trackers_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'trackers_controller' + +# Re-raise errors caught by the controller. +class TrackersController; def rescue_action(e) raise e end; end + +class TrackersControllerTest < Test::Unit::TestCase + fixtures :trackers + + def setup + @controller = TrackersController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:trackers) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:tracker) + assert assigns(:tracker).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:tracker) + end + + def test_create + num_trackers = Tracker.count + + post :create, :tracker => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_trackers + 1, Tracker.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:tracker) + assert assigns(:tracker).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Tracker.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Tracker.find(1) + } + end +end diff --git a/redmine/test/functional/users_controller_test.rb b/redmine/test/functional/users_controller_test.rb new file mode 100644 index 000000000..f1e22817b --- /dev/null +++ b/redmine/test/functional/users_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'users_controller' + +# Re-raise errors caught by the controller. +class UsersController; def rescue_action(e) raise e end; end + +class UsersControllerTest < Test::Unit::TestCase + fixtures :users + + def setup + @controller = UsersController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:users) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:user) + assert assigns(:user).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:user) + end + + def test_create + num_users = User.count + + post :create, :user => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_users + 1, User.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:user) + assert assigns(:user).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil User.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + User.find(1) + } + end +end diff --git a/redmine/test/functional/versions_controller_test.rb b/redmine/test/functional/versions_controller_test.rb new file mode 100644 index 000000000..85b2ef76c --- /dev/null +++ b/redmine/test/functional/versions_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'versions_controller' + +# Re-raise errors caught by the controller. +class VersionsController; def rescue_action(e) raise e end; end + +class VersionsControllerTest < Test::Unit::TestCase + fixtures :versions + + def setup + @controller = VersionsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:versions) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:version) + assert assigns(:version).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:version) + end + + def test_create + num_versions = Version.count + + post :create, :version => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_versions + 1, Version.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:version) + assert assigns(:version).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Version.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Version.find(1) + } + end +end diff --git a/redmine/test/functional/welcome_controller_test.rb b/redmine/test/functional/welcome_controller_test.rb new file mode 100644 index 000000000..d773945e1 --- /dev/null +++ b/redmine/test/functional/welcome_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'welcome_controller' + +# Re-raise errors caught by the controller. +class WelcomeController; def rescue_action(e) raise e end; end + +class WelcomeControllerTest < Test::Unit::TestCase + def setup + @controller = WelcomeController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/test_helper.rb b/redmine/test/test_helper.rb new file mode 100644 index 000000000..a299c7f6d --- /dev/null +++ b/redmine/test/test_helper.rb @@ -0,0 +1,28 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require 'test_help' + +class Test::Unit::TestCase + # Transactional fixtures accelerate your tests by wrapping each test method + # in a transaction that's rolled back on completion. This ensures that the + # test database remains unchanged so your fixtures don't have to be reloaded + # between every test method. Fewer database queries means faster tests. + # + # Read Mike Clark's excellent walkthrough at + # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting + # + # Every Active Record database supports transactions except MyISAM tables + # in MySQL. Turn off transactional fixtures in this case; however, if you + # don't care one way or the other, switching from MyISAM to InnoDB tables + # is recommended. + self.use_transactional_fixtures = true + + # Instantiated fixtures are slow, but give you @david where otherwise you + # would need people(:david). If you don't want to migrate your existing + # test cases which use the @david style and don't mind the speed hit (each + # instantiated fixtures translates to a database query per test method), + # then set this back to true. + self.use_instantiated_fixtures = false + + # Add more helper methods to be used by all tests here... +end diff --git a/redmine/test/unit/attachment_test.rb b/redmine/test/unit/attachment_test.rb new file mode 100644 index 000000000..6f66833d3 --- /dev/null +++ b/redmine/test/unit/attachment_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class AttachmentTest < Test::Unit::TestCase + fixtures :attachments + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/unit/custom_field_test.rb b/redmine/test/unit/custom_field_test.rb new file mode 100644 index 000000000..886bd517f --- /dev/null +++ b/redmine/test/unit/custom_field_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class CustomFieldTest < Test::Unit::TestCase + fixtures :custom_fields + + # Replace this with your real tests. + def test_truth + assert_kind_of CustomField, custom_fields(:first) + end +end diff --git a/redmine/test/unit/document_test.rb b/redmine/test/unit/document_test.rb new file mode 100644 index 000000000..acd96ddba --- /dev/null +++ b/redmine/test/unit/document_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class DocumentTest < Test::Unit::TestCase + fixtures :documents + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/unit/enumeration_test.rb b/redmine/test/unit/enumeration_test.rb new file mode 100644 index 000000000..ea8c01405 --- /dev/null +++ b/redmine/test/unit/enumeration_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class EnumerationTest < Test::Unit::TestCase + fixtures :enumerations + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/unit/issue_category_test.rb b/redmine/test/unit/issue_category_test.rb new file mode 100644 index 000000000..6f5051be9 --- /dev/null +++ b/redmine/test/unit/issue_category_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class IssueCategoryTest < Test::Unit::TestCase + fixtures :issue_categories + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/unit/issue_custom_field_test.rb b/redmine/test/unit/issue_custom_field_test.rb new file mode 100644 index 000000000..2adee1061 --- /dev/null +++ b/redmine/test/unit/issue_custom_field_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class IssueCustomFieldTest < Test::Unit::TestCase + fixtures :issue_custom_fields + + # Replace this with your real tests. + def test_truth + assert_kind_of IssueCustomField, issue_custom_fields(:first) + end +end diff --git a/redmine/test/unit/issue_custom_value_test.rb b/redmine/test/unit/issue_custom_value_test.rb new file mode 100644 index 000000000..09c0551b6 --- /dev/null +++ b/redmine/test/unit/issue_custom_value_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class IssueCustomValueTest < Test::Unit::TestCase + fixtures :issue_custom_values + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/unit/issue_history_test.rb b/redmine/test/unit/issue_history_test.rb new file mode 100644 index 000000000..3da38e73c --- /dev/null +++ b/redmine/test/unit/issue_history_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class IssueHistoryTest < Test::Unit::TestCase + fixtures :issue_histories + + # Replace this with your real tests. + def test_truth + assert_kind_of IssueHistory, issue_histories(:first) + end +end diff --git a/redmine/test/unit/issue_status_test.rb b/redmine/test/unit/issue_status_test.rb new file mode 100644 index 000000000..8e7c0b97b --- /dev/null +++ b/redmine/test/unit/issue_status_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class IssueStatusTest < Test::Unit::TestCase + fixtures :issue_statuses + + # Replace this with your real tests. + def test_truth + assert_kind_of IssueStatus, issue_statuses(:first) + end +end diff --git a/redmine/test/unit/issue_test.rb b/redmine/test/unit/issue_test.rb new file mode 100644 index 000000000..3b318778e --- /dev/null +++ b/redmine/test/unit/issue_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class IssueTest < Test::Unit::TestCase + fixtures :issues + + # Replace this with your real tests. + def test_truth + assert_kind_of Issue, issues(:first) + end +end diff --git a/redmine/test/unit/mailer_test.rb b/redmine/test/unit/mailer_test.rb new file mode 100644 index 000000000..70615d712 --- /dev/null +++ b/redmine/test/unit/mailer_test.rb @@ -0,0 +1,35 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'mailer' + +class MailerTest < Test::Unit::TestCase + FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures' + CHARSET = "utf-8" + + include ActionMailer::Quoting + + def setup + ActionMailer::Base.delivery_method = :test + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries = [] + + @expected = TMail::Mail.new + @expected.set_content_type "text", "plain", { "charset" => CHARSET } + end + + def test_issue_closed + @expected.subject = 'Mailer#issue_closed' + @expected.body = read_fixture('issue_closed') + @expected.date = Time.now + + assert_equal @expected.encoded, Mailer.create_issue_closed(@expected.date).encoded + end + + private + def read_fixture(action) + IO.readlines("#{FIXTURES_PATH}/mailer/#{action}") + end + + def encode(subject) + quoted_printable(subject, CHARSET) + end +end diff --git a/redmine/test/unit/member_test.rb b/redmine/test/unit/member_test.rb new file mode 100644 index 000000000..443c096ec --- /dev/null +++ b/redmine/test/unit/member_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class MemberTest < Test::Unit::TestCase + fixtures :members + + # Replace this with your real tests. + def test_truth + assert_kind_of Member, members(:first) + end +end diff --git a/redmine/test/unit/news_test.rb b/redmine/test/unit/news_test.rb new file mode 100644 index 000000000..db9b3ab0d --- /dev/null +++ b/redmine/test/unit/news_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class NewsTest < Test::Unit::TestCase + fixtures :news + + # Replace this with your real tests. + def test_truth + assert_kind_of News, news(:first) + end +end diff --git a/redmine/test/unit/packages_test.rb b/redmine/test/unit/packages_test.rb new file mode 100644 index 000000000..a5ebcdf1b --- /dev/null +++ b/redmine/test/unit/packages_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class PackagesTest < Test::Unit::TestCase + fixtures :packages + + # Replace this with your real tests. + def test_truth + assert_kind_of Packages, packages(:first) + end +end diff --git a/redmine/test/unit/permission_test.rb b/redmine/test/unit/permission_test.rb new file mode 100644 index 000000000..306c2e61c --- /dev/null +++ b/redmine/test/unit/permission_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class PermissionTest < Test::Unit::TestCase + fixtures :permissions + + # Replace this with your real tests. + def test_truth + assert_kind_of Permission, permissions(:first) + end +end diff --git a/redmine/test/unit/project_test.rb b/redmine/test/unit/project_test.rb new file mode 100644 index 000000000..8a9912576 --- /dev/null +++ b/redmine/test/unit/project_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ProjectTest < Test::Unit::TestCase + fixtures :projects + + # Replace this with your real tests. + def test_truth + assert_kind_of Project, projects(:first) + end +end diff --git a/redmine/test/unit/role_test.rb b/redmine/test/unit/role_test.rb new file mode 100644 index 000000000..90565ae80 --- /dev/null +++ b/redmine/test/unit/role_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class RoleTest < Test::Unit::TestCase + fixtures :roles + + # Replace this with your real tests. + def test_truth + assert_kind_of Role, roles(:first) + end +end diff --git a/redmine/test/unit/tracker_test.rb b/redmine/test/unit/tracker_test.rb new file mode 100644 index 000000000..f738f288b --- /dev/null +++ b/redmine/test/unit/tracker_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class TrackerTest < Test::Unit::TestCase + fixtures :trackers + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/unit/user_test.rb b/redmine/test/unit/user_test.rb new file mode 100644 index 000000000..d6a2a2245 --- /dev/null +++ b/redmine/test/unit/user_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class UserTest < Test::Unit::TestCase + fixtures :users + + # Replace this with your real tests. + def test_truth + assert_kind_of User, users(:first) + end +end diff --git a/redmine/test/unit/version_test.rb b/redmine/test/unit/version_test.rb new file mode 100644 index 000000000..91c52d4a0 --- /dev/null +++ b/redmine/test/unit/version_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class VersionTest < Test::Unit::TestCase + fixtures :versions + + # Replace this with your real tests. + def test_truth + assert_kind_of Version, versions(:first) + end +end diff --git a/redmine/test/unit/workflow_test.rb b/redmine/test/unit/workflow_test.rb new file mode 100644 index 000000000..ff88a9763 --- /dev/null +++ b/redmine/test/unit/workflow_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class WorkflowTest < Test::Unit::TestCase + fixtures :workflows + + # Replace this with your real tests. + def test_truth + assert_kind_of Workflow, workflows(:first) + end +end diff --git a/redmine/vendor/plugins/localization/README b/redmine/vendor/plugins/localization/README new file mode 100644 index 000000000..0996906eb --- /dev/null +++ b/redmine/vendor/plugins/localization/README @@ -0,0 +1,85 @@ += Localization Plugin for Rails
+
+This plugin provides a simple, gettext-like method to
+provide localizations.
+
+== Features
+
+ * Any number of languages or locales
+ * Simple method to defines singluar/plural translations
+ * Can use lambdas to provide Ruby-code based dynamic translations
+ * Customizable for different instances of the application
+
+== Usage
+
+If the localization plugin is installed, it is used automatically.
+
+You need to create a /lang dir in your RAILS_ROOT.
+
+The recommended way to use it is to create files that are named
+like the languages you define in them (but you can put everything in
+one big file too.)
+
+For instance-customizable strings, add overrides in files you
+put in /lang/custom.
+
+=== Simple example:
+
+Create a file /lang/translations.rb:
+
+ Localization.define('de') do |l|
+ l.store 'yes', 'Ja'
+ l.store 'no', 'Nein'
+ end
+
+ Localization.define('fr') do |l|
+ l.store 'yes', 'oui'
+ l.store 'no', 'non'
+ end
+
+In your controller or application.rb:
+
+ Localization.lang = 'de' # or 'fr'
+
+In your view:
+
+ <%=_ 'yes' %>
+ <%=_ 'no' %>
+
+Because the _ method is simply an extension to Object, you
+can use it anywhere (models/controllers/views/libs).
+
+=== Extended example:
+
+Create a file /lang/default.rb with following contents:
+
+ Localization.define do |l|
+ l.store '(time)', lambda { |t| t.strftime('%I:%M%p') }
+ end
+
+Create a file /lang/de_DE.rb with following contents:
+
+ Localization.define('de_DE') do |l|
+ l.store '%d entries', ['Ein Eintrag', '%d Einträge']
+ l.store '(time)', lambda { |t| t.strftime('%H:%M') }
+ end
+
+In your controller or application.rb:
+
+ Localization.lang = 'de_DE'
+
+In your view:
+
+ <%=_ '%d entries', 1 %> # singular variant is chosen
+ <%=_ '%d entries', 4 %> # plural variant is chosen
+ <%=_ '(time)', Time.now %> # call the block with a parameter
+
+== Translation file guesstimation
+
+You can generate a guesstimation of all strings needed to be
+translated in your views by first adding the _('blah') syntax
+everywhere and then calling:
+
+ puts Localization.generate_l10n_file
+
+in the Rails console.
\ No newline at end of file diff --git a/redmine/vendor/plugins/localization/init.rb b/redmine/vendor/plugins/localization/init.rb new file mode 100644 index 000000000..72ed7b9ce --- /dev/null +++ b/redmine/vendor/plugins/localization/init.rb @@ -0,0 +1,3 @@ +require "#{directory}/lib/localization.rb" + +Localization.load
\ No newline at end of file diff --git a/redmine/vendor/plugins/localization/lib/localization.rb b/redmine/vendor/plugins/localization/lib/localization.rb new file mode 100644 index 000000000..5fe0b1ed6 --- /dev/null +++ b/redmine/vendor/plugins/localization/lib/localization.rb @@ -0,0 +1,57 @@ +# Original Localization plugin for Rails can be found at:
+# http://mir.aculo.us/articles/2005/10/03/ruby-on-rails-i18n-revisited
+#
+# Slightly edited by Jean-Philippe Lang
+# - added @@langs and modified self.define method to maintain an array of available
+# langages with labels, eg. { 'en' => 'English, 'fr' => 'French }
+# - modified self.generate_l10n_file method to retrieve already translated strings
+#
+
+module Localization + mattr_accessor :lang, :langs + + @@l10s = { :default => {} } + @@lang = :default
+ @@langs = []
+ + def self._(string_to_localize, *args) + translated = @@l10s[@@lang][string_to_localize] || string_to_localize + return translated.call(*args).to_s if translated.is_a? Proc + if translated.is_a? Array + translated = if translated.size == 3 + translated[args[0]==0 ? 0 : (args[0]>1 ? 2 : 1)] + else + translated[args[0]>1 ? 1 : 0] + end + end + sprintf translated, *args + end + + def self.define(lang = :default, name = :default) + @@l10s[lang] ||= {}
+ @@langs << [ name, lang ]
+ yield @@l10s[lang] + end + + def self.load + Dir.glob("#{RAILS_ROOT}/lang/*.rb"){ |t| require t } + Dir.glob("#{RAILS_ROOT}/lang/custom/*.rb"){ |t| require t } + end + + # Generates a best-estimate l10n file from all views by + # collecting calls to _() -- note: use the generated file only + # as a start (this method is only guesstimating) + def self.generate_l10n_file(lang) + "Localization.define('en_US') do |l|\n" << + Dir.glob("#{RAILS_ROOT}/app/views/**/*.rhtml").collect do |f| + ["# #{f}"] << File.read(f).scan(/<%.*[^\w]_\s*[\(]+[\"\'](.*?)[\"\'][\)]+/) + end.uniq.flatten.collect do |g| + g.starts_with?('#') ? "\n #{g}" : " l.store '#{g}', '#{@@l10s[lang][g]}'" + end.uniq.join("\n") << "\nend" + end + +end + +class Object + def _(*args); Localization._(*args); end +end
\ No newline at end of file |