diff --git a/redmine/Rakefile b/redmine/Rakefile new file mode 100644 index 0000000..cffd19f --- /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 0000000..d5c98f5 --- /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 0000000..fa34baf --- /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 0000000..a9dd6b8 --- /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 0000000..93f6353 --- /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 0000000..3c76465 --- /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 0000000..01664c8 --- /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 0000000..4b555d5 --- /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 0000000..cf0f9dc --- /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 0000000..3de8158 --- /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 0000000..5d5872f --- /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 0000000..ac6b08d --- /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 0000000..065336f --- /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 0000000..2e74045 --- /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 0000000..7dd57cc --- /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 0000000..6e4fc74 --- /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 0000000..38cdb6c --- /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 0000000..64c9fd3 --- /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 0000000..c4fd241 --- /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 0000000..b266975 --- /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 0000000..e18ab6f --- /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 0000000..db27773 --- /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 0000000..4a50b94 --- /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 0000000..4e3aea5 --- /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 0000000..c989764 --- /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 0000000..11a216a --- /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 0000000..bb62931 --- /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 0000000..997d830 --- /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 0000000..17704b7 --- /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 0000000..40c0e40 --- /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 0000000..8bf9091 --- /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 0000000..f4a633f --- /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 0000000..0c85ce2 --- /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 0000000..ed7fd78 --- /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 0000000..8ae3390 --- /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 0000000..3a76b3f --- /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 0000000..bec2117 --- /dev/null +++ b/redmine/app/helpers/sort_helper.rb @@ -0,0 +1,157 @@ +# Helpers to sort tables using clickable column headers. +# +# Author: Stuart Rackham , 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): +# +# +# +# <%= 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) %> +# +# +# +# - 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 '_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 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: + # + # + # Id + #   Sort_asc + # + # + 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 0000000..839327e --- /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 0000000..035db3d --- /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 0000000..e2724fe --- /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 0000000..cace5f5 --- /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 0000000..bc1ff5d --- /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 0000000..9e817d1 --- /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 0000000..faaa8ff --- /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 0000000..40c3a16 --- /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 0000000..d93db44 --- /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 0000000..4a21ac0 --- /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 0000000..b7d4e26 --- /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 0000000..f410a39 --- /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 0000000..ff34cb6 --- /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 0000000..b04ec7e --- /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 0000000..d379365 --- /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 0000000..0642a4b --- /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 0000000..f662141 --- /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 0000000..7c50ee8 --- /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 0000000..ce880b5 --- /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 0000000..6b123d7 --- /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 0000000..1bc1b58 --- /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 0000000..02dee15 --- /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 0000000..212e333 --- /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 0000000..cc360eb --- /dev/null +++ b/redmine/app/views/account/login.rhtml @@ -0,0 +1,13 @@ +
+

<%=_ 'Please login' %>

+ +<%= start_form_tag :action=> "login" %> +


+

+ +


+

+ +

+<%= end_form_tag %> +
\ 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 0000000..34ae488 --- /dev/null +++ b/redmine/app/views/account/my_account.rhtml @@ -0,0 +1,54 @@ +

<%=_('My account')%>

+ +

<%=_('Login')%>: <%= @user.login %>
+<%=_('Created on')%>: <%= format_time(@user.created_on) %>, +<%=_('Last update')%>: <%= format_time(@user.updated_on) %>

+ +
+
+

<%=_('Information')%>

+   + <%= start_form_tag :action => 'my_account' %> + <%= error_messages_for 'user' %> + + +


+ <%= text_field 'user', 'firstname' %>

+ +


+ <%= text_field 'user', 'lastname' %>

+ +


+ <%= text_field 'user', 'mail' %>

+ +


+ <%= select("user", "language", Localization.langs) %>

+ + +

<%= check_box 'user', 'mail_notification' %>

+ +
<%= submit_tag _('Save') %>
+ <%= end_form_tag %> +
+
+ + +
+
+

<%=_('Password')%>

+   + <%= start_form_tag :action => 'change_password' %> + +


+ <%= password_field_tag 'old_password' %>

+ +


+ <%= password_field_tag 'new_password' %>

+ +


+ <%= password_field_tag 'new_password_confirmation' %>

+ +
<%= submit_tag _('Save') %>
+ <%= end_form_tag %> +
+
\ 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 0000000..7f64582 --- /dev/null +++ b/redmine/app/views/account/my_page.rhtml @@ -0,0 +1,19 @@ +

<%=_('My page') %>

+ +

+<%=_('Welcome')%> <%= @user.firstname %>
+<% unless @user.last_before_login_on.nil? %> + <%=_('Last login')%>: <%= format_time(@user.last_before_login_on) %> +<% end %> +

+ +
+

<%=_('Reported issues')%>

+ <%= render :partial => 'issues/list_simple', :locals => { :issues => @reported_issues } %> + <%= "

