summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2012-06-25 17:49:35 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2012-06-25 17:49:35 +0000
commitac56c0c99ccd14c7229145fc22d6e9eb13ee0af0 (patch)
treef54401b77f7195a1795f4a189f9f9d35734c0a2b
parent5961a1e70d1efdfb5c4fd28c20dc8cc4d9a51bac (diff)
downloadredmine-ac56c0c99ccd14c7229145fc22d6e9eb13ee0af0.tar.gz
redmine-ac56c0c99ccd14c7229145fc22d6e9eb13ee0af0.zip
Ability to close projects (read-only) (#3640).
A new permission (Close/reopen project) is available to give non-admin users the ability to close their projects. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@9883 e93f8b46-1217-0410-a6f0-8f06a7374b81
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb16
-rw-r--r--app/helpers/admin_helper.rb4
-rw-r--r--app/helpers/application_helper.rb8
-rw-r--r--app/models/principal.rb2
-rw-r--r--app/models/project.rb35
-rw-r--r--app/models/user.rb6
-rw-r--r--app/views/admin/projects.html.erb6
-rw-r--r--app/views/projects/index.html.erb8
-rw-r--r--app/views/projects/show.html.erb13
-rw-r--r--config/locales/en.yml9
-rw-r--r--config/locales/fr.yml9
-rw-r--r--config/routes.rb2
-rw-r--r--lib/redmine.rb34
-rw-r--r--lib/redmine/access_control.rb15
-rw-r--r--lib/redmine/plugin.rb8
-rw-r--r--test/fixtures/roles.yml1
-rw-r--r--test/functional/projects_controller_test.rb47
-rw-r--r--test/integration/routing/projects_test.rb8
-rw-r--r--test/unit/lib/redmine/access_control_test.rb10
-rw-r--r--test/unit/user_test.rb12
21 files changed, 213 insertions, 42 deletions
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 17968bf79..645e8389b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -276,7 +276,7 @@ class ApplicationController < ActionController::Base
# make sure that the user is a member of the project (or admin) if project is private
# used as a before_filter for actions that do not require any particular permission on the project
def check_project_privacy
- if @project && @project.active?
+ if @project && !@project.archived?
if @project.visible?
true
else
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index c89167aa3..492e31795 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -48,7 +48,11 @@ class ProjectsController < ApplicationController
def index
respond_to do |format|
format.html {
- @projects = Project.visible.find(:all, :order => 'lft')
+ scope = Project
+ unless params[:closed]
+ scope = scope.active
+ end
+ @projects = scope.visible.order('lft').all
}
format.api {
@offset, @limit = api_offset_and_limit
@@ -224,6 +228,16 @@ class ProjectsController < ApplicationController
redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
end
+ def close
+ @project.close
+ redirect_to project_path(@project)
+ end
+
+ def reopen
+ @project.reopen
+ redirect_to project_path(@project)
+ end
+
# Delete @project
def destroy
@project_to_destroy = @project
diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb
index 01f394dd1..bacc7b8ea 100644
--- a/app/helpers/admin_helper.rb
+++ b/app/helpers/admin_helper.rb
@@ -20,6 +20,8 @@
module AdminHelper
def project_status_options_for_select(selected)
options_for_select([[l(:label_all), ''],
- [l(:status_active), '1']], selected.to_s)
+ [l(:project_status_active), '1'],
+ [l(:project_status_closed), '5'],
+ [l(:project_status_archived), '9']], selected.to_s)
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 794cc6f54..8c328326f 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -145,11 +145,11 @@ module ApplicationHelper
# link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
#
def link_to_project(project, options={}, html_options = nil)
- if project.active?
+ if project.archived?
+ h(project)
+ else
url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
link_to(h(project), url, html_options)
- else
- h(project)
end
end
@@ -237,7 +237,7 @@ module ApplicationHelper
# Renders the project quick-jump box
def render_project_jump_box
return unless User.current.logged?
- projects = User.current.memberships.collect(&:project).compact.uniq
+ projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
if projects.any?
s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
"<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
diff --git a/app/models/principal.rb b/app/models/principal.rb
index c4968016a..2b89e16a9 100644
--- a/app/models/principal.rb
+++ b/app/models/principal.rb
@@ -19,7 +19,7 @@ class Principal < ActiveRecord::Base
self.table_name = "#{table_name_prefix}users#{table_name_suffix}"
has_many :members, :foreign_key => 'user_id', :dependent => :destroy
- has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
+ has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}", :order => "#{Project.table_name}.name"
has_many :projects, :through => :memberships
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
diff --git a/app/models/project.rb b/app/models/project.rb
index 1b8f30db3..966806ca9 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -20,6 +20,7 @@ class Project < ActiveRecord::Base
# Project statuses
STATUS_ACTIVE = 1
+ STATUS_CLOSED = 5
STATUS_ARCHIVED = 9
# Maximum length for project identifiers
@@ -161,12 +162,11 @@ class Project < ActiveRecord::Base
# * :with_subprojects => limit the condition to project and its subprojects
# * :member => limit the condition to the user projects
def self.allowed_to_condition(user, permission, options={})
- base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
- if perm = Redmine::AccessControl.permission(permission)
- unless perm.project_module.nil?
- # If the permission belongs to a project module, make sure the module is enabled
- base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
- end
+ perm = Redmine::AccessControl.permission(permission)
+ base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
+ if perm && perm.project_module
+ # If the permission belongs to a project module, make sure the module is enabled
+ base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
end
if options[:project]
project_statement = "#{Project.table_name}.id = #{options[:project].id}"
@@ -325,6 +325,14 @@ class Project < ActiveRecord::Base
update_attribute :status, STATUS_ACTIVE
end
+ def close
+ self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED
+ end
+
+ def reopen
+ self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE
+ end
+
# Returns an array of projects the project can be moved to
# by the current user
def allowed_parents
@@ -404,7 +412,7 @@ class Project < ActiveRecord::Base
@rolled_up_trackers ||=
Tracker.find(:all, :joins => :projects,
:select => "DISTINCT #{Tracker.table_name}.*",
- :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
+ :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt],
:order => "#{Tracker.table_name}.position")
end
@@ -423,20 +431,20 @@ class Project < ActiveRecord::Base
def rolled_up_versions
@rolled_up_versions ||=
Version.scoped(:include => :project,
- :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt])
+ :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt])
end
# Returns a scope of the Versions used by the project
def shared_versions
if new_record?
Version.scoped(:include => :project,
- :conditions => "#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND #{Version.table_name}.sharing = 'system'")
+ :conditions => "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND #{Version.table_name}.sharing = 'system'")
else
@shared_versions ||= begin
r = root? ? self : root
Version.scoped(:include => :project,
:conditions => "#{Project.table_name}.id = #{id}" +
- " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
+ " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" +
" #{Version.table_name}.sharing = 'system'" +
" OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
" OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
@@ -515,6 +523,13 @@ class Project < ActiveRecord::Base
s << ' root' if root?
s << ' child' if child?
s << (leaf? ? ' leaf' : ' parent')
+ unless active?
+ if archived?
+ s << ' archived'
+ else
+ s << ' closed'
+ end
+ end
s
end
diff --git a/app/models/user.rb b/app/models/user.rb
index d0d1df834..1b7a8ea4f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -394,7 +394,7 @@ class User < Principal
def roles_for_project(project)
roles = []
# No role on archived projects
- return roles unless project && project.active?
+ return roles if project.nil? || project.archived?
if logged?
# Find project membership
membership = memberships.detect {|m| m.project_id == project.id}
@@ -456,9 +456,11 @@ class User < Principal
def allowed_to?(action, context, options={}, &block)
if context && context.is_a?(Project)
# No action allowed on archived projects
- return false unless context.active?
+ return false if context.archived?
# No action allowed on disabled modules
return false unless context.allows_to?(action)
+ # No write action allowed on closed projects
+ return false unless context.active? || Redmine::AccessControl.read_action?(action)
# Admin users are authorized for anything else
return true if admin?
diff --git a/app/views/admin/projects.html.erb b/app/views/admin/projects.html.erb
index 6525f8ea5..1d8bcc002 100644
--- a/app/views/admin/projects.html.erb
+++ b/app/views/admin/projects.html.erb
@@ -27,12 +27,12 @@
<tbody>
<% project_tree(@projects) do |project, level| %>
<tr class="<%= cycle("odd", "even") %> <%= project.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
- <td class="name"><span><%= link_to_project(project, {:action => 'settings'}, :title => project.short_description) %></span></td>
+ <td class="name"><span><%= link_to_project(project, {:action => (project.active? ? 'settings' : 'show')}, :title => project.short_description) %></span></td>
<td align="center"><%= checked_image project.is_public? %></td>
<td align="center"><%= format_date(project.created_on) %></td>
<td class="buttons">
- <%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %>
- <%= link_to(l(:button_unarchive), { :controller => 'projects', :action => 'unarchive', :id => project, :status => params[:status] }, :method => :post, :class => 'icon icon-unlock') if !project.active? && (project.parent.nil? || project.parent.active?) %>
+ <%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') unless project.archived? %>
+ <%= link_to(l(:button_unarchive), { :controller => 'projects', :action => 'unarchive', :id => project, :status => params[:status] }, :method => :post, :class => 'icon icon-unlock') if project.archived? && (project.parent.nil? || !project.parent.archived?) %>
<%= link_to(l(:button_copy), { :controller => 'projects', :action => 'copy', :id => project }, :class => 'icon icon-copy') %>
<%= link_to(l(:button_delete), project_path(project), :method => :delete, :class => 'icon icon-del') %>
</td>
diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb
index 42b400ff7..3d527917a 100644
--- a/app/views/projects/index.html.erb
+++ b/app/views/projects/index.html.erb
@@ -25,4 +25,12 @@
<%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
<% end %>
+<% content_for :sidebar do %>
+ <%= form_tag({}, :method => :get) do %>
+ <h3><%= l(:label_project_plural) %></h3>
+ <label for="closed"><%= check_box_tag 'closed', 1, params[:closed] %> <%= l(:label_show_closed_projects) %></label>
+ <p><%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %></p>
+ <% end %>
+<% end %>
+
<% html_title(l(:label_project_plural)) -%>
diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb
index 4963ebbe4..7bd37fc7b 100644
--- a/app/views/projects/show.html.erb
+++ b/app/views/projects/show.html.erb
@@ -2,14 +2,27 @@
<% if User.current.allowed_to?(:add_subprojects, @project) %>
<%= link_to l(:label_subproject_new), {:controller => 'projects', :action => 'new', :parent_id => @project}, :class => 'icon icon-add' %>
<% end %>
+ <% if User.current.allowed_to?(:close_project, @project) %>
+ <% if @project.active? %>
+ <%= link_to l(:button_close), close_project_path(@project), :data => {:confirm => l(:text_are_you_sure)}, :method => :post, :class => 'icon icon-lock' %>
+ <% else %>
+ <%= link_to l(:button_reopen), reopen_project_path(@project), :data => {:confirm => l(:text_are_you_sure)}, :method => :post, :class => 'icon icon-unlock' %>
+ <% end %>
+ <% end %>
</div>
<h2><%=l(:label_overview)%></h2>
+<% unless @project.active? %>
+ <p class="warning"><span class="icon icon-lock"><%= l(:text_project_closed) %></span></p>
+<% end %>
+
<div class="splitcontentleft">
+ <% if @project.description.present? %>
<div class="wiki">
<%= textilizable @project.description %>
</div>
+ <% end %>
<ul>
<% unless @project.homepage.blank? %>
<li><%=l(:field_homepage)%>: <%= link_to h(@project.homepage), @project.homepage %></li>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 05ef97b0e..66d1f4952 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -395,6 +395,7 @@ en:
permission_add_project: Create project
permission_add_subprojects: Create subprojects
permission_edit_project: Edit project
+ permission_close_project: Close / reopen the project
permission_select_project_modules: Select project modules
permission_manage_members: Manage members
permission_manage_project_activities: Manage project activities
@@ -854,6 +855,7 @@ en:
label_completed_versions: Completed versions
label_search_for_watchers: Search for watchers to add
label_session_expiration: Session expiration
+ label_show_closed_projects: View closed projects
button_login: Login
button_submit: Submit
@@ -904,11 +906,17 @@ en:
button_edit_section: Edit this section
button_export: Export
button_delete_my_account: Delete my account
+ button_close: Close
+ button_reopen: Reopen
status_active: active
status_registered: registered
status_locked: locked
+ project_status_active: active
+ project_status_closed: closed
+ project_status_archived: archived
+
version_status_open: open
version_status_locked: locked
version_status_closed: closed
@@ -990,6 +998,7 @@ en:
text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}"
text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it."
text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours."
+ text_project_closed: This project is closed and read-only.
default_role_manager: Manager
default_role_developer: Developer
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 64b65c258..f05afbb48 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -391,6 +391,7 @@ fr:
permission_add_project: Créer un projet
permission_add_subprojects: Créer des sous-projets
permission_edit_project: Modifier le projet
+ permission_close_project: Fermer / réouvrir le projet
permission_select_project_modules: Choisir les modules
permission_manage_members: Gérer les membres
permission_manage_versions: Gérer les versions
@@ -829,6 +830,7 @@ fr:
label_item_position: "%{position} sur %{count}"
label_completed_versions: Versions passées
label_session_expiration: Expiration des sessions
+ label_show_closed_projects: Voir les projets fermés
button_login: Connexion
button_submit: Soumettre
@@ -878,11 +880,17 @@ fr:
button_edit_section: Modifier cette section
button_export: Exporter
button_delete_my_account: Supprimer mon compte
+ button_close: Fermer
+ button_reopen: Réouvrir
status_active: actif
status_registered: enregistré
status_locked: verrouillé
+ project_status_active: actif
+ project_status_closed: fermé
+ project_status_archived: archivé
+
version_status_open: ouvert
version_status_locked: verrouillé
version_status_closed: fermé
@@ -946,6 +954,7 @@ fr:
text_issue_conflict_resolution_cancel: "Annuler ma mise à jour et réafficher %{link}"
text_account_destroy_confirmation: "Êtes-vous sûr de vouloir continuer ?\nVotre compte sera définitivement supprimé, sans aucune possibilité de le réactiver."
text_session_expiration_settings: "Attention : le changement de ces paramètres peut entrainer l'expiration des sessions utilisateurs en cours, y compris la vôtre."
+ text_project_closed: Ce projet est fermé et accessible en lecture seule.
default_role_manager: "Manager "
default_role_developer: "Développeur "
diff --git a/config/routes.rb b/config/routes.rb
index 3e89a88e4..e79d0226f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -93,6 +93,8 @@ RedmineApp::Application.routes.draw do
post 'modules'
post 'archive'
post 'unarchive'
+ post 'close'
+ post 'reopen'
match 'copy', :via => [:get, :post]
end
diff --git a/lib/redmine.rb b/lib/redmine.rb
index 6b74af731..1b9be57f5 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -47,10 +47,11 @@ end
# Permissions
Redmine::AccessControl.map do |map|
- map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true
- map.permission :search_project, {:search => :index}, :public => true
+ map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true, :read => true
+ map.permission :search_project, {:search => :index}, :public => true, :read => true
map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
+ map.permission :close_project, {:projects => [:close, :reopen]}, :require => :member, :read => true
map.permission :select_project_modules, {:projects => :modules}, :require => :member
map.permission :manage_members, {:projects => :settings, :members => [:index, :show, :create, :update, :destroy, :autocomplete]}, :require => :member
map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
@@ -66,7 +67,8 @@ Redmine::AccessControl.map do |map|
:versions => [:index, :show, :status_by],
:journals => [:index, :diff],
:queries => :index,
- :reports => [:issue_report, :issue_report_details]}
+ :reports => [:issue_report, :issue_report_details]},
+ :read => true
map.permission :add_issues, {:issues => [:new, :create, :update_form], :attachments => :upload}
map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update, :update_form], :journals => [:new], :attachments => :upload}
map.permission :manage_issue_relations, {:issue_relations => [:index, :show, :create, :destroy]}
@@ -82,14 +84,14 @@ Redmine::AccessControl.map do |map|
map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member
map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
# Watchers
- map.permission :view_issue_watchers, {}
+ map.permission :view_issue_watchers, {}, :read => true
map.permission :add_issue_watchers, {:watchers => :new}
map.permission :delete_issue_watchers, {:watchers => :destroy}
end
map.project_module :time_tracking do |map|
map.permission :log_time, {:timelog => [:new, :create]}, :require => :loggedin
- map.permission :view_time_entries, :timelog => [:index, :report, :show]
+ map.permission :view_time_entries, {:timelog => [:index, :report, :show]}, :read => true
map.permission :edit_time_entries, {:timelog => [:edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member
map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin
map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
@@ -97,27 +99,27 @@ Redmine::AccessControl.map do |map|
map.project_module :news do |map|
map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy]}, :require => :member
- map.permission :view_news, {:news => [:index, :show]}, :public => true
+ map.permission :view_news, {:news => [:index, :show]}, :public => true, :read => true
map.permission :comment_news, {:comments => :create}
end
map.project_module :documents do |map|
map.permission :manage_documents, {:documents => [:new, :create, :edit, :update, :destroy, :add_attachment]}, :require => :loggedin
- map.permission :view_documents, :documents => [:index, :show, :download]
+ map.permission :view_documents, {:documents => [:index, :show, :download]}, :read => true
end
map.project_module :files do |map|
map.permission :manage_files, {:files => [:new, :create]}, :require => :loggedin
- map.permission :view_files, :files => :index, :versions => :download
+ map.permission :view_files, {:files => :index, :versions => :download}, :read => true
end
map.project_module :wiki do |map|
map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
- map.permission :view_wiki_pages, :wiki => [:index, :show, :special, :date_index]
- map.permission :export_wiki_pages, :wiki => [:export]
- map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
+ map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true
+ map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true
+ map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true
map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment]
map.permission :delete_wiki_pages_attachments, {}
map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
@@ -125,15 +127,15 @@ Redmine::AccessControl.map do |map|
map.project_module :repository do |map|
map.permission :manage_repository, {:repositories => [:new, :create, :edit, :update, :committers, :destroy]}, :require => :member
- map.permission :browse_repository, :repositories => [:show, :browse, :entry, :raw, :annotate, :changes, :diff, :stats, :graph]
- map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
+ map.permission :browse_repository, {:repositories => [:show, :browse, :entry, :raw, :annotate, :changes, :diff, :stats, :graph]}, :read => true
+ map.permission :view_changesets, {:repositories => [:show, :revisions, :revision]}, :read => true
map.permission :commit_access, {}
map.permission :manage_related_issues, {:repositories => [:add_related_issue, :remove_related_issue]}
end
map.project_module :boards do |map|
map.permission :manage_boards, {:boards => [:new, :create, :edit, :update, :destroy]}, :require => :member
- map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
+ map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true, :read => true
map.permission :add_messages, {:messages => [:new, :reply, :quote]}
map.permission :edit_messages, {:messages => :edit}, :require => :member
map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
@@ -142,11 +144,11 @@ Redmine::AccessControl.map do |map|
end
map.project_module :calendar do |map|
- map.permission :view_calendar, :calendars => [:show, :update]
+ map.permission :view_calendar, {:calendars => [:show, :update]}, :read => true
end
map.project_module :gantt do |map|
- map.permission :view_gantt, :gantts => [:show, :update]
+ map.permission :view_gantt, {:gantts => [:show, :update]}, :read => true
end
end
diff --git a/lib/redmine/access_control.rb b/lib/redmine/access_control.rb
index 3845fa5e7..b4f3cca73 100644
--- a/lib/redmine/access_control.rb
+++ b/lib/redmine/access_control.rb
@@ -54,6 +54,16 @@ module Redmine
@loggedin_only_permissions ||= @permissions.select {|p| p.require_loggedin?}
end
+ def read_action?(action)
+ if action.is_a?(Symbol)
+ perm = permission(action)
+ !perm.nil? && perm.read?
+ else
+ s = "#{action[:controller]}/#{action[:action]}"
+ permissions.detect {|p| p.actions.include?(s) && !p.read?}.nil?
+ end
+ end
+
def available_project_modules
@available_project_modules ||= @permissions.collect(&:project_module).uniq.compact
end
@@ -93,6 +103,7 @@ module Redmine
@actions = []
@public = options[:public] || false
@require = options[:require]
+ @read = options[:read] || false
@project_module = options[:project_module]
hash.each do |controller, actions|
if actions.is_a? Array
@@ -115,6 +126,10 @@ module Redmine
def require_loggedin?
@require && (@require == :member || @require == :loggedin)
end
+
+ def read?
+ @read
+ end
end
end
end
diff --git a/lib/redmine/plugin.rb b/lib/redmine/plugin.rb
index 7805c73fb..c3f41501c 100644
--- a/lib/redmine/plugin.rb
+++ b/lib/redmine/plugin.rb
@@ -244,13 +244,15 @@ module Redmine #:nodoc:
# permission :destroy_contacts, { :contacts => :destroy }
# permission :view_contacts, { :contacts => [:index, :show] }
#
- # The +options+ argument can be used to make the permission public (implicitly given to any user)
- # or to restrict users the permission can be given to.
+ # The +options+ argument is a hash that accept the following keys:
+ # * :public => the permission is public if set to true (implicitly given to any user)
+ # * :require => can be set to one of the following values to restrict users the permission can be given to: :loggedin, :member
+ # * :read => set it to true so that the permission is still granted on closed projects
#
# Examples
# # A permission that is implicitly given to any user
# # This permission won't appear on the Roles & Permissions setup screen
- # permission :say_hello, { :example => :say_hello }, :public => true
+ # permission :say_hello, { :example => :say_hello }, :public => true, :read => true
#
# # A permission that can be given to any user
# permission :say_hello, { :example => :say_hello }
diff --git a/test/fixtures/roles.yml b/test/fixtures/roles.yml
index 0eaaafe72..78b35c16e 100644
--- a/test/fixtures/roles.yml
+++ b/test/fixtures/roles.yml
@@ -8,6 +8,7 @@ roles_001:
---
- :add_project
- :edit_project
+ - :close_project
- :select_project_modules
- :manage_members
- :manage_versions
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
index 299a6e767..5fbb7e20e 100644
--- a/test/functional/projects_controller_test.rb
+++ b/test/functional/projects_controller_test.rb
@@ -380,6 +380,21 @@ class ProjectsControllerTest < ActionController::TestCase
assert_template 'settings'
end
+ def test_settings_should_be_denied_for_member_on_closed_project
+ Project.find(1).close
+ @request.session[:user_id] = 2 # manager
+
+ get :settings, :id => 1
+ assert_response 403
+ end
+
+ def test_settings_should_be_denied_for_anonymous_on_closed_project
+ Project.find(1).close
+
+ get :settings, :id => 1
+ assert_response 302
+ end
+
def test_update
@request.session[:user_id] = 2 # manager
post :update, :id => 1, :project => {:name => 'Test changed name',
@@ -397,6 +412,23 @@ class ProjectsControllerTest < ActionController::TestCase
assert_error_tag :content => /name can't be blank/i
end
+ def test_update_should_be_denied_for_member_on_closed_project
+ Project.find(1).close
+ @request.session[:user_id] = 2 # manager
+
+ post :update, :id => 1, :project => {:name => 'Closed'}
+ assert_response 403
+ assert_equal 'eCookbook', Project.find(1).name
+ end
+
+ def test_update_should_be_denied_for_anonymous_on_closed_project
+ Project.find(1).close
+
+ post :update, :id => 1, :project => {:name => 'Closed'}
+ assert_response 302
+ assert_equal 'eCookbook', Project.find(1).name
+ end
+
def test_modules
@request.session[:user_id] = 2
Project.find(1).enabled_module_names = ['issue_tracking', 'news']
@@ -444,6 +476,21 @@ class ProjectsControllerTest < ActionController::TestCase
assert Project.find(1).active?
end
+ def test_close
+ @request.session[:user_id] = 2
+ post :close, :id => 1
+ assert_redirected_to '/projects/ecookbook'
+ assert_equal Project::STATUS_CLOSED, Project.find(1).status
+ end
+
+ def test_reopen
+ Project.find(1).close
+ @request.session[:user_id] = 2
+ post :reopen, :id => 1
+ assert_redirected_to '/projects/ecookbook'
+ assert Project.find(1).active?
+ end
+
def test_project_breadcrumbs_should_be_limited_to_3_ancestors
CustomField.delete_all
parent = nil
diff --git a/test/integration/routing/projects_test.rb b/test/integration/routing/projects_test.rb
index d53c0607c..2d4756f26 100644
--- a/test/integration/routing/projects_test.rb
+++ b/test/integration/routing/projects_test.rb
@@ -70,6 +70,14 @@ class RoutingProjectsTest < ActionController::IntegrationTest
{ :controller => 'projects', :action => 'unarchive', :id => '64' }
)
assert_routing(
+ { :method => 'post', :path => "/projects/64/close" },
+ { :controller => 'projects', :action => 'close', :id => '64' }
+ )
+ assert_routing(
+ { :method => 'post', :path => "/projects/64/reopen" },
+ { :controller => 'projects', :action => 'reopen', :id => '64' }
+ )
+ assert_routing(
{ :method => 'put', :path => "/projects/4223" },
{ :controller => 'projects', :action => 'update', :id => '4223' }
)
diff --git a/test/unit/lib/redmine/access_control_test.rb b/test/unit/lib/redmine/access_control_test.rb
index 94c83e179..7c0f85475 100644
--- a/test/unit/lib/redmine/access_control_test.rb
+++ b/test/unit/lib/redmine/access_control_test.rb
@@ -46,4 +46,14 @@ class Redmine::AccessControlTest < ActiveSupport::TestCase
assert perm.actions.is_a?(Array)
assert perm.actions.include?('projects/settings')
end
+
+ def test_read_action_should_return_true_for_read_actions
+ assert_equal true, @access_module.read_action?(:view_project)
+ assert_equal true, @access_module.read_action?(:controller => 'projects', :action => 'show')
+ end
+
+ def test_read_action_should_return_false_for_update_actions
+ assert_equal false, @access_module.read_action?(:edit_project)
+ assert_equal false, @access_module.read_action?(:controller => 'projects', :action => 'edit')
+ end
end
diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb
index 2ff55f9f3..51a49de01 100644
--- a/test/unit/user_test.rb
+++ b/test/unit/user_test.rb
@@ -874,6 +874,18 @@ class UserTest < ActiveSupport::TestCase
assert ! @admin.allowed_to?(:view_issues, Project.find(1))
end
+ should "return false for write action if project is closed" do
+ project = Project.find(1)
+ Project.any_instance.stubs(:status).returns(Project::STATUS_CLOSED)
+ assert ! @admin.allowed_to?(:edit_project, Project.find(1))
+ end
+
+ should "return true for read action if project is closed" do
+ project = Project.find(1)
+ Project.any_instance.stubs(:status).returns(Project::STATUS_CLOSED)
+ assert @admin.allowed_to?(:view_project, Project.find(1))
+ end
+
should "return false if related module is disabled" do
project = Project.find(1)
project.enabled_module_names = ["issue_tracking"]
three persons, for the purpose of crossing any river that might obstruct our progress. This operation was accordingly commenced, and by the 31st both the canoes being finished, we prepared for our departure on the following day. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> The leather which had been preserved for making shoes was equally divided among the men, two pairs of flannel socks were given to each person, and such articles of warm clothing as remained, were issued to those who most required them. They were also furnished with one of the officers' tents. This being done, I communicated to the men my intention of proceeding in as direct a course as possible to the part of Point Lake, opposite to our spring encampment which was only distant one hundred and forty-nine miles in a straight line. They received the communication cheerfully, considered the journey to be short, and left me, in high spirits, to arrange their own packages. The stores, books, &amp;c., which were not absolutely necessary to be carried, were then put up in boxes to be left en cache here, in order that the men's burdens might be as light as possible. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> The next morning was warm, and very fine. Every one was on the alert at an early hour, being anxious to commence the journey. Our luggage consisted of ammunition, nets, hatchets, ice chisels, astronomical instruments, clothing, blankets, three kettles, and the two canoes, which were each carried by one man. The officers carried such a portion of their own things as their strength would permit;the weight carried by each man was about ninety pounds, and with this we advanced at the rate of about a mile an hour, including rests. In the evening the hunters killed a lean cow, out of a large drove of musk-oxen; but the men were too much laden to carry more than a small portion of its flesh. The alluvial soil, which towards the mouth of the river spreads into plains, covered with grass and willows, was now giving place to a more barren and hilly country; so that we could but just collect sufficient brush wood to cook our suppers. The part of the river we skirted to-day was shallow, and flowed over a bed of sand ; its width about one hundred and twenty yards. About midnight our tent was blown down by a squall, and we were completely drenched with rain before it could be re-pitched. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> In the morning of the 1st of September a fall of snow took place; the canoes became a cause of delay, by the difficulty of carrying them in a high wind, and they sustained much damage from the falls of those who had charge of them. The face of the country was broken by hills of moderate elevation, but the ground was plentifully strewed with small stones, which, to men bearing heavy burthens, and whose feet were protected only by soft moose skin shoes, occasioned great pain. At the end of eleven miles we encamped, and sent for a musk-ox and a deer, which St. Germain and Augustus had killed. The day was extremely cold, the ther-mometer varying between 34 deg and 36 deg. In the afternoon a heavy fall of snow took place, on the wind changing from north-west to south-west. We found no wood at the encampment, but made a fire of moss to cook the supper, and crept under our blankets for warmth. At sun-rise the thermometer was at 31 deg, and the wind fresh from north-west; but the weather became mild in the course of the forenoon, and the snow disappeared from the gravel. The afternoon was remarkably fine, and the thermometer rose to 50 deg. One of the hunters killed a musk-ox. The hills in this part are lower and more round-backed than those we passed yesterday, and exhibited but little naked rock; they were covered with lichens. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> Having ascertained from the summit of the highest hill near the tents, that the river continued to preserve a west course; and fear-ing that by pursuing it further we might lose much time, and un-necessarily walk over a great deal of ground, I determined on quitting its banks the next day, and making as directly as we could for Point Lake. We accordingly followed the river on the 3d, only to the place where the musk-ox had been killed last evening, and after the meat was procured, crossed the river in our two canoes lashed together. We now emerged from the valley of the river, and entered a level, but very barren, country, varied only by small lakes and marshes, the ground being covered with small stones. Many old tracks of rein-deer were seen in the clayey soil, and some more recent ones of the musk-ox. We encamped on the borders of Wright's River, which flows to the eastward; the direct distance walked to-day being ten miles and three-quarters. The next morning was very fine, and,as the day advanced, the weather became quite warm. We set out at six A. M., and, having forded the river, walked over a perfectly level country, interspersed with small lakes, which communicated with each other, by streams running in various directions. No berry-bearing plants were found in this part, the surface of the earth being thinly covered in the moister places with a few grasses, and on the drier spots with lichens. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> Having walked twelve miles and a half, we encamped at seven P.M., and distributed our last piece of pemmican, and a little arrow-root for supper, which afforded but a scanty meal. This evening was warm, but dark clouds overspread the sky. Our men now began to find their burdens very oppressive, and were much fatigued by this day's march, but did not complain. One of them was lame from an inflammation in the knee. Heavy rain commenced at midnight, and continued without intermission until five in the morning, when it was succeeded by snow on the wind changing to north-west, which soon increased to a violent gale. As we had nothing to eat, and were destitute of the means of making a fire, we remained in our beds all the day ; but the covering of our blankets was insufficient to prevent us from feeling the severity of the frost, and suffering in-convenience from the drifting of the snow into our tents. There was no abatement of the storm next day ; our tents were completely frozen, and the snow had drifted around them to a depth of three feet, and even in the inside there was a covering of several inches on our blankets. Our suffering from cold, in a comfortless canvass tent in such weather, with the temperature at 20 deg, and without fire, will easily be imagined;it was, however, less than that which we felt from hunger. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> The morning of the 7th cleared up a little, but the wind was still strong, and the weather extremely cold. From the unusual con-tinuance of the storm, we feared the winter had set in with all its rigour, and that by longer delay we should only be exposed to an accumulation of difficulties; we therefore prepared for our journey, although we were in a very unfit condition for starting, being weak from fasting, and our garments stiffened by the frost. We had no means of making a fire to thaw them, the moss, at all times difficult to kindle, being now covered with ice and snow. A considerable time was consumed in packing up the frozen tents and bed clothes, the wind blowing so strong that no one could keep his hands long out of his mittens. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> Just as we were about to commence our march, I was seized with a fainting fit, in consequence of exhaustion and sudden exposure to the wind; but after eating a morsel of portable soup, I recovered, so far as to be able to move on. I was unwilling at first to take this morsel of soup, which was diminishing the small and only remaining meal for the party; but several of the men urged me to it, with much kindness. The ground was covered a foot deep with snow, the margin of the lakes was incrusted with ice, and the swamps over which we had to pass were entirely frozen; but the ice not being sufficiently strong to bear us, we frequently plunged knee-deep in water. Those who carried the canoes were repeatedly blown down by the violence of the wind, and they often fell, from making an insecure step on a slippery stone; on one of these occasions, the largest canoe was so much broken as to be rendered utterly unser-viceable. This was felt as a serious disaster, as the remaining canoe having through mistake been made too small, it was doubtful whether it would be sufficient to carry us across a river. Indeed we had found it necessary in crossing Hood's River, to lash the two canoes together. As there was some suspicion that Benoit, who carried the canoe, had broken it intentionally, he having on a former occasion been overheard by some of the men to say, that he would do so when he got it in charge, we closely examined him on the point; he roundly denied having used the expressions attributed to him, and insisted that it was broken by his falling accidentally; and as he brought men to attest the latter fact, who saw him tumble, we did not press the matter further. I may here remark that our people had murmured a good deal at having to carry two canoes, though they were informed of the necessity of taking both, in case it should be deemed advisable to divide the party; which it had been thought probable we should be obliged to do, if animals proved scarce, in order to give the whole the better chance of procuring subsistence, and also for the purpose of sending forward some of the best walkers to search for Indians, and to get them to meet us with supplies of provision. The power of doing this was now at an end. As the accident could not be remedied we turned it to the best account by making a fire of the bark and timbers of the broken vessel, and cooked the remainder of our portable soup and arrow-root. This was a scanty meal after three days' fasting, but it served to allay the pangs of hunger, and enabled us to proceed at a quicker pace than before. The depth of the snow caused us to march in Indian file, that is in each other's steps; the voyagers taking it in turn to lead the party. A distant object was pointed out to this man in the direction we wished to take, and Mr. Hood followed immediately behind him, to renew the bearings, and keep him from deviating more than could be helped from the mark. It may be here observed, that we proceeded in this manner throughout our route across the barren grounds. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> In the afternoon we got into a more hilly country, where the ground was strewed with large stones. The surface of these was covered with lichens of the genus gyrophora, which the Canadians term tripe de roche. A considerable quantity was gathered, and with half a partridge each, (which were shot in the course of the day,) furnished us with a slender supper, which we cooked with a few willows, dug up from beneath the snow. We passed a comfortless night in our damp clothes, but took the precaution of sleeping upon our socks and shoes to prevent them from freezing. This plan was afterwards adopted throughout the journey. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> At half past five in the morning we proceeded; and after walking about two miles, came to Cracroft's River, flowing to the westward, with a very rapid current over a rocky channel. We had much diffi-culty in crossing this, the canoe being useless, not only from the bot-tom of the channel being obstructed by large stones, but also from its requiring gumming, an operation which, owing to the want of wood and the frost, we were unable to perform. However, after following the course of the river some way, we effected a passage by means of a range of large rocks that crossed a rapid. As the current was strong, and many of the rocks were covered with water to the depth of two or three feet, the men were exposed to much danger in carrying their heavy burthens across, and several of them actually slipped into the stream, but were immediately rescued by the others. Junius went farther up the river in search of a better crossing place, and did not rejoin us to-day. As several of the party were drenched from head to foot, and we were all wet to the middle, our clothes became stiff with the frost, and we walked with much pain for the remainder of the day. The march was continued to a late hour, being anxious to rejoin the hunters who had gone before, but we were obliged to encamp at the end of ten miles and a quarter, without seeing them. Our only meal to-day consisted of a partridge each, (which the hunters shot,) mixed with tripe de roche. This repast although scanty for men, with appetites such as our daily fatigue created, proved a cheerful one, and was received with thankfulness. Most of the men had to sleep in the open air, in consequence of the absence of Credit, who carried their tent; but we fortunately found an unusual quantity of roots to make a fire, which prevented their suffering much from the cold, though the thermometer was at 17 deg. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> We started at six on the 9th, and at the end of two miles re-gained our hunters, who were halting on the borders of a lake amidst a clump of stunted willows. This lake stretched to the westward as far as we could see, and its waters were discharged by a rapid stream one hundred and fifty yards wide. Being entirely ignorant where we might be led by pursuing the course of the lake, and dreading the idea of going a mile unnecessarily out of the way, we deter-mined on crossing the river if possible; and the canoe was gummed for the purpose, the willows furnishing us with fire. But we had to await the return of Junius before we could make the traverse. In the mean time we gathered a little tripe de roche, and breakfasted upon it and a few partridges that were killed in the morning. St. Germain and Adam were sent upon some recent tracks of deer. Junius arrived in the afternoon, and informed us that he had seen a large herd of musk-oxen on the banks of Cracroft's River, and had wounded one of them, but it had escaped. He brought about four pounds of meat, the remains of a deer that had been devoured by the wolves. The poor fellow was much fatigued, having walked throughout the night, but as the weather was particularly favourable for our crossing the river, we could not allow him to rest. After he had taken some refreshment we proceeded to the river. The canoe being put into the water was found extremely ticklish, but it was managed with much dexterity by St. Germain, Adam, and Peltier, who ferried over one passenger at a time, causing him to lie flat in its bottom, by no means a pleasant position, owing to its leakiness, but there was no alternative. The transport of the whole party was effected by five o'clock, and we walked about two miles further, and encamped, having come five miles and three quarters on a south-west course. Two young alpine hares were shot by St. Germain, which, with the small piece of meat brought in by Junius, furnished the supper of the whole party. There was no tripe de roche here. The country had now become decidedly hilly, and was covered with snow. The lake preserved its western direction, as far as I could see from the summit of the highest mountain near the encampment. We subsequently learned from the Copper Indians, that the part at which we had crossed the river was the Congecatha-wha-chaga of Hearne, of which I had little idea at the time, not only from the difference of latitude, but also from its being so much farther east of the mouth of the Copper-Mine River, than his track is laid down. He only making one degree and three quarters difference of lon-gitude, and we, upwards of four. Had I been aware of the fact, several days harassing march, and a disastrous accident would have been prevented by keeping on the western side of the lake, instead of crossing the river. We were informed also, that this river is the Anatessy or River of Strangers, and is supposed to fall into Bathurst's Inlet; but although the Indians have visited its mouth, their description was not sufficient to identify it with any of the rivers whose mouths we had seen. It probably falls in that part of the coast which was hid from our view by Goulburn's or Elliot's Islands. </fo:block> <fo:block font-size="8pt" font-family="serif" font-style="italic" line-height="10pt" space-before.optimum="3pt" space-after.optimum="3pt" text-align="start">September 10. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> We had a cold north wind, and the atmosphere was foggy. The thermometer 18 deg at five A.M. In the course of our march this morning, we passed many small lakes; and the ground becoming higher and more hilly as we receded from the river, was covered to a much greater depth with snow. This ren-dered walking not only extremely laborious, but also hazardous in the highest degree;for the sides of the hills, as is usual throughout the barren grounds, abounding in accumulations of large angular stones, it often happened that the men fell into the interstices with their loads on their backs, being deceived by the smooth appearance of the drifted snow. If any one had broken a limb here, his fate would have been melancholy indeed; we could neither have remained with him, nor carried him on. We halted at ten to gather tripe de roche,but it was so frozen, that we were quite benumbed with cold before a sufficiency could be collected even for a scanty meal. On proceeding our men were somewhat cheered, by observing on the sandy summit of a hill, from whence the snow had been blown, the summer track of a man; and afterwards by seeing several deer tracks on the snow. About noon the weather cleared up a little, and, to our great joy, we saw a herd of musk-oxen grazing in a valley below us. The party instantly halted, and the best hunters were sent out; they approached the animals with the utmost caution, no less than two hours being consumed before they got within gun-shot. In the mean time we beheld their proceed-ings with extreme anxiety, and many secret prayers were, doubtless, offered up for their success. At length they opened their fire, and we had the satisfaction of seeing one of the largest cows fall; another was wounded, but escaped. This success infused spirit into our starving party. To skin and cut up the animal was the work of a few minutes. The contents of its stomach were devoured upon the spot, and the raw intestines, which were next attacked, were pronounced by the most delicate amongst us to be excellent. A few willows, whose tops were seen peeping through the snow in the bottom of the valley, were quickly grubbed, the tents pitched, and supper cooked, and devoured with avidity. This was the sixth day since we had had a good meal. The tripe de roche, even where we got enough, only serving to allay the pangs of hunger for a short time. After supper, two of the hunters went in pursuit of the herd, but could not get near them. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> We were detained all the next day by a strong southerly wind, and were much incommoded in the tents by the drift snow. The temperature was 20 deg. The average for the last ten days about 24.5 deg. We restricted ourselves to one meal to-day as we were at rest, and there was only meat remaining sufficient for the next day. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> The gale had not diminished on the 12th, and, as we were fearful of its continuance for some time, we determined on going forward; our only doubt regarded the preservation of the canoe, but the men promised to pay particular attention to it, and the most careful persons were appointed to take it in charge. The snow was two feet deep, and the ground much broken, which rendered the march extremely painful. The whole party complained more of faintness and weakness than they had ever done before; their strength seemed to have been impaired by the recent supply of animal food. In the afternoon the wind abated, and the snow ceased; cheered with the change we proceeded forward at a quicker pace, and encamped at six P. M., having come eleven miles. Our supper consumed the last of our meat. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> We set out on the 13th, in thick hazy weather, and, after an hour's march, had the extreme mortification to find ourselves on the borders of a large lake, which we subsequently learned from the Indians was named Contwoy-to, or Rum Lake; neither of its extre- mities could be seen, and as the portion which lay to the east seemed the widest, we coasted along to the westward portion in search of a crossing-place. This lake being bounded by steep and lofty hills, our march was very fatiguing. Those sides which were exposed to the sun, were free from snow, and we found upon them some excellent berries. We encamped at six P. M., having come only six miles and a half. Credit was then missing, and he did not return during the night. We supped off a single partridge and some tripe de roche;this unpalatable weed was now quite nau-seous to the whole party, and in several it produced bowel com-plaints. Mr. Hood was the greatest sufferer from this cause. This evening we were extremely distressed, at discovering that our im-provident companions,since we left Hood's River, had thrown away three of the fishing-nets, and burnt the floats ; they knew we had brought them to procure subsistence for the party, when the animals should fail, and we could scarcely believe the fact of their having wilfully deprived themselves of this resource, especially when we considered that most of them had passed the greater part of their servitude in situations where the nets alone had supplied them with food. Being thus deprived of our principal resource, that of fishing, and the men evidently getting weaker every day, it became necessary to lighten their burthens of every thing except ammunition, clothing, and the instruments that were required to find our way. I, therefore, issued directions to deposit at this encampment the dipping needle, azimuth compass, magnet, a large thermometer, and a few books we had carried, having torn out of these such parts as we should require to work the observations for latitude and longitude. I also promised,as an excitement to the efforts in hunting, my gun to St. Germain, and an ample compensation to Adam, or any of the other men who should kill any animals. Mr. Hood, on this occasion, lent his gun to Michel, the Iroquois, who was very eager in the chase, and often successful. </fo:block> <fo:block font-size="8pt" font-family="serif" font-style="italic" line-height="10pt" space-before.optimum="3pt" space-after.optimum="3pt" text-align="start">September 14. </fo:block> <!-- Normal text --> <fo:block font-size="11pt" font-family="serif" line-height="12pt" space-after.optimum="0pt" text-align="start"> This morning the officers being assembled round a small fire, Perrault presented each of us with a small piece of meat which he had saved from his allowance. It was received with great thankfulness, and such an act of self-denial and kindness, being totally unexpected in a Canadian voyager, filled our eyes with tears. In directing our course to a river issuing from the lake, we met Credit, who communicated the joyful intelligence of his having killed two deer in the morning. We instantly halted, and having shared the deer that was nearest to us, prepared breakfast. After which, the other deer was sent for, and we went down to the river, which was about three hundred yards wide, and flowed with great velocity through a broken rocky channel. Having searched for a part where the current was most smooth, the canoe was placed in the water at the head of a rapid, and St. Germain, Solomon Belanger, and I, embarked in order to cross. We went from the shore very well, but in mid-channel the canoe became difficult to manage under our burden as the breeze was fresh. The current drove us to the edge of the rapid, when Belanger unfortunately applied his paddle to avert the apparent danger of being forced down it, and lost his balance. The canoe was overset in con-sequence in the middle of the rapid. We fortunately kept hold of it, until we touched a rock where the water did not reach higher than our waists; here we kept our footing, notwithstanding the strength of the current, until the water was emptied out of the canoe. Belanger then held the canoe steady whilst St. Germain placed me in it, and afterwards embarked himself in a very dexterous manner. It was impossible, however, to embark Belanger, as the canoe would have been hurried down the rapid, the moment he should have raised his foot from the rock on which he stood. We were, therefore, compelled to leave him in his perilous situation. We had not gone twenty yards before the canoe, striking on a sunken rock, went down. The place being shallow, we were again enabled to empty it, and the third attempt brought us to the shore. In the mean time Belanger was suffering extremely, immersed to his middle in the centre of a rapid, the temperature of which was very little above the freezing point, and the upper part of his body covered with wet clothes, exposed in a temperature not much above zero, to a strong breeze. He called piteously for relief, and St. Germain on his return endeavoured to embark him, but in vain. The canoe was hurried down the rapid, and when he landed he was rendered by the cold incapable of further exertion, and Adam at-tempted to embark Belanger, but found it impossible. An attempt was next made to carry out to him a line, made of the slings of the men's loads. This also failed, the current acting so strongly upon it, as to prevent the canoe from steering, and it was finally broken and carried down the stream. At length, when Belanger's strength seemed almost exhausted, the canoe reached him with a small cord belonging to one of the nets, and he was dragged perfectly senseless through the rapid. By the direction of Dr. Richardson, he was instantly stripped, and being rolled up in blankets, two men un-dressed themselves and went to bed with him; but it was some hours before he recovered his warmth and sensations. As soon as Belanger was placed in his bed, the officers immediately sent over my blankets, and a person to make a fire. Augustus brought the canoe over, and in returning he was obliged to descend both the rapids, before he could get across the stream; which hazardous service he performed with the greatest coolness and judgment. It is impossible to describe my sensations as I witnessed the various unsuccessful attempts to relieve Belanger. The distance prevented my seeing distinctly what was going on, and I continued pacing up and down upon the rock on which I landed, regardless of the coldness of my drenched and stiffening garments. The canoe, in every attempt to reach him, was hurried down the rapid, and was lost to the view amongst the rocky islets, with a rapidity that seemed to threaten certain destruction; once, indeed, I fancied that I saw it overwhelmed in the waves. Such an event would have been fatal to the whole party. Separated as I was from my com-panions, without gun, ammunition, hatchet, or the means of making a fire, and in wet clothes, my doom would have been speedily sealed. My companions too, driven to the necessity of coasting the lake, must have sunk under the fatigue of rounding its innumerable arms and bays, which, as we have learned from the Indians, are very extensive. By the goodness of Providence, however, we were spared at that time, and some of us have been permitted to offer up our thanksgivings, in a civilized land, for the signal deliverances we then and afterwards experienced. </fo:block> </fo:flow> </fo:page-sequence> </fo:root>