(Last #{@reported_issues.length} updated)

" if @reported_issues.length > 0 %> +
+
+

<%=_('Assigned to me')%>

+ <%= render :partial => 'issues/list_simple', :locals => { :issues => @assigned_issues } %> + <%= "

(Last #{@assigned_issues.length} updated)

" if @assigned_issues.length > 0 %> +
\ 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 0000000..df918e5 --- /dev/null +++ b/redmine/app/views/account/show.rhtml @@ -0,0 +1,19 @@ +

<%= @user.display_name %>

+ +

+<%= mail_to @user.mail %>
+<%=_('Registered on')%>: <%= format_date(@user.created_on) %> +

+ +

<%=_('Projects')%>

+

+<% for membership in @user.memberships %> + <%= membership.project.name %> (<%= membership.role.name %>, <%= format_date(membership.created_on) %>) +
+<% end %> +

+ +

<%=_('Activity')%>

+

+<%=_('Reported issues')%>: <%= Issue.count( [ "author_id=?", @user.id]) %> +

\ 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 0000000..b3607d8 --- /dev/null +++ b/redmine/app/views/admin/index.rhtml @@ -0,0 +1,45 @@ +

<%=_('Administration')%>

+ +

+<%= image_tag "projects" %> +<%= link_to _('Projects'), :controller => 'admin', :action => 'projects' %> | +<%= link_to _('New'), :controller => 'projects', :action => 'add' %> +

+ +

+<%= image_tag "users" %> +<%= link_to _('Users'), :controller => 'users' %> | +<%= link_to _('New'), :controller => 'users', :action => 'add' %> +

+ +

+<%= image_tag "role" %> +<%= link_to _('Roles and permissions'), :controller => 'roles' %> +

+ +

+<%= image_tag "tracker" %> +<%= link_to _('Trackers'), :controller => 'trackers' %> | +<%= link_to _('Custom fields'), :controller => 'custom_fields' %> +

+ +

+<%= image_tag "workflow" %> +<%= link_to _('Issue Statuses'), :controller => 'issue_statuses' %> | +<%= link_to _('Workflow'), :controller => 'roles', :action => 'workflow' %> +

+ +

+<%= image_tag "options" %> +<%= link_to _('Enumerations'), :controller => 'enumerations' %> +

+ +

+<%= image_tag "mailer" %> +<%= link_to _('Mail notifications'), :controller => 'admin', :action => 'mail_options' %> +

+ +

+<%= image_tag "help" %> +<%= link_to _('Information'), :controller => 'admin', :action => 'info' %> +

\ 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 0000000..c73f59c --- /dev/null +++ b/redmine/app/views/admin/info.rhtml @@ -0,0 +1,4 @@ +

<%=_('Information')%>

+ +<%=_('Version')%>: <%= RDM_APP_NAME %> <%= RDM_APP_VERSION %>
+<%=_('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 0000000..2d1d80e --- /dev/null +++ b/redmine/app/views/admin/mail_options.rhtml @@ -0,0 +1,16 @@ +

<%=_('Mail notifications')%>

+ +

<%=_('Select actions for which mail notification should be enabled.')%>

+ +<%= start_form_tag ({}, :id => 'mail_options_form')%> +<% for action in @actions %> + <%= check_box_tag "action_ids[]", action.id, action.mail_enabled? %> + <%= action.descr %>
+<% end %> +
+

+<%=_('Check all')%> | +<%=_('Uncheck all')%> +

+<%= 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 0000000..dd39535 --- /dev/null +++ b/redmine/app/views/admin/projects.rhtml @@ -0,0 +1,35 @@ +

<%=_('Projects')%>

+ + + + <%= sort_header_tag('projects.name', :caption => _('Project')) %> + + + <%= sort_header_tag('projects.created_on', :caption => _('Created on')) %> + + + +<% odd_or_even = 1 + for project in @projects + odd_or_even = 1 - odd_or_even %> + + + +<% end %> +
<%=_('Description')%><%=_('Public')%>
<%= link_to project.name, :controller => 'projects', :action => 'settings', :id => project %> + <%= project.descr %> + <%= image_tag 'true' if project.public? %> + <%= format_date(project.created_on) %> + + <%= start_form_tag({:controller => 'projects', :action => 'destroy', :id => project}) %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+ +<%= 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 %> + +
+ +<%= 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 0000000..d268461 --- /dev/null +++ b/redmine/app/views/custom_fields/_form.rhtml @@ -0,0 +1,26 @@ +<%= error_messages_for 'custom_field' %> + + +


+<%= text_field 'custom_field', 'name' %>

+ +


+<%= select("custom_field", "typ", CustomField::TYPES) %>

+ +

<%= check_box 'custom_field', 'is_required' %> +

+ +

<%= check_box 'custom_field', 'is_for_all' %> +

+ +

(<%=_('0 means no restriction')%>)
+<%= text_field 'custom_field', 'min_length', :size => 5 %> - +<%= text_field 'custom_field', 'max_length', :size => 5 %>

+ +

(eg. ^[A-Z0-9]+$)
+<%= text_field 'custom_field', 'regexp', :size => 50 %>

+ +

(separator: |)
+<%= text_area 'custom_field', 'possible_values', :rows => 5, :cols => 60 %>

+ + diff --git a/redmine/app/views/custom_fields/edit.rhtml b/redmine/app/views/custom_fields/edit.rhtml new file mode 100644 index 0000000..ab4ea8b --- /dev/null +++ b/redmine/app/views/custom_fields/edit.rhtml @@ -0,0 +1,6 @@ +

<%=_('Custom field')%>

+ +<%= 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 0000000..0e29a5b --- /dev/null +++ b/redmine/app/views/custom_fields/list.rhtml @@ -0,0 +1,32 @@ +

<%=_('Custom fields')%>

+ + + + + + + + + + +<% for custom_field in @custom_fields %> + + + + + + + + +<% end %> +
<%=_('Name')%><%=_('Type')%><%=_('Required')%><%=_('For all projects')%><%=_('Used by')%>
<%= link_to custom_field.name, :action => 'edit', :id => custom_field %><%= CustomField::TYPES[custom_field.typ][0] %><%= image_tag 'true' if custom_field.is_required? %><%= image_tag 'true' if custom_field.is_for_all? %><%= custom_field.projects.count.to_s + ' ' + _('Project') + '(s)' unless custom_field.is_for_all? %> + <%= start_form_tag :action => 'destroy', :id => custom_field %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %>
+ +<%= 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 %> + +
+ +<%= 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 0000000..0e6492a --- /dev/null +++ b/redmine/app/views/custom_fields/new.rhtml @@ -0,0 +1,7 @@ +

<%=_('New custom field')%>

+ +<%= 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 0000000..4440a15 --- /dev/null +++ b/redmine/app/views/documents/_form.rhtml @@ -0,0 +1,15 @@ +<%= error_messages_for 'document' %> + + +


+

+ +


+<%= text_field 'document', 'title', :size => 60 %>

+ +


+<%= text_area 'document', 'descr', :cols => 60, :rows => 5 %>

+ + diff --git a/redmine/app/views/documents/edit.rhtml b/redmine/app/views/documents/edit.rhtml new file mode 100644 index 0000000..2f1e9a9 --- /dev/null +++ b/redmine/app/views/documents/edit.rhtml @@ -0,0 +1,8 @@ +

<%=_('Document')%>

+ +<%= 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 0000000..bc9557e --- /dev/null +++ b/redmine/app/views/documents/show.rhtml @@ -0,0 +1,45 @@ +

<%= @document.title %>

+ +<%=_('Description')%>: <%= @document.descr %>
+<%=_('Category')%>: <%= @document.category.name %>
+
+ +<% 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 %> + +

+ + +<% for attachment in @document.attachments %> + + + + + + <% if authorize_for('documents', 'destroy_attachment') %> + + <% end %> +<% end %> +
<%= link_to attachment.filename, :action => 'download', :id => @document, :attachment_id => attachment %><%= format_date(attachment.created_on) %><%= attachment.author.display_name %><%= human_size(attachment.size) %>
<%= attachment.downloads %> <%=_('download')%>(s)
+ <%= start_form_tag :action => 'destroy_attachment', :id => @document, :attachment_id => attachment %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+
+ +<% if authorize_for('documents', 'add_attachment') %> + <%= start_form_tag ({ :controller => 'documents', :action => 'add_attachment', :id => @document }, :multipart => true) %> + <%=_('Add file')%>
<%= 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 0000000..d78dc35 --- /dev/null +++ b/redmine/app/views/enumerations/_form.rhtml @@ -0,0 +1,9 @@ +<%= error_messages_for 'enumeration' %> + + +<%= hidden_field 'enumeration', 'opt' %> + +


+<%= text_field 'enumeration', 'name' %>

+ + diff --git a/redmine/app/views/enumerations/edit.rhtml b/redmine/app/views/enumerations/edit.rhtml new file mode 100644 index 0000000..16bd377 --- /dev/null +++ b/redmine/app/views/enumerations/edit.rhtml @@ -0,0 +1,10 @@ +

<%=_('Enumerations')%>

+ +<%= 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 0000000..b5ce65c --- /dev/null +++ b/redmine/app/views/enumerations/list.rhtml @@ -0,0 +1,22 @@ +

<%=_('Enumerations')%>

+ +<% for option in Enumeration::OPTIONS %> + + <% if @params[:opt]==option[1] %> + +

<%= image_tag 'dir_open' %> <%=_ option[0] %>

+ + + + <% else %> +

<%= image_tag 'dir' %> <%= link_to _(option[0]), :opt => option[1] %>

+ <% end %> + +<% end %> + diff --git a/redmine/app/views/enumerations/new.rhtml b/redmine/app/views/enumerations/new.rhtml new file mode 100644 index 0000000..30048f2 --- /dev/null +++ b/redmine/app/views/enumerations/new.rhtml @@ -0,0 +1,6 @@ +

<%=_('New enumeration')%>

+ +<%= 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 0000000..eba2f16 --- /dev/null +++ b/redmine/app/views/issue_categories/_form.rhtml @@ -0,0 +1,7 @@ +<%= error_messages_for 'issue_category' %> + + +


+<%= text_field 'issue_category', 'name' %>

+ + diff --git a/redmine/app/views/issue_categories/edit.rhtml b/redmine/app/views/issue_categories/edit.rhtml new file mode 100644 index 0000000..3afdd1c --- /dev/null +++ b/redmine/app/views/issue_categories/edit.rhtml @@ -0,0 +1,6 @@ +

Editing issue category

+ +<%= 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 0000000..5f4b9ce --- /dev/null +++ b/redmine/app/views/issue_statuses/_form.rhtml @@ -0,0 +1,17 @@ +<%= error_messages_for 'issue_status' %> + + +


+<%= text_field 'issue_status', 'name' %>

+ +

<%= check_box 'issue_status', 'is_closed' %> +

+ +

<%= check_box 'issue_status', 'is_default' %> +

+ +

+#<%= text_field 'issue_status', 'html_color', :size => 6 %>

+ + + diff --git a/redmine/app/views/issue_statuses/edit.rhtml b/redmine/app/views/issue_statuses/edit.rhtml new file mode 100644 index 0000000..d3ed922 --- /dev/null +++ b/redmine/app/views/issue_statuses/edit.rhtml @@ -0,0 +1,6 @@ +

<%=_('Issue status')%>

+ +<%= 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 0000000..050e08f --- /dev/null +++ b/redmine/app/views/issue_statuses/list.rhtml @@ -0,0 +1,30 @@ +

<%=_('Issue statuses')%>

+ + + + + + + + + + +<% for status in @issue_statuses %> + + + + + + + +<% end %> +
<%=_('Status')%><%=_('Default status')%><%=_('Issue closed')%><%=_('Color')%>
<%= link_to status.name, :action => 'edit', :id => status %><%= image_tag 'true' if status.is_default %><%= image_tag 'true' if status.is_closed %>  + <%= start_form_tag :action => 'destroy', :id => status %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+ +<%= pagination_links_full @issue_status_pages %> +
+ +<%= 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 0000000..f7ac082 --- /dev/null +++ b/redmine/app/views/issue_statuses/new.rhtml @@ -0,0 +1,6 @@ +

<%=_('New issue status')%>

+ +<%= 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 0000000..66b70a1 --- /dev/null +++ b/redmine/app/views/issues/_list_simple.rhtml @@ -0,0 +1,28 @@ +<% if issues.length > 0 %> + + + +
+ + + + + + + <% for issue in issues %> + + + + + + <% end %> +
#<%=_('Tracker')%><%=_('Subject')%>
+ <%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %>
+

<%= issue.project.name %> - <%= issue.tracker.name %>
+ <%= issue.status.name %> - <%= format_time(issue.updated_on) %>

+

<%= link_to issue.subject, :controller => 'issues', :action => 'show', :id => issue %>

+
+
+<% 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 0000000..58032ce --- /dev/null +++ b/redmine/app/views/issues/change_status.rhtml @@ -0,0 +1,29 @@ +

<%=_('Issue')%> #<%= @issue.id %>: <%= @issue.subject %>

+ +<%= error_messages_for 'history' %> +<%= start_form_tag :action => 'change_status', :id => @issue %> + +<%= hidden_field_tag 'confirm', 1 %> +<%= hidden_field 'history', 'status_id' %> + +

<%=_('New status')%>: <%= @history.status.name %>

+ +
+


+

+
+ +


+

+ +


+<%= text_area 'history', 'notes', :cols => 60, :rows => 10 %>

+ +<%= 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 0000000..1249cfc --- /dev/null +++ b/redmine/app/views/issues/edit.rhtml @@ -0,0 +1,62 @@ +

<%=_('Issue')%> #<%= @issue.id %>

+ +<%= error_messages_for 'issue' %> +<%= start_form_tag :action => 'edit', :id => @issue %> + + +

<%=_('Status')%>: <%= @issue.status.name %>

+ +
+


+

+
+ +
+


+

+
+ +
+


+

+
+ +
+


+

+
+ +

*
+<%= text_field 'issue', 'subject', :size => 60 %>

+ +

*
+<%= text_area 'issue', 'descr', :cols => 60, :rows => 10 %>

+ + +<% for custom_value in @custom_values %> +

<%= content_tag "label", custom_value.custom_field.name %> + <% if custom_value.custom_field.is_required? %>*<% end %> +
+ <%= custom_field_tag custom_value %>

+<% end %> + + +


+

+ + +
<%= submit_tag _('Save') %>
+<%= end_form_tag %> diff --git a/redmine/app/views/issues/show.rhtml b/redmine/app/views/issues/show.rhtml new file mode 100644 index 0000000..1c20c92 --- /dev/null +++ b/redmine/app/views/issues/show.rhtml @@ -0,0 +1,90 @@ + +

<%=_('Issue')%> #<%= @issue.id %> - <%= @issue.subject %>

+ +
+

<%=_('Tracker')%>: <%= @issue.tracker.name %>

+

<%=_('Priority')%>: <%= @issue.priority.name %>

+

<%=_('Category')%>: <%= @issue.category.name unless @issue.category_id.nil? %>

+

<%=_('Status')%>: <%= @issue.status.name %>

+

<%=_('Author')%>: <%= @issue.author.display_name %>

+

<%=_('Assigned to')%>: <%= @issue.assigned_to.display_name unless @issue.assigned_to.nil? %>

+ +

<%=_('Subject')%>: <%= @issue.subject %>

+

<%=_('Description')%>: <%= @issue.descr %>

+

<%=_('Created on')%>: <%= format_date(@issue.created_on) %>

+ +<% 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}) %> + + + <%= 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 %> + +
+ + +
+
+

<%=_('History')%>

+ +<% for history in @issue.histories.find(:all, :include => :author) %> + + + + + +<% if history.notes? %> + +<% end %> +<% end %> +
<%= format_date(history.created_on) %><%= history.author.display_name %><%= history.status.name %>
<%= history.notes %>
+
+
+ +
+
+

<%=_('Attachments')%>

+ +<% for attachment in @issue.attachments %> + + + + +<% if authorize_for('issues', 'destroy_attachment') %> + +<% end %> + +<% end %> +
<%= link_to attachment.filename, :action => 'download', :id => @issue, :attachment_id => attachment %> (<%= human_size(attachment.size) %>)<%= format_date(attachment.created_on) %><%= attachment.author.display_name %> + <%= start_form_tag :action => 'destroy_attachment', :id => @issue, :attachment_id => attachment %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+
+<% 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 %> +
+
+ diff --git a/redmine/app/views/layouts/base.rhtml b/redmine/app/views/layouts/base.rhtml new file mode 100644 index 0000000..731632f --- /dev/null +++ b/redmine/app/views/layouts/base.rhtml @@ -0,0 +1,89 @@ + + + +redMine + + + +<%= stylesheet_link_tag "application" %> +<%= stylesheet_link_tag "rails" %> +<%= javascript_include_tag :defaults %> + + + +
+ + + + + +
+ + <% unless @project.nil? || @project.id.nil? %> +

<%= @project.name %>

+ + <% end %> + + <% unless session[:user].nil? %> +

<%=_('My projects') %>

+ + <% end %> + +
+ +
+ <% if flash[:notice] %>

<%= flash[:notice] %>

<% end %> + <%= @content_for_layout %> +
+ + + +
+ + \ 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 0000000..1f238f5 --- /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 0000000..9efec9a --- /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 0000000..e3612ea --- /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 0000000..609b15c --- /dev/null +++ b/redmine/app/views/news/_form.rhtml @@ -0,0 +1,13 @@ +<%= error_messages_for 'news' %> + + +

*
+<%= text_field 'news', 'title', :size => 60 %>

+ +


+<%= text_area 'news', 'shortdescr', :cols => 60, :rows => 2 %>

+ +


+<%= text_area 'news', 'descr', :cols => 60, :rows => 10 %>

+ + diff --git a/redmine/app/views/news/edit.rhtml b/redmine/app/views/news/edit.rhtml new file mode 100644 index 0000000..2e849ab --- /dev/null +++ b/redmine/app/views/news/edit.rhtml @@ -0,0 +1,6 @@ +

<%=_('News')%>

+ +<%= 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 0000000..30aa2cd --- /dev/null +++ b/redmine/app/views/news/show.rhtml @@ -0,0 +1,10 @@ +

<%= @news.title %>

+ +

+<%=_('Summary')%>: <%= @news.shortdescr %>
+<%=_('By')%>: <%= @news.author.display_name %>
+<%=_('Date')%>: <%= format_time(@news.created_on) %> +

+ +<%= @news.descr %> + diff --git a/redmine/app/views/projects/_form.rhtml b/redmine/app/views/projects/_form.rhtml new file mode 100644 index 0000000..2d38117 --- /dev/null +++ b/redmine/app/views/projects/_form.rhtml @@ -0,0 +1,28 @@ +<%= error_messages_for 'project' %> + + +


+<%= text_field 'project', 'name' %>

+ +


+<%= text_field 'project', 'descr', :size => 60 %>

+ +


+<%= text_field 'project', 'homepage', :size => 40 %>

+ +

<%= check_box 'project', 'public' %> +

+ +
<%=_('Custom fields')%> +<% for custom_field in @custom_fields %> + checked="checked"<%end%> + > <%= custom_field.name %> + +<% end %>
+
+ + diff --git a/redmine/app/views/projects/add.rhtml b/redmine/app/views/projects/add.rhtml new file mode 100644 index 0000000..6344705 --- /dev/null +++ b/redmine/app/views/projects/add.rhtml @@ -0,0 +1,7 @@ +

<%=_('New project')%>

+ +<%= 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 0000000..13e75dd --- /dev/null +++ b/redmine/app/views/projects/add_document.rhtml @@ -0,0 +1,26 @@ +

<%=_('New document')%>

+ +<%= error_messages_for 'document' %> +<%= start_form_tag( { :action => 'add_document', :id => @project }, :multipart => true) %> + + +


+

+ +


+<%= text_field 'document', 'title', :size => 60 %>

+ +


+<%= text_area 'document', 'descr', :cols => 60, :rows => 5 %>

+ +


+<%= file_field 'attachment', 'file' %>

+ + + +<%= 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 0000000..4e8ce9b --- /dev/null +++ b/redmine/app/views/projects/add_file.rhtml @@ -0,0 +1,13 @@ +

<%=_('New file')%>

+ +<%= start_form_tag ({ :action => 'add_file', :project => @project }, :multipart => true) %> + +


+

+ +

<%=_('File')%>
<%= file_field 'attachment', 'file' %>

+
+<%= 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 0000000..a6b37cc --- /dev/null +++ b/redmine/app/views/projects/add_issue.rhtml @@ -0,0 +1,62 @@ +

<%=_('New issue')%>

+ +<%= start_form_tag( { :action => 'add_issue', :id => @project }, :multipart => true) %> +<%= error_messages_for 'issue' %> + + + +
+


+

+
+ +
+


+

+
+ +
+


+

+
+ +
+


+

+
+ +


+<%= text_field 'issue', 'subject', :size => 80 %>

+ +


+<%= text_area 'issue', 'descr', :cols => 60, :rows => 10 %>

+ + +<% for custom_value in @custom_values %> +
+

<%= content_tag "label", custom_value.custom_field.name %> + <% if custom_value.custom_field.is_required? %>*<% end %> +
+ <%= custom_field_tag custom_value %>

+
+<% end %> + +
+


+<%= file_field 'attachment', 'file' %>

+
+ + + +<%= 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 0000000..c106e7c --- /dev/null +++ b/redmine/app/views/projects/add_news.rhtml @@ -0,0 +1,7 @@ +

<%=('Add news')%>

+ +<%= 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 0000000..27b258f --- /dev/null +++ b/redmine/app/views/projects/add_version.rhtml @@ -0,0 +1,7 @@ +

<%=_('New version')%>

+ +<%= 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 0000000..3f97abc --- /dev/null +++ b/redmine/app/views/projects/changelog.rhtml @@ -0,0 +1,12 @@ +

<%=_('Change log')%>

+ +<% fixed_issues = @fixed_issues.group_by {|i| i.fixed_version } %> +<% fixed_issues.each do |version, issues| %> +

<%= version.name %> - <%= format_date(version.date) %>
+ <%=h version.descr %>

+
    + <% issues.each do |i| %> +
  • <%= link_to i.long_id, :controller => 'issues', :action => 'show', :id => i %> [<%= i.tracker.name %>]: <%= i.subject %>
  • + <% end %> +
+<% end %> diff --git a/redmine/app/views/projects/destroy.rhtml b/redmine/app/views/projects/destroy.rhtml new file mode 100644 index 0000000..2e26478 --- /dev/null +++ b/redmine/app/views/projects/destroy.rhtml @@ -0,0 +1,12 @@ +

<%=_('Confirmation')%>

+
+
+

<%=_('Are you sure you want to delete project')%> <%= @project.name %> ?

+

+ <%= start_form_tag({:controller => 'projects', :action => 'destroy', :id => @project}) %> + <%= hidden_field_tag "confirm", 1 %> + <%= submit_tag _('Delete') %> + <%= end_form_tag %> +

+
+
\ 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 0000000..2b2ac2d --- /dev/null +++ b/redmine/app/views/projects/list.rhtml @@ -0,0 +1,22 @@ +

<%=_('Public projects')%>

+ + + + <%= sort_header_tag('projects.name', :caption => _('Project')) %> + + <%= sort_header_tag('projects.created_on', :caption => _('Created on')) %> + + +<% odd_or_even = 1 + for project in @projects + odd_or_even = 1 - odd_or_even %> + + +<% end %> +
Description
<%= link_to project.name, :action => 'show', :id => project %> + <%= project.descr %> + <%= format_date(project.created_on) %> +
+ +<%= 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 0000000..1c7db5e --- /dev/null +++ b/redmine/app/views/projects/list_documents.rhtml @@ -0,0 +1,21 @@ +

<%=_('Documents')%>

+ +<% documents = @documents.group_by {|d| d.category } %> +<% documents.each do |category, docs| %> +

<%= category.name %>

+
    +<% docs.each do |d| %> +
  • + <%= link_to d.title, :controller => 'documents', :action => 'show', :id => d %> +
    + <%=_('Desciption')%>: <%= d.descr %>
    + <%= format_time(d.created_on) %> +
  • + +<% end %> +
+<% end %> + +

+<%= link_to_if_authorized '» ' + _('New'), :controller => 'projects', :action => 'add_document', :id => @project %> +

diff --git a/redmine/app/views/projects/list_files.rhtml b/redmine/app/views/projects/list_files.rhtml new file mode 100644 index 0000000..217e679 --- /dev/null +++ b/redmine/app/views/projects/list_files.rhtml @@ -0,0 +1,47 @@ +

<%=_('Files')%>

+ +<% delete_allowed = authorize_for('versions', 'destroy_file') %> + + + + + + + + + + <% if delete_allowed %><% end %> + + +<% for version in @versions %> + + + + <% odd_or_even = 1 + for file in version.attachments + odd_or_even = 1 - odd_or_even %> + + + + + + + + <% if delete_allowed %> + + <% end %> + + <% end %> +<% end %> +
<%=_('Version')%><%=_('File')%><%=_('Date')%><%=_('Size')%>D/LMD5
<%= image_tag 'package' %> <%= version.name %>
<%= link_to file.filename, :controller => 'versions', :action => 'download', :id => version, :attachment_id => file %><%= format_date(file.created_on) %><%= human_size(file.size) %><%= file.downloads %><%= file.digest %> + <%= start_form_tag :controller => 'versions', :action => 'destroy_file', :id => version, :attachment_id => file %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+ +
+

+<%= link_to_if_authorized '» ' + _('New'), :controller => 'projects', :action => 'add_file', :id => @project %> +

+ + diff --git a/redmine/app/views/projects/list_issues.rhtml b/redmine/app/views/projects/list_issues.rhtml new file mode 100644 index 0000000..5be81b1 --- /dev/null +++ b/redmine/app/views/projects/list_issues.rhtml @@ -0,0 +1,56 @@ +

<%=_('Issues')%>

+ +
+ + + + + + + + + +
<%=_('Status')%>:
<%= search_filter_tag("issues.status_id") %>
<%=_('Tracker')%>:
<%= search_filter_tag("issues.tracker_id") %>
<%=_('Priority')%>:
<%= search_filter_tag("issues.priority_id") %>
<%=_('Category')%>:
<%= search_filter_tag("issues.category_id") %>
<%=_('Author')%>:
<%= search_filter_tag("issues.author_id") %>
+ <%= submit_tag _('Apply filter') %> + <%= end_form_tag %> + + <%= start_form_tag %> + <%= submit_tag _('Reset') %> + <%= end_form_tag %> +
+ +   + + + + + <%= sort_header_tag('issues.id', :caption => '#') %> + <%= sort_header_tag('issue_statuses.name', :caption => _('Status')) %> + <%= sort_header_tag('issues.tracker_id', :caption => _('Tracker')) %> + + <%= 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')) %> + + + <% for issue in @issues %> + + + + + + + + + + <% end %> +
<%=_('Subject')%>
<%= link_to issue.long_id, :controller => 'issues', :action => 'show', :id => issue %><%= issue.status.name %><%= issue.tracker.name %><%= link_to issue.subject, :controller => 'issues', :action => 'show', :id => issue %><%= issue.author.display_name %><%= format_time(issue.created_on) %><%= format_time(issue.updated_on) %>
+

+ <%= pagination_links_full @issue_pages %> + [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ] +

+ + +

+<%= link_to_if_authorized '» ' + _('Report an issue'), :controller => 'projects', :action => 'add_issue', :id => @project %> +

\ 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 0000000..6fca0d2 --- /dev/null +++ b/redmine/app/views/projects/list_members.rhtml @@ -0,0 +1,11 @@ +

<%=_('Project members')%>

+ +<% members = @members.group_by {|m| m.role } %> +<% members.each do |role, member| %> +

<%= role.name %>

+
    +<% member.each do |m| %> +
  • <%= link_to m.user.display_name, :controller => 'account', :action => 'show', :id => m.user %> (<%= format_date m.created_on %>)
  • +<% end %> +
+<% end %> diff --git a/redmine/app/views/projects/list_news.rhtml b/redmine/app/views/projects/list_news.rhtml new file mode 100644 index 0000000..7db5339 --- /dev/null +++ b/redmine/app/views/projects/list_news.rhtml @@ -0,0 +1,17 @@ +

<%=_('News')%>

+ +<% for news in @news %> +

+ <%= news.title %> (<%= link_to_user news.author %> <%= format_time(news.created_on) %>) + <%= 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?' %> +
+ <%= news.shortdescr %> + [<%= link_to _('Read...'), :controller => 'news', :action => 'show', :id => news %>] +

+<% end %> + +<%= pagination_links_full @news_pages %> +

+<%= link_to_if_authorized '» ' + _('Add'), :controller => 'projects', :action => 'add_news', :id => @project %> +

diff --git a/redmine/app/views/projects/settings.rhtml b/redmine/app/views/projects/settings.rhtml new file mode 100644 index 0000000..c130ed0 --- /dev/null +++ b/redmine/app/views/projects/settings.rhtml @@ -0,0 +1,105 @@ +

<%=_('Settings')%>

+ +
+<%= start_form_tag :action => 'edit', :id => @project %> +<%= render :partial => 'form' %> +
<%= submit_tag _('Save') %>
+<%= end_form_tag %> +
+ +
+

<%=_('Members')%>

+<%= error_messages_for 'member' %> + +<% for member in @project.members.find(:all, :include => :user) %> + <% unless member.new_record? %> + + + + + + + <% end %> +<% end %> +
<%= member.user.display_name %> + <%= start_form_tag :controller => 'members', :action => 'edit', :id => member %> + + + <%= submit_tag _('Save'), :class => "button-small" %> + <%= end_form_tag %> + + <%= start_form_tag :controller => 'members', :action => 'destroy', :id => member %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+
+
+ <%= start_form_tag :controller => 'projects', :action => 'add_member', :id => @project %> + + + <%= submit_tag _('Add') %> + <%= end_form_tag %> +
+ +
+

<%=_('Versions')%>

+ + +<% for version in @project.versions %> + + + + + +<% end %> +
<%= link_to version.name, :controller => 'versions', :action => 'edit', :id => version %><%=h version.descr %> + <%= start_form_tag :controller => 'versions', :action => 'destroy', :id => version %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+
+ <%= start_form_tag ({ :controller => 'projects', :action => 'add_version', :id => @project }, :method => 'get' ) %> + <%= submit_tag _('New version...') %> + <%= end_form_tag %> +
+ + +
+

<%=_('Issue categories')%>

+ +<% for @category in @project.issue_categories %> + <% unless @category.new_record? %> + + + + + + <% end %> +<% end %> +
+ <%= start_form_tag :controller => 'issue_categories', :action => 'edit', :id => @category %> + <%= text_field 'category', 'name', :size => 25 %> + + <%= submit_tag _('Save'), :class => "button-small" %> + <%= end_form_tag %> + + <%= start_form_tag :controller => 'issue_categories', :action => 'destroy', :id => @category %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+
+ +<%= start_form_tag :action => 'add_issue_category', :id => @project %> +<%= error_messages_for 'issue_category' %> +
+<%= text_field 'issue_category', 'name', :size => 25 %> +<%= submit_tag _('Create') %> +<%= end_form_tag %> + +
diff --git a/redmine/app/views/projects/show.rhtml b/redmine/app/views/projects/show.rhtml new file mode 100644 index 0000000..3f10654 --- /dev/null +++ b/redmine/app/views/projects/show.rhtml @@ -0,0 +1,53 @@ +

<%=_('Overview')%>

+ +
+ <%= @project.descr %> +
    +
  • <%=_('Homepage')%>: <%= link_to @project.homepage, @project.homepage %>
  • +
  • <%=_('Created on')%>: <%= format_date(@project.created_on) %>
  • +
+ +
+

<%= image_tag "tracker" %> <%=_('Trackers')%>

+
    + <% for tracker in Tracker.find_all %> +
  • <%= 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')%> +
  • + <% end %> +
+ <%= link_to_if_authorized '» ' + _('Report an issue'), :controller => 'projects', :action => 'add_issue', :id => @project %> +
+
[ <%= link_to _('View all issues'), :controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1 %> ]
+
+
+ +
+
+

<%= image_tag "users" %> <%=_('Members')%>

+ <% for member in @members %> + <%= link_to_user member.user %> (<%= member.role.name %>)
+ <% end %> +
+ +
+

<%=_('Latest news')%>

+ <% for news in @project.news %> +

+ <%= news.title %> (<%= link_to_user news.author %> <%= format_time(news.created_on) %>)
+ <%= news.shortdescr %> + [<%= link_to _('Read...'), :controller => 'news', :action => 'show', :id => news %>] +

+
+ <% end %> +
[ <%= link_to _('View all news'), :controller => 'projects', :action => 'list_news', :id => @project %> ]
+
+
+ + + + + + diff --git a/redmine/app/views/reports/_simple.rhtml b/redmine/app/views/reports/_simple.rhtml new file mode 100644 index 0000000..5816284 --- /dev/null +++ b/redmine/app/views/reports/_simple.rhtml @@ -0,0 +1,34 @@ +<% col_width = 70 / (@statuses.length+3) %> + + + + +<% for status in @statuses %> + +<% end %> + + + + + +<% for row in rows %> + + + <% for status in @statuses %> + + <% end %> + + + +<% end %> + +
<%= status.name %><%=_('Open')%><%=_('Closed')%><%=_('Total')%>
<%= link_to row.name, :controller => 'projects', :action => 'list_issues', :id => @project, + :set_filter => 1, + "issues.#{field_name}" => row.id %><%= 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 %><%= aggregate data, { field_name => row.id, "closed" => 0 } %><%= aggregate data, { field_name => row.id, "closed" => 1 } %><%= link_to (aggregate data, { field_name => row.id }), + :controller => 'projects', :action => 'list_issues', :id => @project, + :set_filter => 1, + "issues.#{field_name}" => row.id %>
\ 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 0000000..32a1f55 --- /dev/null +++ b/redmine/app/views/reports/issue_report.rhtml @@ -0,0 +1,13 @@ +

<%=_('Reports')%>

+ +<%=_('Issues by tracker')%> +<%= render :partial => 'simple', :locals => { :data => @issues_by_tracker, :field_name => "tracker_id", :rows => @trackers } %> +
+ +<%=_('Issues by priority')%> +<%= render :partial => 'simple', :locals => { :data => @issues_by_priority, :field_name => "priority_id", :rows => @priorities } %> +
+ +<%=_('Issues by category')%> +<%= 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 0000000..02eef47 --- /dev/null +++ b/redmine/app/views/roles/_form.rhtml @@ -0,0 +1,22 @@ +<%= error_messages_for 'role' %> + + +


+<%= text_field 'role', 'name' %>

+ +<%=_('Permissions')%> +<% permissions = @permissions.group_by {|p| p.group_id } %> +<% permissions.keys.sort.each do |group_id| %> +
<%= _(Permission::GROUPS[group_id]) %> +<% permissions[group_id].each do |p| %> +
<%= check_box_tag "permission_ids[]", p.id, (@role.permissions.include? p) %> + <%= _(p.descr) %> +
+<% end %> +
+<% end %> +
+<%=_('Check all')%> | +<%=_('Uncheck all')%>
+ + diff --git a/redmine/app/views/roles/edit.rhtml b/redmine/app/views/roles/edit.rhtml new file mode 100644 index 0000000..cffbe01 --- /dev/null +++ b/redmine/app/views/roles/edit.rhtml @@ -0,0 +1,10 @@ +

<%=_('Role')%>

+ +<%= start_form_tag ({ :action => 'edit', :id => @role }, :id => 'role_form') %> +<%= render :partial => 'form' %> + +
+<%= 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 0000000..146e458 --- /dev/null +++ b/redmine/app/views/roles/list.rhtml @@ -0,0 +1,23 @@ +

<%=_('Roles')%>

+ + + + + + + +<% for role in @roles %> + + + +<% end %> +
<%=_('Role')%>
<%= link_to role.name, :action => 'edit', :id => role %> + <%= start_form_tag :action => 'destroy', :id => role %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+ +<%= pagination_links_full @role_pages %> +
+ +<%= 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 0000000..c82fb21 --- /dev/null +++ b/redmine/app/views/roles/new.rhtml @@ -0,0 +1,8 @@ +

<%=_('New role')%>

+ +<%= start_form_tag ({ :action => 'new' }, :id => 'role_form') %> +<%= render :partial => 'form' %> + +
<%= 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 0000000..652f6a0 --- /dev/null +++ b/redmine/app/views/roles/workflow.rhtml @@ -0,0 +1,70 @@ +

<%=_('Workflow setup')%>

+ +

<%=_('Select a workflow to edit')%>:

+ +<%= start_form_tag ({:action => 'workflow'}, :method => 'get') %> +
+


+

+
+ +
+


+ + +<%= submit_tag _('Edit') %> +

+
+<%= end_form_tag %> + + + +<% unless @tracker.nil? or @role.nil? %> +
+ <%= form_tag ({:action => 'workflow', :role_id => @role, :tracker_id => @tracker }, :id => 'workflow_form' ) %> + + + + + + + + <% for new_status in @statuses %> + + <% end %> + + + <% for old_status in @statuses %> + + + + <% for new_status in @statuses %> + + <% end %> + + + <% end %> +
<%=_('Issue status')%><%=_('New statuses allowed')%>
<%= new_status.name %>
<%= old_status.name %> + + checked="checked"<%end%> + <%if old_status==new_status%>disabled<%end%> + > +
+
+

+<%=_('Check all')%> | +<%=_('Uncheck all')%> +

+
+<%= submit_tag _('Save') %> +<%= end_form_tag %> + +<% end %> +
\ 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 0000000..95d06d7 --- /dev/null +++ b/redmine/app/views/trackers/_form.rhtml @@ -0,0 +1,10 @@ +<%= error_messages_for 'tracker' %> + + +


+<%= text_field 'tracker', 'name' %>

+ +

<%= check_box 'tracker', 'is_in_chlog' %> +

+ + diff --git a/redmine/app/views/trackers/edit.rhtml b/redmine/app/views/trackers/edit.rhtml new file mode 100644 index 0000000..a209b3f --- /dev/null +++ b/redmine/app/views/trackers/edit.rhtml @@ -0,0 +1,6 @@ +

<%=_('Tracker')%>

+ +<%= 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 0000000..3622a40 --- /dev/null +++ b/redmine/app/views/trackers/list.rhtml @@ -0,0 +1,24 @@ +

<%=_('Trackers')%>

+ + + + + + + +<% for tracker in @trackers %> + + + + +<% end %> +
<%=_('Tracker')%>
<%= link_to tracker.name, :action => 'edit', :id => tracker %> + <%= start_form_tag :action => 'destroy', :id => tracker %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+ +<%= pagination_links_full @tracker_pages %> +
+ +<%= 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 0000000..a24fca7 --- /dev/null +++ b/redmine/app/views/trackers/new.rhtml @@ -0,0 +1,7 @@ +

<%=_('New tracker')%>

+ +<%= 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 0000000..92f1e0e --- /dev/null +++ b/redmine/app/views/users/_form.rhtml @@ -0,0 +1,28 @@ +<%= error_messages_for 'user' %> + + +


+<%= text_field 'user', 'login' %>

+ +


+<%= password_field 'user', 'password' %>

+ +


+<%= text_field 'user', 'firstname' %>

+ +


+<%= text_field 'user', 'lastname' %>

+ +


+<%= text_field 'user', 'mail' %>

+ +


+<%= select("user", "language", Localization.langs) %>

+ +

<%= check_box 'user', 'admin' %>

+ +

<%= check_box 'user', 'mail_notification' %>

+ +

<%= check_box 'user', 'locked' %>

+ + diff --git a/redmine/app/views/users/add.rhtml b/redmine/app/views/users/add.rhtml new file mode 100644 index 0000000..9d0ba8d --- /dev/null +++ b/redmine/app/views/users/add.rhtml @@ -0,0 +1,6 @@ +

<%=_('New user')%>

+ +<%= 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 0000000..a033a7f --- /dev/null +++ b/redmine/app/views/users/edit.rhtml @@ -0,0 +1,7 @@ +

<%=_('User')%>

+ +<%= 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 0000000..b1bf937 --- /dev/null +++ b/redmine/app/views/users/list.rhtml @@ -0,0 +1,46 @@ +

<%=_('Users')%>

+ + + + <%= sort_header_tag('users.login', :caption => _('Login')) %> + <%= sort_header_tag('users.firstname', :caption => _('Firstname')) %> + <%= sort_header_tag('users.lastname', :caption => _('Lastname')) %> + + <%= 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')) %> + + +<% for user in @users %> + + + + + + + + + + + +<% end %> +
<%=_('Mail')%>
<%= link_to user.login, :action => 'edit', :id => user %><%= user.firstname %><%= user.lastname %><%= user.mail %><%= image_tag 'true' if user.admin? %><%= image_tag 'locked' if user.locked? %><%= format_time(user.created_on) %><%= format_time(user.last_login_on) unless user.last_login_on.nil? %> + <%= 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 %> +
+ +

<%= pagination_links_full @user_pages %> +[ <%= @user_pages.current.first_item %> - <%= @user_pages.current.last_item %> / <%= @user_count %> ] +

+ +

+<%= link_to '» ' + _('New user'), :action => 'add' %> +

\ 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 0000000..189e106 --- /dev/null +++ b/redmine/app/views/versions/_form.rhtml @@ -0,0 +1,13 @@ +<%= error_messages_for 'version' %> + + +


+<%= text_field 'version', 'name', :size => 20 %>

+ +


+<%= text_field 'version', 'descr', :size => 60 %>

+ +


+<%= date_select 'version', 'date' %>

+ + diff --git a/redmine/app/views/versions/edit.rhtml b/redmine/app/views/versions/edit.rhtml new file mode 100644 index 0000000..6db4daa --- /dev/null +++ b/redmine/app/views/versions/edit.rhtml @@ -0,0 +1,8 @@ +

<%=_('Version')%>

+ +<%= 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 0000000..cbffa82 --- /dev/null +++ b/redmine/app/views/welcome/index.rhtml @@ -0,0 +1,30 @@ +
+

<%=_('Welcome')%> !

+ +
+

Latest news

+ <% for news in @news %> +

+ <%= news.title %> (<%= link_to_user news.author %> <%= format_time(news.created_on) %> - <%= news.project.name %>)
+ <%= news.shortdescr %>
+ [<%= link_to 'Read...', :controller => 'news', :action => 'show', :id => news %>] +

+
+ <% end %> +
+
+ +
+
+

Latest projects

+
    + <% for project in @projects %> +
  • + <%= link_to project.name, :controller => 'projects', :action => 'show', :id => project %> (added <%= format_time(project.created_on) %>)
    + <%= project.descr %> +
  • + <% end %> +
+
+ +
diff --git a/redmine/config/boot.rb b/redmine/config/boot.rb new file mode 100644 index 0000000..9fcd50f --- /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 0000000..1702510 --- /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 0000000..de12e7c --- /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 0000000..52aef32 --- /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 0000000..04b7792 --- /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 0000000..4cd4e08 --- /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 0000000..0b34ef1 --- /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 0000000..a48798e --- /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 0000000..2559159 --- /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 0000000..e6288f6 --- /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 0000000..8f851df --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..6a19ec527623d26c8b1c9218c7d89b3067f2ad41 GIT binary patch literal 40960 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 + Copyright (C) + + 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. + + , 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 0000000..72a9936 --- /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 + +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 0000000..1c794e4 --- /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 0000000..18bedda --- /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 0000000..aa40242 --- /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 0000000..ef15f9c --- /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 0000000..5dc7a59 --- /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 0000000..d3c9983 --- /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 "

Application error

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 0000000..e1c8e91 --- /dev/null +++ b/redmine/public/404.html @@ -0,0 +1,7 @@ + + + +

File not found

+ + \ No newline at end of file diff --git a/redmine/public/500.html b/redmine/public/500.html new file mode 100644 index 0000000..713ee8a --- /dev/null +++ b/redmine/public/500.html @@ -0,0 +1,7 @@ + + + +

Sorry, an application error occured

+ + \ No newline at end of file diff --git a/redmine/public/dispatch.cgi b/redmine/public/dispatch.cgi new file mode 100644 index 0000000..7095803 --- /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 0000000..418aa3a --- /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 0000000..7095803 --- /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 0000000..e69de29 --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..6dc4f684a771e74bd03e644dac9e71bf7257f5ce GIT binary patch literal 379 zc$@)y0fhdEP) z4-En9y*hjV000SaNLh0L01FZT01FZU(%pXi00004XF*Lt006O%3;baP0001HP)t-s z|NsBEsTjt#8rH-dysa7M*hl5mG~&=8%w_=W;A`pIQtI4U&%PVIwHfc_fa={~>)vGN z*FxjbCiU;N^Xs7S=Zx*)a^=-G_VK**?XL3bobKa(^zEnj^2Pf1*ZcY7`SsBF^T!Pn z!dw6V00DGTPE!Ct=GbNc004_gL_t(|+HH;L62c%127$EY&>k35dq|J>e@WBOQO9xr z?eYOi{bx%^U%IU8whyCy`+|r>G(q;1&^uxrxSc-PUqlx)LEmCFpd+=EiXDK zBn)@zd-lkKBOrUrp?p9sK{ZVqx)|fl6ovd^eFzul;9Bb(6qJ?akY5lC9Gn+2T7Qmz Zcmc7E2Xsj42f+XU002ovPDHLkV1jWdstf=C literal 0 Hc$@9yhK}ScV}{X|I1ka zxg?KsKxli7yO2UwVr*?>QIv&X|GYB)v?JM5u>Qocfj_`L8nn#zX(iQUBL(|Kf)K@}m3Wk#t`~ zjB`@|!YKS=7VtzB@{ck9(`WzcmH+RW|NXPRn_K;-CjB=L`~U#&U?Ka)QUBU>|Ky7Q z|F-7QjKygx|4l6aMNaxUJ^7 zzP3%U(QumAlJ4JxskPgLr~T{fzI1JGueV>%(#*ro@wK+fdUboIg;s=aTIEahpa1{> z0d!JMQvg8b*k%9#0NqJMK~#9!V_<-Q62;QevUCO*$X6=Jwk|9xR)q3RGBUF=vvYED z^Eko$IKB9U#H8eu)YP;PkghP^@W609NA72JVJ9`I5Csj*l4HqjzS2s}x6H_yD3q1i#eRTsXYa3ez1`a`O z9bG*ERiJ>Tp^-5IgOa4AgtCf&sv6J`Es(>+B_yRprDfP<<>VE>4&@gR6k-tO6%plQ y5`(y#lZ}g;hnJ550a#es*f|)G0V5MLf&&1s-zLY=jF7zm0000lm$G>8qI;8 eo(!SZK$AcJU=9tPyNd^?jlt8^&t;ucLK6T|c{Tt5 literal 0 Hc$@1O+}0E~gB)8BSTiqpZ;!=;_H2 cY7I2`^AG0G(7C&KfZ7;5UHx3vIVCg!0IX~>DF6Tf literal 0 Hc$@u=IGeCVt(B&pQL?x&H?MQ z&PKmlzS92L=T}`;dy_U7onQ53PG(DNL!gk?l>NMGOm+&$tO#EqC;uXjS!|QI-%g+< N44$rjF6*2UngHz^i}C;f literal 0 Hc$@O>_%)r3)0fZTy)|kuy3bLd-`Z_W&Z0zU$lgJ9>8wB`-xc>kD-__N1=FFKl+8EN( z((X)WxI3TW@oI)Q+ZcWxW%zg1=;I!S|966b%FdiQ^Z$8=Y@Pl~pkBt3AirP+hi5m^ zfSd+T7srr_TgeGPP?XTXyxf(e@Bdv_J|De+Z}WF|$6Rx?_?I4bH}-p$Y{bIzD?>Lo z$Z%9&T{YGEozs!MVQZuA&U?n1d_Kd!IEZ1RZTh(v8vceXvmf8P7#KHg5l7nUCDWHK zwY27HuvA*OaG_sQlfZ1(9$%k?95zK^SH~T{9TqXTozRr!+?bRObPI#0tDnm{r-UW| D1kHGz literal 0 Hc$@;M1%U0q#g&YXFpjUg>9 z?ap+DyYm?yuV#3&jp6?pATs{AhvEO7AfU1{XU_a*VEF&MqjI-74^TT}NswPKgTu2M zX+Tbkr;B4q#jV&rPocvKJS?pbnWCl=>CEd~2k%3`jKlh(RRt5%Uh5(-s*Z=?jySlojrKL@%Vwjo6 zaIT(VWjn*nnKSoHWH{f;aAqdMouv%#_b~iF!|?zA%>Q>o|39r{7zLwXxB#O>f&D;= zu_VYZn8D%MjWi&q#?!?yq~ccYxoDxI0s^jqeamdu8T`MvEV^TM$hqgYM2^KIo5ef- z=XtT$@QK)l4*sXtvP9REvOcMD2>m`ev1nR=`1B(A{mrkM-6dvSUwt_Nn~YUU}gyL332`Z|NqRHGt<)2jE#-g z7&FW?X4qlOuyyO!GikAYB(84v6p!Iy0Twm<`&U6*#2X+9#Dw0 zz$3C4sPqE}Gdis?nE@1JNq6*hWMJ6X&;2Kn705RT@CkAK|NsBYnKOYrAmjfTgp-=4~tjE2r_Og)%p&OiniFSggeL voWp1Z2e*L%mysm50fVNh>x4ZEgpM#UY`Dx)E>nE`0?1HLS3j3^P6#WAGfR&1}Q5Q8Gek(96h?dP$vEzRL~ z;@MI2c+JeW?`BsD^nZ|ABO}uObY9l8oaxg;7kX^|ALLLcD-}HJlxkGhoU0#?1-$W-JFTn9*sC$qb+%OS+@4BLl<6e(pbstPBjy3;{kNuK)i3RkWQbXWc1p)}&xk ztK_`ECVsb1(dDStC#h53#rHf9sJv_*wo=xlLdAV;VA18s`bS|+@BPZ(hBUm6>v@+v z@tH%)4n^bIp#0NC6W?^K_*CydBRRi}m&V61n`+H{3 zhn0JO&D!|gzv7*H3DAT$ex+9wjO#T54z1q*sbt2t{Ar&z9QzSc^VYxWm3Q%*z?wJF zUC*@q4;{byt99v*(rModCVji}{BLB<2fxZ!34QP6%sVxMP8`1UbMdxc6|=wQP5PAC z|0${Cb5!&DklOb?mG8pqAIjKtY6hQNw)?~3%ir5q{Vt#Ly>$AIfB*g`c72X*eD9Te zQNdJYQHW!}q64}KlH^kc!U zuk9u|mlT~AY6YE1>$tya_s7#$e{MYby?gQ7z@qai?yI#z&Z-9; zRSP(v;j@Qf6bwF~*!p)TkYX$e@(X5gcy=QV$XV;@;uunKD>>l+o7d@6r?emJxwGd_ zQ`4bEkAyg`n_H@y>e?zBYg@CQ>F?<2>g(+7_V@5%^8WpkorR5+y}hNat(Ey_pWit> zy>)r-gD+f8p_EjIZ0W^c%Eo$u3oX~es#k8p2o- zZCW4ycX4!9Pg~F+@9W?pCUe6_CrcV1K<5KJ-k7SZY*o`j>}Cv0Vr5z)_D33MD}$%2pUXO@geCywmS}_k literal 0 Hc$@NS%G|m0G|-o|NsBboH-N7o0-O7Y-|kV zrlqC*KV!UQ%a)ZZR|0uwfWVmH;lqbOtKPnSyZGp&XrOAwk|4ie28U-i(tw;*o-U3d z6}L>!DDoY05NUZBDW!9gdrQ;0h08dbF4*t|Y}(6^WO{JTa}E^_z5~1*T(=YEt#T-0 z;%Q4ZNt_<^{ptA>2A3X1hE@l$pOa@ixo4=hXx=o9mn-D&-6{5t`NJlCn@cpdbnO*Z zxAz|A*E$0uYW_4=FKztXop&pCZ-~U3^}k*|dC^%@CG6?@^5y0e2Sd18Z2bJ5hi+|K p)F`{#b3wUTa&W7zgXE5uUrb;BXx;j~Po4n;JYD@<);T3K0RX~nj?(}D literal 0 Hc$@ZWzG@P~Z5F$09{>OU|I~W_z*zsgQUAkQ|K@zZaU!}|0{{QN|L~~) z#dH6rPXC)jvttwg;-CN1i~q4!|C~Gjx=yxM0RR8*|NpiB>YM+$WdD>rwQ3;$;D-O+ zeE;2ZylENdWdQhT0P0Er|9AlVdI0y394Ks)!vFu8=l>{qp-X&~08w?!|NlmWi2_%E z+5i7(rmvjT)VcKY8)AC~ReQC)y}QuRtG&G~Yla@10q6h#00DGTPE!Ct=GbNc0041G zL_t(|+GAi~V6e2Zwy|XZ17mg*Q!{f5Fk7G9fPul#2+Y>j0TQ};V7987x`w8fCWy(# zuE@cmq|BoNWb;YO$jZsfb0`4W%Eip-#gC`UKlj&v z?r;7)-u`}j{{R2)|NsC0{r∓pX3;umAph|NHy%pI@K;Jl-8B{5(DUetY`!{P6qb z>CemK*K$rh12mAaB*-tA!Qt7BG$5zl)5S5Q;#ThYXucx~9IO|@CPu$nks<#({*^}B zB(>m2{O)_&Qh(~pqz8vSlQ{7!h9Tzq-6?NMQuI$&?1i9seTz(tw0}62# zctjR6FmMZjFyp1Wb$@_@Ea{HEjtmSN`?>!lvI6;v0X`wF|Ns9tHa1R6OPe`!=9Vp6 zR<2xm=FAzO;M=!vA3l6|#+cz`@a6wN#f&9Ee!&b5&u*jvIn|ynjv*Ddbo&hX4h3+u zEzI$I-|swmomBcg#VUcV51C|NybIF55Sg!KBhPtK{9ivqpnjj&+mcD4(zW)FI(;U0 zsT<7|ZP>phcSrv5%|c$vt4-2&-BjK?WAEfcx4$3eY8PLr>$tdU=G}{XX6uTcczn3@ j*!+wg3kvGj3H)a;ST4c;F_~>S&>0M#u6{1-oD!M<0?l#W literal 0 Hc$@bnWCl=>CEd~2k%3`jKlh(RRt5%Uh5(-s*Z=?jr=_KxIdf*_%$a-k z?Af(z*T#(-*Q{Cd?%lg*&z?Pb^5p5$r+4n$*}Z%BmMvR0Y}f!)zjNo#moHyFeE9J0 z-MjDKzh@W)qhQE_lh2CR0V&3kAirP+hi5m^fSf2#7srr_Td93F#TpbiTv%^qTwe3< zf4H%T_ul*%wsR7fA8;6b;bNLD`1!24vdcp86-tFkGe3mS{Gb@{*TKfc$3}lo+t+Q; s6XYabwg=AUoNMOWVDqOtE3fqv!_})S`g0EnE&*BX>FVdQ&MBb@0L*}V)c^nh literal 0 Hc$@Tn9*sC$qb+%OS+@4BLl<6e(pbstPBjy3;{kNuK)l4H#Rm-OG}$MbEdKJ z|1Dd#tX#R$c;^2zXU_aT!*C{z;mk~iw{PG6pUD7JIn$Woj4{K*hYuM>!6+CO;JwMT zN+88p666=m;PC858jzFh>EaktaVz$0B;NrA4%a}&+__tS-_LP0xpr@Vxl@y&%tXt@ zzm!*fU2^ZYQds#U-XRHhaxxv%b&t;ucLK6UPi){P= literal 0 Hc$@ zgoK0#i46?LjNKKsT$y=UPa!HG>ujF5f_Febmgt0s3?iPG z3X@XQIC0>?qeqXlG&D3AL<$Q9Svn0m8YJAf9R4abwHCIA2c literal 0 Hc$@2*ig0XH)*OK#H*>$S;_|;n|HeAg9pN#WAGfR&qi@!h^(y zCS!I5lk+o;1&+-!PB&|iTzzg%o6)s5ZAM{^5;>8p%ceI=WM;13HeKAnz+l13=|VAwwvNe@v;tZj1CBU)no6wb4s2=kyvnd7zM;`^W+Nj5 XW1y_ILFUU4kc&KB{an^LB{Ts5xJY5@ literal 0 Hc$@CLdthj)A!BBmWB&y|X`RY;f`BJ<_ju%@N||NoLFD~mQl$aHGjq>;5dG_D{h(5s}0 z6&=HANU$m__3PuddU(lvR_xWj`}Oho@9EyQt-n!E*P(KhM@X_VFV2l&>deNZJT%y8iwA zoG>u1B`p2=_u9k4v1Mud`1+qvOZoHg#bITJ9U`qBAek?40RR96!AV3xRCwBy*IQ$v zN(=yC9IhRft9V64L`77pqF_Cx@c;kSNoGK)`?Ps*cP(EtGlYZ{D5cxspMQvjKH)Oh6X(pa|J{ zGy1J$Ej7=Z{uvmMfRRsE;v`p;45B~6*ep#hM^ji zl$+7qoWq~}ewG=61uFw0He{tJurMU&4Iv?=B^eR(wAHk!miA)O7p_+YR>lbmU3rmn ze?+ze(+sEd6foB&*l9+?zkr_a-5*v&p*?c}HOGtyHg6r{WFYpQ=#z0Hc7VWLx$>M3|b0|Gn z+5t#z6*ffSVc6DjpmB2?AAR@@vB!wCK?9Yl;33;Q7^%(401QW|k=R8b!OwtLJPjjm zO9Ia;qCq)rOq!1Ia*6#A%#xb}yDx1P*pWla>9j$bnMn3CBqe4`TRll_Iy29kmG?4fbKuF=XqU|?3b@B zA`&a?KIgZ|KJx5eND_c3Em=WZn@xW8hRJ^G&sY^b(FW?WC9W_sb;+lAPdLTdBaKIK;-f}*h4|1aTjw7qX_k~e{TWO7jqcekERN;Jyh%67)q4rKpL*CEYL;|#GY{B@5 zi52XoC?xsoorJKxsliugF#z38MJqrYCWV(t<=G&f;^Me13&AiI9{3jUZ$ zFM`*L(9qc^VMxkz1oaDH!1pcD^IXp>Z0Jb=_qs?Vsrs{mp<^{$N!EC9o+`CO-(o}E zJ`y{*;9s|wr22-QoJ87y^~;)Q@b%P4UgSSsx>2$o@Vd{%Pk0@4qZ^fhB(vt$c1TG> z*{Ad;foraENbld`=MCNm4?9kvlgK~&J>ialpJ7nua zx0oRzwG5;}Qne)Fg(N3kf?JVmB;}y&5(0+~r*aL$0Zof8fe!AtHWH>A^1Y)@G@GsA zup`R{Qg?{+MaxTq#2n{6w|)c&yaJ7{U4ngAH5v6I)*;@rEBE*ehIPBwKBQU)YKE8F0lR!Sm?sE4Xk-sj&E$|A-9n dP56HS1^^A-61FoN)nxzx002ovPDHLkV1kw_Sd9Px literal 0 Hc$@twtJ{H2&HXIs+%wegM>jwsKkVb5D6IonECB89s&{NYJF4M^b9Oz zQ|5d4yj{T42q!qJ`!<1c$g8t?u#{>yV@z+58I)5Jt*wYQ&osalEGzH~57PiykY`xg z-O?><8sG|6FUjWeU>nknSx-11+B}Py`yE!a^(v>caQmZe8|a;;F*<_b)V#8*WJ|{f z*rO|M^|Y%j39hPC%ya}Q5+X8lkUdO9;nq?(!N(c?tHIlf6zy;62Y=SGM!E{as{jB1 M07*qoM6N<$f``*@wEzGB literal 0 Hc$@1|BPNp>B9Is$bPgSc6&`*WAF>}Pl^!RVA1aw5ESDxMnJO-s zFEExdFR~yitsf|`A1MF-|G`>IlPM~cEiLxz^8zYJqA)Ol|yG}W|NjI`7HnJl$m>MB=7agjos>PU% zxI{I!K{d2BFtRc_vMxBNC^WjFoxf#D=Eb<(s+hJsGPOA|w@^TB5*ejKJHw@;@ZHq8 zSwyx;Jd>~%hibG-Hp6U4?bg8Q&9UdmspiF^pC~BWp^w;`hR1nSzFk4+%B!e>UjQ#-(4Kfz-}#cNN#T0G&or?WUTy;widjBm_?V#|J7$$V4Pm~@&OBBebt z#d1o;Z%D&xN4i%-j{SjC00001bW%=J06^y0W&i*J3Q0skRCwAo&E5DF9!;R`US%xf%l>M!0W;^^bMAL%&Y8J{ z7#N>1m6}n*YPC8XDTh|a5QFIX`K!HIsr>%a*ls~md5|cXOL#_EYH$Hs)6oXETe%29VP>2ruHb8ER~iYKUrC=uf=8g>C#lgPK=cD>1?il;o9AM z_siw7EI)WyuoH$-noJkxZ!FxrB}o!KT)%w>tBo`U3d~-)iVce$UvdzJp%It^xKx~< zE0rX*p?cYh%w;0~r=4|YbEUlM6_s_Ni{0&tli6H8FHKF)AO}O|yW1(rG!B)&P)HGG zAQ`u+YxHbD>qfx|%`vWDYqE|(~JWK_y~z1{(p zvDs$B;{Zs24B!?Is%!>+9|*7?lhWM)qD$YYRxhvzlc&_V7myhfm{hid1FUKVK)=8e zqsC-1QS*+SyZZVp7K@dqHeN8`SFtRs=?16Oayq@iK>h)#vU$kG7F3D=0000=?BG literal 0 Hc$@QK$!8;-MT+OL6&q!Uq=Rpjs4tz5?O(KtpJ}8*Z=?jpP9)pGmRlFEzQ{2 z`2QJ(GiT21+_C)it0!s33^S*C0TnR(XE2`05I*^AB~TY*NswPKgTu2MX+Tb)r;B4q z#jWH7ATUc{U{32k%O+42?Y@a|idk7gfC55)g%AJNY5;w|taC0$8rg!)9ma{WT zW&-K$T#XVA6CFG`oI^4fF*UnNPY{&SIFc}tFR6vgamfLm862A~`UtF);-GX)B9 z7I;J!Gca%qfiUBxyLEqnf-LEdzK#qG8~eHcB(ehe>H$6>t`DOawt6shnKS(FwEB?1 zaMYjSf4R*6G={H94F7>3hwJ~E(#O$^(G23vKrJzzE{-7;w~}w<^swbf^jyBspxk`( z)2CB2jZdFW+%ae3!8LpK6j^ahYc^aJV9BA(&1f2`l-V-FShgo|mBS{3wH&=q1$e}@ v=662h2x-s^Id~%BPy-v2qy+251_1`~`SJSG+?NMQuI$%X7!lvI6-A0X`wF|NsA=Hf`FoXU`aj1GimD zK@?+2kY6x^!?PP{K#r`Zi(^Q|t>gp;1~pcNg-cjcG&D6CBpsG12sAupkknwDdB`F1 PIY^tQtDnm{r-UW|P}DC9 literal 0 Hc$@?NMQuI$%X7%y{W;-5;PJOS+@4BLl<6e(pbstU$g&fKQ0)|NsAI&YTJ4%}ir3HZ}%w z)6&xZpE2IDWy{KyD}lT-Kw!-9@Zm$CRd3(EU3_#>G*C5TNswPKgTu2MX+X{@PZ!6K zid&{<6!{K0h_pP6l+roLy`^d0!eyLI7i{&&e~M+%r^LG;f;5%N6qX?i72+{9%*6%_SOJy7mgI z+j|f5Yn_1+HGi6`mo|Rx&bt-6H$>vi`d=@fyy&c{687|c`Ev7#gCSfkHhzB3L$|gq oYLwmWxuD!EIk;8VL2^gSFQ%`5v~K<0C(i%^p00i_>zopr0HD{6;Q#;t literal 0 Hc$@?NMQuI$%Xn0OgjBKmVC1BEyX zJR*x37`TN%nDNrxx<5ccmUKs7M+SzC{oH>NS%G}E0G|-o|Ns9R&t$lqv-1g1gs~*Z zFPOpM*^M+HN8QuKF{I*Fa>46C48?7+Y9b9c2{67&sYrHVITRs$4z6z+h6! V==jdE|UNN literal 0 Hc$@yP zQK$!8;-MT+OL6&q!Uq=Rpjs4tz5?O(KtpJ}8*Z=?jpP9)pGmYW@83tox zlpjF7T}G;_MuY&m3hvqmkceT=?d4^@G7GkCiCxvXdr%UBZR7tG-B>_!@p6XxmS7*cV|^~^=y1_c4;3sr@ULTmwi|4j1=3>ts)KNeK@ zwp{9odT*0-NhA1W99O^8X5xZzQMPh5f9 b;W)e6Eq>;0ERDTD8yGxY{an^LB{Ts5rD#se literal 0 Hc$@bnWCl=>CEd~2k%3`jKlh(RRv_OXz$e7@|NsBS#>Q!BX)|Ze+_GiM z%9Sh6oH_IG;ls3@(5a%sz#-$f&4U3m7IOPMP*hM}tEssED)0Kw=6H>%wnM84R|2rJY5dsab&B N;OXk;vd$@?2>`t8Wkvu1 literal 0 Hc$@0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + 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 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("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + 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("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + 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 "
      " + ret.join('') + "
    "; + } + }, 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(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /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 0000000..92d1f73 --- /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 0000000..414398c --- /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.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 0000000..e9ccd3c --- /dev/null +++ b/redmine/public/javascripts/prototype.js @@ -0,0 +1,1785 @@ +/* Prototype JavaScript framework, version 1.4.0 + * (c) 2005 Sam Stephenson + * + * 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: '(?:)((\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 '#'; + } +} + +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 '#'; + } +} + +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 = '' + this.content + '
    '; + 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 0000000..22016c4 --- /dev/null +++ b/redmine/public/manual/administration.html @@ -0,0 +1,121 @@ + + + +redMine - Aide en ligne + + + + + + + +

    [ Index ]

    +

    Administration

    +Sommaire: +
      +
    1. Utilisateurs
    2. +
    3. R�les et permissions
    4. +
    5. Trackers
    6. +
    7. Champs personnalis�s
    8. +
    9. Statuts de demande
    10. +
    11. Workflow
    12. +
    13. Listes de valeurs
    14. +
    15. Notifications par mail
    16. +
    17. Informations
    18. +
    + + +

    1. Utilisateurs

    +

    Ces �crans vous permettent de g�rer les utilisateurs de l'application.

    +

    1.1 Liste des utilisateurs

    +

    +Liste des utilisateurs
    +

    1.2 Cr�ation ou modification d'un utilisateur

    +
      +
    • Administrateur: d�clare l'utilisateur comme administrateur de l'application.
    • +
    • Notifications par mail: permet d'activer ou non l'envoi automatique de notifications par mail pour cet utilisateur
    • +
    • Verrouill�: d�sactive le compte de l'utilisateur
    • +
    +

    En mode modification, laissez le champ Password vide pour laisser le mot de passe de l'utilisateur inchang�.

    +

    Un utilisateur d�clar� comme administrateur dispose de toutes les permissions sur l'application et sur tous les projets.

    + + +

    2. R�les et permissions

    +

    Les r�les permettent de d�finir les permissions des diff�rents membres d'un projet.
    +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.

    +

    Sur l'�cran d'�dition du r�le, cochez les actions que vous souhaitez autoriser pour le r�le.

    + + +

    3. Trackers

    +

    Les trackers permettent de typer les demandes et de d�finir des workflows sp�cifiques pour chacun de ces types.

    + +

    4. Champs personnalis�s

    +

    Les champs personnalis�s vous permettent d'ajouter des informations suppl�mentaires sur les demandes.

    +Un champ personnalis� peut �tre de l'un des types suivants: +
      +
    • Integer: entier positif ou n�gatif
    • +
    • String: cha�ne de caract�re
    • +
    • Date: date
    • +
    • Boolean: bool�en (case � cocher)
    • +
    • List: valeur � s�lectionn�e parmi une liste pr�d�finie (liste d�roulante)
    • +
    +Des �l�ments de validation peuvent �tre d�finis: +
      +
    • Required: champ dont la saisie est obligatoire sur les demandes
    • +
    • For all projects: champ automatiquement associ� � l'ensemble des projets
    • +
    • Min - max length: longueurs minimales et maximales pour les champs en saisie libre (0 signifie qu'il n'y a pas de restriction)
    • +
    • Regular expression: expression r�guli�re permettant de valider la valeur saisie
    • +
    • Possible values (only for lists): valeurs possibles pour les champs de type "List". Les valeurs sont s�par�es par le caract�re |
    • +
    +

    Si l'option For all projects n'est pas activ�e, chaque projet pourra ou non utilis� le champ personnalis� pour ses demandes +(voir Project settings).

    + + +

    5. Statuts des demandes

    +

    Cet �cran vous permet de d�finir les diff�rents statuts possibles des demandes.

    +
      +
    • Closed: indique que le statut correspond � une demande consid�r�e comme ferm�e
    • +
    • Default: statut appliqu� par d�faut aux nouvelles demandes (seul un statut peut �tre d�clar� comme statut par d�faut)
    • +
    • HTML color: code de couleur HTML repr�sentant le statut � l'affichage
    • +
    + + +

    6. Workflow

    +

    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.

    +

    S�lectionnez le r�le et le type de demande pour lesquels vous souhaitez modifier le workflow, puis cliquez sur Edit. +L'�cran vous permet alors de modifier, pour le r�le et le type de demande choisi, les changements autoris�s.

    +

    Les lignes repr�sentent les statuts initiaux des demandes. Les colonnes repr�sentent les statuts autoris�s � �tre appliqu�s.

    +

    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.
    +Celles au statut Assigned pourront �tre pass�es au statut Resolved.
    +Le statut de toutes les autres demandes de type Bug ne pourra pas �tre modifi� par le D�veloppeur.

    +

    +Exemple de configuration d'un workflow
    +

    Remarque: pour qu'un r�le puisse changer le statut des demandes, la permission "Changer le statut des demandes" doit lui �tre explicitement donn�e ind�pendemment de la configuration du workflow (voir Roles et permissions). + + +

    7. Listes de valeurs

    +

    Les listes de valeurs utilis�es par l'application (exemple: les priorit�s des demandes) peuvent �tre personnalis�es en fonction de vos besoins.
    +Cet �cran vous permet de d�finir les valeurs possibles pour chacune des listes suivantes:

    +
      +
    • Priorit�s des demandes
    • +
    • Cat�gories de documents
    • +
    + + +

    8. Notifications par mail

    +

    Cet �cran vous permet de s�lectionner les actions qui donneront lieu � une notification par mail aux membres du projet.

    +

    Remarque: l'envoi de mails doit �tre activ� dans la configuration de l'application si souhaitez effectuer des notifications.

    + + +

    9. Informations

    +

    Affiche des informations relatives � l'application

    +
      +
    • Version: version de l'application
    • +
    • Database: type de base de donn�es utilis�e
    • +
    + + + + + diff --git a/redmine/public/manual/images/issues_list.png b/redmine/public/manual/images/issues_list.png new file mode 100644 index 0000000000000000000000000000000000000000..2fa6dc1b630e4b6f7696cf71451bc7b3bc9d6e66 GIT binary patch literal 8055 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 P2LmX|smWGKo4x-x&ov{- literal 0 Hc$@sK)+gfbq$^5zvzBR-(WMi`}!6#g<007`$ zH=0z-=jc_5a}&YT))WAsrGZyHfs#1`*5;-T*Se=a+ouj|!9VSs0S-j_>} z0q0>xx-WtI1y}0y&z_;?@qy!2?~++iGSqZ~J(;S_jf?|txJPnD7OWWEOq9~61n0VU zD$i!XBbRu9Xkw)4W631Q3JDo4SPQ)B9n8iPMKtJixNa4J$yTvmaJ^<4i)CG6ezV6>z@#DIcLCqiBc z;*Cs%LF0dh&YP+yrVA%$`|X_$DkZP&sX_ANXlg-HXnRj#L_~Ek>TIS_(Tv*pEHTWv zNudrcmwx~^@KAOiG(VobvLjcg!=d*2*kgxnx|z~f)&!;>2ktLc`*MM;s}|rS8_VoE z;QqiM=3jL9yl1v$=b||zEo;&Z?R?{NghqL&IN|vLAc?rFsMTP%Iu0t4{&6VBF4|6N z`SF|7DGcgI4*x5X)yZAEkWK%;gr9(R)={YZ_x`CqO~;XbpSH(GS$nBHH=ps<;~vQ6 zp&U-;=ALxk-f=TbQNzBYRF1+If3P-Oeoy3@JOW$Mf2-OvIEJHN%`j@BzmoCdV%f?# zm#*~sGQ#LyGAr-yOA)m|!)7sx&@uWn*@Z$RgeIxAKatli-BLe4uHfjh)DgnCJ7?%c zo*M@>kdYe{F;lZorz1`}_%bd-|CPiwx|=R`cHbGJu{UVqjdPQ;QtlOFaamet6Vd`& z3YXnRvGCuKimDb2AxL8k_&zG$@D4p{qUC8}clER1dtRhH%8X|)E9+YQPFz!k?L&SO zP@eI5bMQP?6^Dr35tCAED1SIP$G>p5;qHsDl3MMZbioJSoX+-!f%;y6yndTtlUg>z z(;Tgbc0ZREVq40e+FMFaUfP<|tz!-;wA`d@=@>?y+hW)N zBP(x=ehwirmR*@|ybH zDb1rKSmJ4Jq_Uv5Xx@hT^2ycJZUdpWX!+o`R@<~@z-Gyy?@*^Yw>Wk^F?S4u%YWDB z1E?t#>S@0i3z1YBSJ!kyXQE;8swLZhSMhVE5w4w+PiQZNCXJ*#uKiX`I_`mM1yfy9 z57qHO4Z3N_wSCN~7z~p=jUWjGMRM4K<}uJZRC@;9EGPIfFz;;E1oO}IQ;U3yL~h(^d|@812PVSG^&;3t&j zOVvytP1y$(4VRQn_+``S7ta~3?3+=zIM=X3@9&)`9^?JV3;cjF91KvNJzM|?$x*e|cPK1i9d!hc) zYEsvUgRy?=U)Dr5#>Q*jQDr1o!`f(-5NsD{WM0om=W0BEjYDj5(ABa@bg|Gn597t_ zv1$t^3n#Z(?-7X@Wf9GBb?QdBmoRMsZLNV=-+S8pukSmQEJT!?fSjxXw>HdS(ab?H z9BsYuHIalT=pf~Dqf-O`OG*UD@9+r%k5zKHp8ea51ToIc~+H6nddxm{yv ze$leIVp7_CbTreprES4Bt#vg;yen+4tZQ76b5ZJgLesgZXuX7(W%lwhA!*@HG$|(r z1Lrsl%bGYA$it=e@Z#;(>kqlWrNh(fgI{K6eM)+7^lohqS+LSBtF>BP^d|k$gP!9v>B74wf25_U#ar_3I8PCej4P11Rj-1 z)VCEBR1#Brh}fdI9v{Ewiz|0WiFz-|+4^%S`lJ)35${r0ZChmtJnRSE-~>+rKn&7c%8jfuSeoWwwX1%sB0>?*$BlDe@DZf64j;dEcx=w}Y%Wu_$H`L^El zh3lbNio<^$EuU_kn&#}UfVv&6;62Rj5GIz80(mb@y^{Q_2%ziR8v+TuKQU(_?R;9d zFx6}NwJUf%#&28E6&Yf9Sk8`DZ*qGZiU+FLUmfVO?!rAec{(;9d}lN|rs|b5-g;B; xx3_&P2I~9qVqr%EO_Q@Kkxfbx3o@HBc5^mRofN2%C7(ZlwS}E|iJ3?2{{U^{&*%UE literal 0 Hc$@y{cGJgYn}7%^X_-QXYaGtexCQ)p&DxP_z#{u0002^iVCt?0KmQNo3<wALScI@rgp17^wZ$3N?#C!QYh%n?N+ zdn!bM)M z(eGNsT2|K)QBhrq$l8e+MUm+>d(8N?-9JdY~MFS z{5iEO?_?_F7Z%Z_UJo?dHMFO-iMe;~Yn^0YzcI_oq6UQ9D@i9?m}S5XJ8$ZfA_JnS8@-=;LMW#(aODY6#97ua}qe0 z%Qi5!Sp3SiO}$(KaxiQRQ&+JCd>&y>7_vv84=4u6#WPQ&?u=`*oIya-**@MPA}R7j4>l>n+$< z5N$!mpPkhC``MfQMOI~;PFXxe3}7Kc|6wSp$z%BlcE!n+Z=p?5#n#ZvU{v_3u~ZGE zuO$`F)FN|^ay9q;+Buy<{+|@HhVRslmh{@cJx4lFZa!aMD_A0wh_r+X95qMx*gU3L zIb<|gqh!7xWv(eJ**sjnsYx-@D|?#M_KA74T)V@jg-BQu^R?XSMb0W_i38plB(*`e zqOSkUq0`HcFc5tiM8&r#Cw*DI{KB zAbLNgJY$*QaLjo73rb3>3_6uy6kKz@RWDC}1DVdyP8FmPA{d2TZN%hfoRhI_a^zlW z6w0U(!7KtS%W>S~FHaH|DutT^^~KQj*@;e!&szlzf+cpi$!I}|oRR!#S=#;bEOOOO z)=z@Fekq#G{^F{Cg!e5vLHtN{?2(1d*m&8Vw#;|H;O1Yhq_Onu^ChMm)RYv+_;!P| zN8T#k2V!U6Pfy*eXQ%=mY(clNThjmSW=Me*;hvB>WGIf((uh(MZ!L_74PYi}ylk#@ z>dV`IVi6!7`DE{C99`PS=qsj30qD79bgW2To+Y}r{sf?er^ga&P}5(}BpTCQo$+?l z^gn>%{mu5boF{|{VBPIm-1bW)5+v7ShXk;Fi;V#*m04(dVlrBZ2%#Q@|C1T?IcriT zajJ3qIzJDl+X3;Uyt|NFIFtk}Hl+`JsadO=Ig2wF+TPILw~Gk0&y}Xv)heAJV=vLu zHN+IT`-s%X5Q;LlV^rp}*Iw^(pWaY@ITf|1(?Qc34Q2DD5XPUVeW!Hsa{_8nm+jK| z*;KcZ!NxtV$aIg5ZFH|=@8k7L=w`A$?H%r*c@faeG&&q4;@h>%HD0cgiq<=v?fpnP zGL&&(?12IRM5t**+STO-0p}wJYycfd?5A3Wy~9k!w}ZKb2Pw4BCG*hX&l+@dcth8Ydq=}*m=xYrqQ@8co10}1laPWy@FGFmzU&irD) z&$Pe-Q!1&R)7*n3-wh!HV!0b-d^S)ira(`|0y27oIxVrUF6ohD8_{KHVRRsBUh4-~ z%QB}ewMw+{51$*o?Yl>m8}!P?PmcvPcJ6GB^VHRsy9!P>q5cTgm^;OYTjR# zsVx=9_d$UyLMIICCV&}BP1NZnc_KlXyg!*joH$tt6_(EF;f8QAW-5>$*{^_F zg%?J}y21p?z1J%TczL5Ery>Q!5x90)My77^4%y%EsIT*C=vyGuvmt_W?3 zB+{aGUk!w5AS;6vMyRpF6B`Ajz(EQw>MvFv;K2qdKsQCq zxC_!xwXhiE?k^^rxWD=!;XO2fz+2^lfhU6!dM83J3G0SSie7rzRX?k2yO@X> zY+|@}#x5Sx{!X3^sdjX=XOsidkSufuW5jY9H|0!-Af?xPdV7yhcF*o7iQ}syr7I&% zaa{x1UhYkkGZ^689-nB{{owO<6h3FsrhL1|(BdQI9d4#5GpMr}l>K=czI+7W?HX zJuI~VKe!@%W-6mfnC;nwtN3UtB&=z_spD|y4v5o);FZ=<23r(j7^jzAO)4}i`SoXF8ah%DJd-+z;T#{|@QLaarAKE5@igffMA9_o8{7j?_GX7dR zysH^C$uIqiJ?KHoF{IFN+*gj{XE*vV@D}5+wMr*xrfYU;PeAh$b*n?Qxs3_4)K~j0 zePc;CB(g38-6G?&-8}tAv;+I}(W&_E&iV4n$wT!F`xT?J{*mn9)cV#cXtCYb^Mu^@mLCw`|bo!f2+Tqv6 zkVoB4Op-9FA&%v*n}}nv`g&>R-_KJR^Su^~w;-D6WyO%fgs(r@?8PikQtz)9ejKg- zArHEMk1&%9NT^1H7jo2kk@9a3c%o5+5(s`%H2Sf5sN?yd-PtJk%4I`dvUR~}DL1A+ zB|Rl0eY=b(^6k@iFtMP}a7bp8UTB|v~kRWT1dW>E`DaG z`O0nbK3miNi7d%S5Uiv91Sa!EXOhkPa*wkW@*T@6?R|^K8Vt!fnrT^|vyMLnw%#Ck z!cUKkEm)dbFllCk38(_-O2~VeSobkR^rYKvY8-Fql_&#b5dfstb6%Z2( zAvXX;TVE{WRx7POxO~01*#H9iC1V*=TdFy^YZAp;uoV=>V?WIlr`W2jqW-vu$Yg2DpkXyo+O7=a*s0$O1v(P{o`A1DralU$;gRAZ zMdrZH+8?>#rY2c*KlG5C%t1j$RTvzuCE(x75a4A2s(kAh79i(a+(|u7nFzl0 zkSuQu`mzt59w&V5E6`NBx~JEZcQgA#FZ9vO_t8*_vcQTr25weT1znr##RsO8F}a^Y zXZ;I!)DjVUKS_kWN$Nw;+-k45qgbcgLVs$yNAkWhx7hzVGLVF@oYg59-gd%P?(RVE zqo@%od|wPT4L?s%YHhP{`r$Le@q2}BPjFgiBoyf-^&NVP^0B=+*n<}qmn5bqoxLg8 zkTotd1qc$>9UzUYz^)>!+YVMyHGVm(({(y)+H{~K4r3o51^R{3D^Q#R-M6_daQciP z6zn4HVK&^@*vN|xud%}omlszSonV>oq>HWIySApKymbP{Eu$BN&cBL^4xT<8*f8

    }GGLotRJ&O?5%_eE-rSK6NkBo-xIkc2J5*WDU<3+QsOf=rynjp5hUMBXButvI z8efXVk#_hV@rcbnHl{~2rsuQ++^{e_?l-2+6kfEX<=SDsfG(Wn(uCvV@A|ObzESR< zP;NJGj^kq8=H%*m&6vx=tmA36F-m&%rr0$RenHrzIu<0O--BYCpTNaS*leQN7-R#_ zyBnK&TD^VSGGnhsg;?KxmivQy;0DI`A67nAM&TvLtgXW;1p{Y-n6}%cp<$n5+EXGw z)8_`EL0Dwo2w9e^%wH~Vt({2h9`VVmtI0}y=?2sYO>NXzS*;@`)LUE*{mBq1M038X zgO;OUZW`?cuWUd3WyB-P|0|-Kp@mB#B|rP}{Rq3k7;nj1L$8-(O;Zcku=D8;qEnHp zdX8?V$OC3|6Zq0nEzY%Bjm5UeSZ{ef-5=Vn1$H`PANF^yR4H9B;>%A}NT-SNlxqxSEG|nvQb#?ao^WK_JGGqq9O4(8IM~|rq|EBJ|$eGJk zv}@xTe>|_n>RNs4j6h~mT-OkBiAwEu?km(J>EZ6K_+dvRXy%vvdmpRcZb3di+p#$j zS}~^;-^OSpX|*>L?;$rn1!Z^=YLp8;GU0dYj@tM#r1Ev>O%h(PNwn_~&~T3|2DPxJ zq6~}`u713m{?O>m!6gLclxmArx!2vV?&BoIGDi%be@#IAdOh<^b$PH_+!DmGcMO1t z2qZ{36Z)H`C`z?3g8*i;anYRCmEEFLIhVwE~9rrzoFB&&4xH9OCZUNODkFVODD?}Cs_SL=!Qy0 z_3o(TBb%vSk1?9PrJB|ae{|ihD{SVEvRHg^Qv{C5luNjlBNp%T=ws6wBr4Lu>8eZu z7}O=^q(@;<(>Px<*EWK+0?2q65EVhHzfOMDZ5$rl6WDXrT!YT#?J?<3Gm$37j*?bo zH&+D8!Wudk`yNUeebn05#f{@RexHmBT4>le5&xBnFxejzJhb!vXn0Z<1AfnWHAu@o zdpSUbH*9l#w!V&v4;HP$UVM@AVbO(m>wyLz%?X6abYUtxo9Tvda(p%f=CM&`CKr27 z*qNbuvg($xwqndB!WT|wi`}aF>%Bh@n(zI_KB%EG;9@j5dDe{PFUbmTDA;T`jp9`Q5OPZ|MHLYm^ac#OPoT5HAw_C3xFx;(Jsg5*#-14(9V`Dawn z)kb1U|GPAfQ2v6Pp@{7WePcM{odshZb4k%$$ zHyXr#mVh954~U+^`w=)F_&JiTMm`^UPl;jY$wIsx+(Dan!>g@BO|f##qYAF|E;1J` zHIow04EMBC9${L^$E*CS3s$7rS`~kB%${N!vq2P1Sg>eg^AWkZOn}n;}S@rY*JEPpJXoc2msH zbt}B3fmg9|M{CoaX?Jf@@dRcj&;i#vNKU1y<<2lypjz*qf2`)Atk!?a3T+O_7UG>p z2$nG`KaMd;XNDAn)~pD#8&?$ zVz@L#%FQ2Gp6d!36Js|&tLrLF+7O(Sp_FC*EAQX_rUhTFWL70F zrYT~rwP?VZ#>{b6rw)|`En!kZu+@Bh2Ta8Aj0$ zduj}=k~odqeRQLcd;12l9r3A+00vP)DRao>o;zc!ysc>HedyeZafdSw53WR>jMDrGxS zNZYtOv;CKyF0Z!I;$ocsmzw6N?&2@1&W#yvSEI0DRL%wFZC0Ng>T=4z9z@5IK!y@f&&bss4V{Jk2w31%df3u&B*P5M|w)=#%ig#xo!`(N$>wq<9S82=(yjkx*05*Bt0&oQ#k7IM#54=CRy@gN{6%YXhM2a9qnt%ZUNG}N; zB}i`)NTdltAtFggNJzfCe&4(9{c+cQ@13>IIdf+Ao_Y4O_nv)Df`ysEiDT!F0RVs# zhPU*r006dHR)6m30oJ{Y`~%H;$cE_KhUf;lyM{dS_Y=PT$k!bZ?CL8kte_(71%Biw zd{tilny`YJx`L9rf|{_Dyn?*^msK?fmL_|Mp{YLm^pVr2xeoeVKC{Of;t0597YqP! za{qO)jW0mytieMbH%xB;0JSNcbl1ZG0C&5g-i_N~bDInPpRU}E?%d8^3lF_3#Prdb zl6P*yxjyEyRKk=0P zrcQ#9Cy!5qRq@feImz|$m!G$^ArCc!N49cXcGiZnnQJOL`@bG2j?A|N!-BsL5!4iS zvD7%jv_pW$RUIt{0f3ZL9=1+l;DN^q*Uh4~r{@LgZq{qcH^=<0>~T-P%Eb#$9 zN_uu!GsI%|vG#q^t=QtGluA-aP>BLtfndG8I%uUrqtPkzNv8YfsMbpF>ybTt+Fz)&;a3*fw|JUvLKGeEs2Ph%H!^_;xfyFis%egNde4A0Jh10!$!-NUnK zF3(gdHN=&IR_Q^>QYHH21EY4Iw1fe|JrEk;k6ky+4grcOcDX6gMMjHm+FCJ;Y>z2c z!khf2T}iyge-S2~n5^z{9Tm2?ZG#p9ZR=^BeotmKbIt7l7-~UfK)xeU>j9|S@>|+( z&v862**NEvN=gBe@C3!2gP@o=kFpml=k8u)e3q(K+ph*aFJ4~JW7fvk91FZq5liVP z(A=YHVD|=pROkOLMbV0x)*O4)C|XuZ`!YCKX(HBu2=j6wg}fFcoh+wspJ zSZW_CYE59x@0QYK;FbkDmN$MeZh2s`mj4RAHcQX&f)K9viTcVU!t%mR%Xb&|yHey* zd4Llzl&E2)S+;q{dW+Znx7=mMNs@ zc&VFl>64gWLZQt@#3hN3i#948J0BZ`>;nc9Ty1}P#1urA zXZ>0fQ1*IPXdv=dd~CqBMlj7$^;q$&lFl3G^NPFmJWBYq;|(rLs&y;9K+VM(%^f;* zKr(A`=VOApS5yC(*qqOIKb__vK_FkUt-00gx8x!V5;iPS_Wke|S^rJ)jIoE~#*mE3 z(+-BvPe;SoC-0Z<*L_KNaz#d)Jlm2}b{k)UaOBlrjQ27nS4R?TSW?4&Qvh{agrOCMtg^N8`!baQfQ4wN|danHb% zJ!_^2IBB9>$hdMvw$l~|2{qTPn*Qnq#gNwHC+~@3TS*6-@Yw~emDq!6T~TaCUadB0*Tb!# zTR5-|IldTIBzg`SJ%w(T$K3>YG`^1dX@xX8H7&^N1%s!QnVN z?wJu*wDfd0%P~Evmj>blXTGbVw&NsyuiYTO`i6$d$!cxS-e|q1%>C78T+Nl>>^xIq zw-UGcA=f#fkZc^9=Frsn`L(&!kXj^pZ4dYJl5%t(ulWc8;ltMPYfGv>R|CD2&Lgl} zzVF1D700Poy~(LuJrV6dtZhPN=^11Nk3zW*7j9tiL~_ub%Hh!^=Zcf+xVuXKK#w? z|C9i@_jm33bH;~pJy-M?9unjR4831Ms;V%s=(XfyfbU%`ivP35KQ8&-1ZtF2P*gFJ zb$(?~tS zH4{74NCRUwTJPGe6y#nCF1k!RXIcn5qaNalG|rOI>0(ZhbK9nwy*58qcUSCgx8IdXCXq1#poELR5% z7OKnk3rK6j+h43xkT;xCWTb6&iT6HWr^4PfL^Sn6bM3E9Uo4T14i*7OCzQ?bu-)VP zfA~z{Fc%?vMfdo!Z}&Iv{L%)4=&{y}kpOc$B}p8*w?HE~r0q#H*R~u%(xNo_j z9~6r^{3*fqG>ADww46P~Y(g`guA33lkqu?5q1096o&2+o3&yu^W)!%g0t=Trj-t(l z0@@s%JGRX7R!`J}+{;bH37)j7iyA%)#D)`+{@ELKyb-N+Mka6>5ulM3J{7%!)L+qq-8soCQwrL6mdWFp z!RJsF`b(7*YYcCki>=)LadQ+INU)DVRp)!sHpqw16XLd#bH#^Vxb@Im3~Ok+nzLFS zkJz(eUmK}=X8wjRO#6RuMNT=ib!}2gz?O)u@ zJbF&LI@&k%h|LPXURc_O>)Lk=zPM994|%eDfb7vQC(@sgwi1~JI+$uh(6>E#BAYuENr7{vv(RW}Qk3n$ILvT57&Lu|P%m$U-8mY8# z>A|Xao#vCi39Y?;nmI3bB2@f%Ps>3^U3vqD{w=dyafaob4vwQP0Pawky*1{wv4WheL?ED!iZ;^uO+=Z=c8pr6Fx)d}rVpK{rzAakgO?go}H2g~r38`HUaPK2yL@;EbRRY6oJ%R8GF4j z^Ey=<6Q@R9-RT+nIo6%qI(0-!)&1nkOwk$Bmu%q6xG-h~JM-7uj0O`eA)pZfWZe%M zjFeurm&)9bBCnjCELmvHcJoQ*D*3kt>8)f}W9B3G(htPwwV4q?Pc8hufvwDhQN|q; zc01oDbk?{bi-er>2d_SN4HU%_ta(1Mpt&pGxgw zAB1+#KxQyp<{cj#eIMHhYA~^UVxPXl?UbaPietdb-R;ZPUZOIiaW$Dg}r&Cv0uiw{~`X=WRc!w{3ghb9zBwW)-RSn5*h!CNoXfhN?&6cET>G9^diSB zvuOTzZSdZXJ62ycVpFwcRH68iCT99}iZi;t8W3VpK-#MeulW4RK7Cy`f@Xe>2~Z}G z^we;7WPd`TRh(51m~%7jVXVD`#xX7?G86ZjZaoTzE~KR^`X>Eb$Cwpdc8P1e-%F`eh9rNB(M|4bObQcBI8 zCX~-efLV#QT0)f(@IQl zE#|wjjmR#se;-$GYxfWl{O^TC_aBQwiI?7IR!rME)|+ctnUyQ!*+sXDxxBKAKJ6fq zPovf}nH$cR{{&nZ>iGZRYYvG4J|^1d^qSM7O&pUla>Zl*ezQvgj|Z<=`qLYpwIxo0 zJtcO-#h^>OF1Yh|zSg5nJLa&IB+19&nI~n6GR3xeP)85WTickYqHekqMj6aJAap6~ ze&vq@@a}YIrDM*5s|K@DnO~y;;>n~bvBb|5uk;Rfmr!*M$YvOmerGI?^73h9I2*sF zB)B9ZS${`A`BWc>(+*^mEux1bLdU%yaf7yv6^2N?L5*JRjkAkO%Q=XYabCW_800R0-0C)c}ydVOWy5Bvk1EN^!?II3BcKQ+@b%9EYy*qD`HPl TBZtZWtY)ZhrdO?V@9Dn)AZhQ@ literal 0 Hc$@ + + +redMine - Aide en ligne + + + + + + + +

    Aide

    + +

    Documentation en ligne redMine

    + +Sommaire: + + + + \ 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 0000000..13922f6 --- /dev/null +++ b/redmine/public/manual/projects.html @@ -0,0 +1,109 @@ + + + +redMine - Aide en ligne + + + + + + + +

    [ Index ]

    +

    Projets

    +Sommaire: +
      +
    1. Aper�u
    2. +
    3. Demandes +
        +
      • Liste des demandes
      • +
      • Nouvelle demande
      • +
      • Changer le statut d'une demande
      • +
      +
    4. +
    5. Rapports
    6. +
    7. Annonces
    8. +
    9. Historique
    10. +
    11. Documents
    12. +
    13. Membres
    14. +
    15. Fichiers
    16. +
    17. Configuration
    18. +
        +
      • Projet
      • +
      • Membres
      • +
      • Versions
      • +
      • Cat�gories de demande
      • +
      +
    + + +

    1. Aper�u

    +

    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. + + +

    2. Demandes

    +

    2.1 Liste des demandes

    +Par d�faut, l'ensemble des demandes sont affich�es. Vous pouvez utiliser les diff�rents filtres pour limiter l'affichage � certaines demandes seulement.
    +Lorsque vous appliquez un filtre, il reste en place durant toute votre session. Vous pouvez le red�finir, ou le supprimer en cliquant sur Annuler. +

    +Liste des demandes
    + +

    2.2 Nouvelle demande

    +

    TODO

    + +

    2.3 Changer le statut d'une demande

    +

    TODO

    + + +

    3. Rapports

    +

    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.

    + + +

    4. Annonces

    +

    Les nouvelles vous permettent d'informer les utilisateurs sur l'activit� du projet.

    + + +

    5. Historique

    +

    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 Trackers).

    + + +

    6. Documents

    +

    Les documents sont group�s par cat�gories (voir Listes de valeurs).
    +Un document peut contenir plusieurs fichiers (exemple: r�visions ou versions successives)

    + + +

    7. Membres

    +

    Affichage de l'ensemble des membres du projet, par r�le

    + + +

    8. Fichiers

    +

    Ce module vous permet de publier les fichiers de l'application (sources, binaires, ...) pour chaque version de l'application .

    + +

    9. Configuration

    +

    9.1 Projet

    +
      +
    • Public: 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.
      + Si le projet n'est pas public, seuls les membres du projet y ont acc�s, en fonction de leur r�le.
    • +
    • Champs personnalis�s: s�lectionner les champs personnalis�s que vous souhaitez utiliser au sein du projet.
      + Seul l'administrateur peut ajouter de nouveaux champs personnalis�s.
    • +
    +

    9.2 Membres

    +

    Cette section vous permet de d�finir les membres du projet ainsi que leurs r�les respectifs.
    +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.

    + +

    9.3 Versions

    +

    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.
    +Vous pouvez par ailleurs publier les diff�rentes versions de l'application (voir Fichiers). +

    + + +

    9.4 Cat�gories de demande

    +

    Les cat�gories de demande vous permettent de typer les demandes. Les cat�gories peuvent par exemple correspondre aux modules de l'application.

    + + + + + diff --git a/redmine/public/manual/stylesheets/help.css b/redmine/public/manual/stylesheets/help.css new file mode 100644 index 0000000..f67bc72 --- /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 0000000..4ab9e89 --- /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 0000000..ac66105 --- /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 0000000..e2954c9 --- /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 0000000..7b07d46 --- /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 0000000..64af76e --- /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 0000000..42f28f7 --- /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 0000000..fa0e6fc --- /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 0000000..ef976e0 --- /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 0000000..c842d35 --- /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 0000000..d855ac8 --- /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 0000000..26ca64c --- /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 0000000..c77f045 --- /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 0000000..7118f39 --- /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 0000000..6816b32 --- /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 0000000..ccc30f9 --- /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 0000000..dfabcb8 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..bb5e51d --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..8794d28 --- /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 0000000..537eb8f --- /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 0000000..e44ac94 --- /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 0000000..f86e325 --- /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 0000000..c9bd463 --- /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 0000000..e9f4b76 --- /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 0000000..291838b --- /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 0000000..4ea4e1c --- /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 0000000..17f11ef --- /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 0000000..1be41f8 --- /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 0000000..5f47c35 --- /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 0000000..b360c6c --- /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 0000000..8da34ec --- /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 0000000..4b52ffa --- /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 0000000..299aef2 --- /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 0000000..75063c6 --- /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 0000000..f1e2281 --- /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 0000000..85b2ef7 --- /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 0000000..d773945 --- /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 0000000..a299c7f --- /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 0000000..6f66833 --- /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 0000000..886bd51 --- /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 0000000..acd96dd --- /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 0000000..ea8c014 --- /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 0000000..6f5051b --- /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 0000000..2adee10 --- /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 0000000..09c0551 --- /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 0000000..3da38e7 --- /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 0000000..8e7c0b9 --- /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 0000000..3b31877 --- /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 0000000..70615d7 --- /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 0000000..443c096 --- /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 0000000..db9b3ab --- /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 0000000..a5ebcdf --- /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 0000000..306c2e6 --- /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 0000000..8a99125 --- /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 0000000..90565ae --- /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 0000000..f738f28 --- /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 0000000..d6a2a22 --- /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 0000000..91c52d4 --- /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 0000000..ff88a97 --- /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 0000000..0996906 --- /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 0000000..72ed7b9 --- /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 0000000..5fe0b1e --- /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