684 lines
27 KiB
Ruby
684 lines
27 KiB
Ruby
#
|
|
# Copyright (C) 2011 - present Instructure, Inc.
|
|
#
|
|
# This file is part of Canvas.
|
|
#
|
|
# Canvas is free software: you can redistribute it and/or modify it under
|
|
# the terms of the GNU Affero General Public License as published by the Free
|
|
# Software Foundation, version 3 of the License.
|
|
#
|
|
# Canvas 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 Affero General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License along
|
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
# @API Files
|
|
# @subtopic Folders
|
|
#
|
|
# @model Folder
|
|
# {
|
|
# "id": "Folder",
|
|
# "description": "",
|
|
# "properties": {
|
|
# "context_type": {
|
|
# "example": "Course",
|
|
# "type": "string"
|
|
# },
|
|
# "context_id": {
|
|
# "example": 1401,
|
|
# "type": "integer"
|
|
# },
|
|
# "files_count": {
|
|
# "example": 0,
|
|
# "type": "integer"
|
|
# },
|
|
# "position": {
|
|
# "example": 3,
|
|
# "type": "integer"
|
|
# },
|
|
# "updated_at": {
|
|
# "example": "2012-07-06T14:58:50Z",
|
|
# "type": "datetime"
|
|
# },
|
|
# "folders_url": {
|
|
# "example": "https://www.example.com/api/v1/folders/2937/folders",
|
|
# "type": "string"
|
|
# },
|
|
# "files_url": {
|
|
# "example": "https://www.example.com/api/v1/folders/2937/files",
|
|
# "type": "string"
|
|
# },
|
|
# "full_name": {
|
|
# "example": "course files/11folder",
|
|
# "type": "string"
|
|
# },
|
|
# "lock_at": {
|
|
# "example": "2012-07-06T14:58:50Z",
|
|
# "type": "datetime"
|
|
# },
|
|
# "id": {
|
|
# "example": 2937,
|
|
# "type": "integer"
|
|
# },
|
|
# "folders_count": {
|
|
# "example": 0,
|
|
# "type": "integer"
|
|
# },
|
|
# "name": {
|
|
# "example": "11folder",
|
|
# "type": "string"
|
|
# },
|
|
# "parent_folder_id": {
|
|
# "example": 2934,
|
|
# "type": "integer"
|
|
# },
|
|
# "created_at": {
|
|
# "example": "2012-07-06T14:58:50Z",
|
|
# "type": "datetime"
|
|
# },
|
|
# "unlock_at": {
|
|
# "type": "datetime"
|
|
# },
|
|
# "hidden": {
|
|
# "example": false,
|
|
# "type": "boolean"
|
|
# },
|
|
# "hidden_for_user": {
|
|
# "example": false,
|
|
# "type": "boolean"
|
|
# },
|
|
# "locked": {
|
|
# "example": true,
|
|
# "type": "boolean"
|
|
# },
|
|
# "locked_for_user": {
|
|
# "example": false,
|
|
# "type": "boolean"
|
|
# },
|
|
# "for_submissions": {
|
|
# "example": false,
|
|
# "type": "boolean",
|
|
# "description": "If true, indicates this is a read-only folder containing files submitted to assignments"
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
class FoldersController < ApplicationController
|
|
include Api::V1::Folders
|
|
include Api::V1::Attachment
|
|
include AttachmentHelper
|
|
|
|
before_action :require_context, :except => [:api_index, :show, :api_destroy, :update, :create, :create_file, :copy_folder, :copy_file]
|
|
|
|
def index
|
|
if authorized_action(@context, @current_user, :read)
|
|
render :json => Folder.root_folders(@context).map{ |f| f.as_json(permissions: {user: @current_user, session: session}) }
|
|
end
|
|
end
|
|
|
|
|
|
# @API List folders
|
|
# @subtopic Folders
|
|
# Returns the paginated list of folders in the folder.
|
|
#
|
|
# @example_request
|
|
#
|
|
# curl 'https://<canvas>/api/v1/folders/<folder_id>/folders' \
|
|
# -H 'Authorization: Bearer <token>'
|
|
#
|
|
# @returns [Folder]
|
|
def api_index
|
|
folder = Folder.find(params[:id])
|
|
if authorized_action(folder, @current_user, :read_contents)
|
|
can_view_hidden_files = can_view_hidden_files?(folder.context, @current_user, session)
|
|
opts = {:can_view_hidden_files => can_view_hidden_files, :context => folder.context}
|
|
if can_view_hidden_files && folder.context.is_a?(Course) &&
|
|
master_courses? && MasterCourses::ChildSubscription.is_child_course?(folder.context)
|
|
opts[:master_course_restricted_folder_ids] = MasterCourses::FolderLockingHelper.locked_folder_ids_for_course(folder.context)
|
|
end
|
|
|
|
scope = folder.active_sub_folders
|
|
unless can_view_hidden_files
|
|
scope = scope.not_hidden.not_locked
|
|
end
|
|
if params[:sort_by] == 'position'
|
|
scope = scope.by_position
|
|
else
|
|
scope = scope.by_name
|
|
end
|
|
@folders = Api.paginate(scope, self, api_v1_list_folders_url(folder))
|
|
render :json => folders_json(@folders, @current_user, session, opts)
|
|
end
|
|
end
|
|
|
|
# @API List all folders
|
|
# @subtopic Folders
|
|
# Returns the paginated list of all folders for the given context. This will
|
|
# be returned as a flat list containing all subfolders as well.
|
|
#
|
|
# @example_request
|
|
#
|
|
# curl 'https://<canvas>/api/v1/courses/<course_id>/folders' \
|
|
# -H 'Authorization: Bearer <token>'
|
|
#
|
|
# @returns [Folder]
|
|
def list_all_folders
|
|
if authorized_action(@context, @current_user, :read)
|
|
can_view_hidden_files = can_view_hidden_files?(@context, @current_user, session)
|
|
|
|
url = named_context_url(@context, :api_v1_context_folders_url, include_host: true)
|
|
|
|
scope = @context.active_folders
|
|
unless can_view_hidden_files
|
|
scope = scope.not_hidden.not_locked
|
|
end
|
|
if params[:sort_by] == 'position'
|
|
scope = scope.by_position
|
|
else
|
|
scope = scope.by_name
|
|
end
|
|
|
|
folders = Api.paginate(scope, self, url)
|
|
render json: folders_json(folders, @current_user, session, :can_view_hidden_files => can_view_hidden_files, :context => @context)
|
|
end
|
|
end
|
|
|
|
# @API Resolve path
|
|
# @subtopic Folders
|
|
# Given the full path to a folder, returns a list of all Folders in the path hierarchy,
|
|
# starting at the root folder, and ending at the requested folder. The given path is
|
|
# relative to the context's root folder and does not include the root folder's name
|
|
# (e.g., "course files"). If an empty path is given, the context's root folder alone
|
|
# is returned. Otherwise, if no folder exists with the given full path, a Not Found
|
|
# error is returned.
|
|
#
|
|
# @example_request
|
|
#
|
|
# curl 'https://<canvas>/api/v1/courses/<course_id>/folders/by_path/foo/bar/baz' \
|
|
# -H 'Authorization: Bearer <token>'
|
|
#
|
|
# @returns [Folder]
|
|
def resolve_path
|
|
if authorized_action(@context, @current_user, [:read, :manage_files])
|
|
can_view_hidden_files = can_view_hidden_files?(@context, @current_user, session)
|
|
folders = Folder.resolve_path(@context, params[:full_path], can_view_hidden_files)
|
|
raise ActiveRecord::RecordNotFound if folders.blank?
|
|
render json: folders_json(folders, @current_user, session, :can_view_hidden_files => can_view_hidden_files, :context => @context)
|
|
end
|
|
end
|
|
|
|
# @API Get folder
|
|
# @subtopic Folders
|
|
# Returns the details for a folder
|
|
#
|
|
# You can get the root folder from a context by using 'root' as the :id.
|
|
# For example, you could get the root folder for a course like:
|
|
#
|
|
# @example_request
|
|
# curl 'https://<canvas>/api/v1/courses/1337/folders/root' \
|
|
# -H 'Authorization: Bearer <token>'
|
|
#
|
|
# @example_request
|
|
# curl 'https://<canvas>/api/v1/folders/<folder_id>' \
|
|
# -H 'Authorization: Bearer <token>'
|
|
#
|
|
# @returns Folder
|
|
def show
|
|
if api_request?
|
|
if params[:id] == 'root'
|
|
require_context
|
|
@folder = Folder.root_folders(@context).first
|
|
else
|
|
get_context
|
|
if @context
|
|
@folder = @context.folders.active.find(params[:id])
|
|
else
|
|
@folder = Folder.find(params[:id])
|
|
end
|
|
end
|
|
else
|
|
require_context
|
|
@folder = @context.folders.find(params[:id])
|
|
end
|
|
raise ActiveRecord::RecordNotFound if @folder.deleted?
|
|
if authorized_action(@folder, @current_user, :read_contents)
|
|
if api_request?
|
|
render :json => folder_json(@folder, @current_user, session)
|
|
else
|
|
respond_to do |format|
|
|
format.html { redirect_to named_context_url(@context, :context_files_url, :folder_id => @folder.id) }
|
|
can_view_hidden_files = can_view_hidden_files?(@context, @current_user, session)
|
|
|
|
files = if can_view_hidden_files
|
|
@folder.active_file_attachments.by_position_then_display_name
|
|
else
|
|
@folder.visible_file_attachments.not_hidden.not_locked.by_position_then_display_name
|
|
end
|
|
files_options = {:permissions => {:user => @current_user}, :methods => [:currently_locked, :mime_class, :readable_size], :only => [:id, :comments, :content_type, :context_id, :context_type, :display_name, :folder_id, :position, :media_entry_id, :filename, :workflow_state]}
|
|
folders_options = {:permissions => {:user => @current_user}, :methods => [:currently_locked, :mime_class], :only => [:id, :context_id, :context_type, :lock_at, :last_lock_at, :last_unlock_at, :name, :parent_folder_id, :position, :unlock_at]}
|
|
sub_folders_scope = @folder.active_sub_folders
|
|
unless can_view_hidden_files
|
|
sub_folders_scope = sub_folders_scope.not_hidden.not_locked
|
|
end
|
|
res = {
|
|
:actual_folder => @folder.as_json(folders_options),
|
|
:sub_folders => sub_folders_scope.by_position.map { |f| f.as_json(folders_options) },
|
|
:files => files.map { |f|
|
|
f.as_json(files_options).tap { |json|
|
|
json['attachment'].merge! doc_preview_json(f, @current_user)
|
|
}
|
|
}
|
|
}
|
|
format.json { render :json => res }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def download
|
|
if authorized_action(@context, @current_user, :read)
|
|
@folder = @context.folders.find(params[:folder_id])
|
|
user_id = @current_user && @current_user.id
|
|
|
|
# Destroy any previous zip downloads that might exist for this folder,
|
|
# except the last one (cause we might be able to use it)
|
|
folder_filename = "#{t :folder_filename, "folder"}.zip"
|
|
|
|
@attachments = Attachment.where(context_id: @folder,
|
|
context_type: @folder.class.to_s,
|
|
display_name: folder_filename,
|
|
user_id: user_id,
|
|
workflow_state: ['to_be_zipped', 'zipping', 'zipped', 'unattached', 'errored']).
|
|
where("file_state<>'deleted'").
|
|
order(:created_at).to_a
|
|
@attachment = @attachments.pop
|
|
@attachments.each{|a| a.destroy_permanently! }
|
|
last_date = (@folder.active_file_attachments.map(&:updated_at) + @folder.active_sub_folders.by_position.map(&:updated_at)).compact.max
|
|
if @attachment && last_date && @attachment.created_at < last_date
|
|
@attachment.destroy_permanently!
|
|
@attachment = nil
|
|
end
|
|
|
|
if @attachment.nil?
|
|
@attachment = @folder.file_attachments.build(:display_name => folder_filename)
|
|
@attachment.user_id = user_id
|
|
@attachment.workflow_state = 'to_be_zipped'
|
|
@attachment.file_state = '0'
|
|
@attachment.context = @folder
|
|
@attachment.save!
|
|
ContentZipper.send_later_enqueue_args(:process_attachment, { :priority => Delayed::LOW_PRIORITY, :max_attempts => 1 }, @attachment, @current_user)
|
|
render :json => @attachment
|
|
else
|
|
respond_to do |format|
|
|
if @attachment.zipped?
|
|
if Attachment.s3_storage?
|
|
format.html { redirect_to @attachment.inline_url }
|
|
format.zip { redirect_to @attachment.inline_url }
|
|
else
|
|
cancel_cache_buster
|
|
format.html { send_file(@attachment.full_filename, :type => @attachment.content_type_with_encoding, :disposition => 'inline') }
|
|
format.zip { send_file(@attachment.full_filename, :type => @attachment.content_type_with_encoding, :disposition => 'inline') }
|
|
end
|
|
format.json { render :json => @attachment.as_json(:methods => :readable_size) }
|
|
else
|
|
flash[:notice] = t :file_zip_in_process, "File zipping still in process..."
|
|
format.html { redirect_to named_context_url(@context, :context_folder_url, @folder.id) }
|
|
format.zip { redirect_to named_context_url(@context, :context_folder_url, @folder.id) }
|
|
format.json { render :json => @attachment }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API Update folder
|
|
# @subtopic Folders
|
|
# Updates a folder
|
|
#
|
|
# @argument name [String]
|
|
# The new name of the folder
|
|
#
|
|
# @argument parent_folder_id [String]
|
|
# The id of the folder to move this folder into. The new folder must be in the same context as the original parent folder.
|
|
#
|
|
# @argument lock_at [DateTime]
|
|
# The datetime to lock the folder at
|
|
#
|
|
# @argument unlock_at [DateTime]
|
|
# The datetime to unlock the folder at
|
|
#
|
|
# @argument locked [Boolean]
|
|
# Flag the folder as locked
|
|
#
|
|
# @argument hidden [Boolean]
|
|
# Flag the folder as hidden
|
|
#
|
|
# @argument position [Integer]
|
|
# Set an explicit sort position for the folder
|
|
#
|
|
# @example_request
|
|
#
|
|
# curl -XPUT 'https://<canvas>/api/v1/folders/<folder_id>' \
|
|
# -F 'name=<new_name>' \
|
|
# -F 'locked=true' \
|
|
# -H 'Authorization: Bearer <token>'
|
|
#
|
|
# @returns Folder
|
|
def update
|
|
folder_params = process_folder_params(params, api_request?)
|
|
if api_request?
|
|
@folder = Folder.find(params[:id])
|
|
@context = @folder.context
|
|
else
|
|
require_context
|
|
@folder = @context.folders.find(params[:id])
|
|
end
|
|
if authorized_action(@folder, @current_user, :update)
|
|
respond_to do |format|
|
|
just_hide = folder_params.delete(:just_hide)
|
|
if just_hide == '1'
|
|
folder_params[:locked] = false
|
|
folder_params[:hidden] = true
|
|
end
|
|
if parent_folder_id = folder_params.delete(:parent_folder_id)
|
|
parent_folder = @context.folders.active.find(parent_folder_id)
|
|
return unless authorized_action(parent_folder, @current_user, :manage_contents)
|
|
folder_params[:parent_folder] = parent_folder
|
|
end
|
|
if @folder.update_attributes(folder_params)
|
|
if !@folder.parent_folder_id || !@context.folders.where(id: @folder).first
|
|
@folder.parent_folder = Folder.root_folders(@context).first
|
|
@folder.save
|
|
end
|
|
flash[:notice] = t :event_updated, 'Event was successfully updated.'
|
|
format.html { redirect_to named_context_url(@context, :context_files_url) }
|
|
if api_request?
|
|
format.json { render :json => folder_json(@folder, @current_user, session) }
|
|
else
|
|
format.json { render :json => @folder.as_json(:methods => [:currently_locked], :permissions => {:user => @current_user, :session => session}), :status => :ok }
|
|
end
|
|
else
|
|
format.html { render :edit }
|
|
format.json { render :json => @folder.errors, :status => :bad_request }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API Create folder
|
|
# @subtopic Folders
|
|
# Creates a folder in the specified context
|
|
#
|
|
# @argument name [Required, String]
|
|
# The name of the folder
|
|
#
|
|
# @argument parent_folder_id [String]
|
|
# The id of the folder to store the file in. If this and parent_folder_path are sent an error will be returned. If neither is given, a default folder will be used.
|
|
#
|
|
# @argument parent_folder_path [String]
|
|
# The path of the folder to store the new folder in. The path separator is the forward slash `/`, never a back slash. The parent folder will be created if it does not already exist. This parameter only applies to new folders in a context that has folders, such as a user, a course, or a group. If this and parent_folder_id are sent an error will be returned. If neither is given, a default folder will be used.
|
|
#
|
|
# @argument lock_at [DateTime]
|
|
# The datetime to lock the folder at
|
|
#
|
|
# @argument unlock_at [DateTime]
|
|
# The datetime to unlock the folder at
|
|
#
|
|
# @argument locked [Boolean]
|
|
# Flag the folder as locked
|
|
#
|
|
# @argument hidden [Boolean]
|
|
# Flag the folder as hidden
|
|
#
|
|
# @argument position [Integer]
|
|
# Set an explicit sort position for the folder
|
|
#
|
|
# @example_request
|
|
#
|
|
# curl 'https://<canvas>/api/v1/folders/<folder_id>/folders' \
|
|
# -F 'name=<new_name>' \
|
|
# -F 'locked=true' \
|
|
# -H 'Authorization: Bearer <token>'
|
|
#
|
|
#
|
|
# @example_request
|
|
#
|
|
# curl 'https://<canvas>/api/v1/courses/<course_id>/folders' \
|
|
# -F 'name=<new_name>' \
|
|
# -F 'locked=true' \
|
|
# -H 'Authorization: Bearer <token>'
|
|
#
|
|
# @returns Folder
|
|
def create
|
|
folder_params = process_folder_params(params, api_request?)
|
|
source_folder_id = folder_params.delete(:source_folder_id)
|
|
if folder_params[:folder_id]
|
|
parent_folder = Folder.find(folder_params[:folder_id])
|
|
@context = parent_folder.context
|
|
else
|
|
require_context
|
|
end
|
|
|
|
if (folder_params[:folder_id] && (folder_params[:parent_folder_path] || folder_params[:parent_folder_id])) ||
|
|
(folder_params[:parent_folder_path] && folder_params[:parent_folder_id])
|
|
render :json => {:message => t('only_one_folder', "Can't set folder path and folder id")}, :status => 400
|
|
return
|
|
elsif folder_params[:folder_id]
|
|
folder_params.delete(:folder_id)
|
|
elsif folder_params[:parent_folder_id]
|
|
parent_folder = @context.folders.find(folder_params.delete(:parent_folder_id))
|
|
elsif @context.respond_to?(:folders) && folder_params[:parent_folder_path].is_a?(String)
|
|
root = Folder.root_folders(@context).first
|
|
if authorized_action(root, @current_user, :create)
|
|
parent_folder = Folder.assert_path(folder_params.delete(:parent_folder_path), @context)
|
|
else
|
|
return
|
|
end
|
|
end
|
|
return if parent_folder && !authorized_action(parent_folder, @current_user, :manage_contents)
|
|
folder_params[:parent_folder] = parent_folder
|
|
|
|
@folder = @context.folders.build(folder_params)
|
|
if authorized_action(@folder, @current_user, :create)
|
|
if !@folder.parent_folder_id || !@context.folders.where(id: @folder.parent_folder_id).first
|
|
@folder.parent_folder_id = Folder.unfiled_folder(@context).id
|
|
end
|
|
if source_folder_id.present? && (source_folder = Folder.where(id: source_folder_id).first) && source_folder.grants_right?(@current_user, session, :read)
|
|
@folder = source_folder.clone_for(@context, @folder, {:everything => true})
|
|
end
|
|
respond_to do |format|
|
|
if @folder.save
|
|
flash[:notice] = t :folder_created, 'Folder was successfully created.'
|
|
format.html { redirect_to named_context_url(@context, :context_files_url) }
|
|
if api_request?
|
|
format.json { render :json => folder_json(@folder, @current_user, session) }
|
|
else
|
|
format.json { render :json => @folder.as_json(:permissions => {:user => @current_user, :session => session}) }
|
|
end
|
|
else
|
|
format.html { render :new }
|
|
format.json { render :json => @folder.errors, :status => :bad_request }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def process_folder_params(parameters, api_request)
|
|
folder_params = (api_request ? parameters : parameters[:folder]) || {}
|
|
folder_params.permit(:name, :parent_folder_id, :parent_folder_path, :folder_id,
|
|
:source_folder_id, :lock_at, :unlock_at, :locked,
|
|
:hidden, :context, :position, :just_hide)
|
|
end
|
|
private :process_folder_params
|
|
|
|
def destroy
|
|
@folder = Folder.find(params[:id])
|
|
if authorized_action(@folder, @current_user, :delete)
|
|
@folder.destroy
|
|
respond_to do |format|
|
|
format.html { redirect_to named_context_url(@context, :context_files_url) }# show.rhtml
|
|
format.json { render :json => @folder }
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API Delete folder
|
|
# @subtopic Folders
|
|
# Remove the specified folder. You can only delete empty folders unless you
|
|
# set the 'force' flag
|
|
#
|
|
# @argument force [Boolean]
|
|
# Set to 'true' to allow deleting a non-empty folder
|
|
#
|
|
# @example_request
|
|
#
|
|
# curl -XDELETE 'https://<canvas>/api/v1/folders/<folder_id>' \
|
|
# -H 'Authorization: Bearer <token>'
|
|
def api_destroy
|
|
@folder = Folder.find(params[:id])
|
|
if authorized_action(@folder, @current_user, :delete)
|
|
if @folder.root_folder?
|
|
render :json => {:message => t('no_deleting_root', "Can't delete the root folder")}, :status => 400
|
|
elsif @folder.context.is_a?(Course) && master_courses? &&
|
|
MasterCourses::ChildSubscription.is_child_course?(@folder.context) &&
|
|
MasterCourses::FolderLockingHelper.locked_folder_ids_for_course(@folder.context).include?(@folder.id)
|
|
render :json => {:message => "Can't delete folder containing files locked by Blueprint Course"}, :status => 400
|
|
elsif @folder.has_contents? && params[:force] != 'true'
|
|
render :json => {:message => t('no_deleting_folders_with_content', "Can't delete a folder with content")}, :status => 400
|
|
else
|
|
@context = @folder.context
|
|
@folder.destroy
|
|
render :json => folder_json(@folder, @current_user, session)
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API Upload a file
|
|
#
|
|
# Upload a file to a folder.
|
|
#
|
|
# This API endpoint is the first step in uploading a file.
|
|
# See the {file:file_uploads.html File Upload Documentation} for details on
|
|
# the file upload workflow.
|
|
#
|
|
# Only those with the "Manage Files" permission on a course or group can
|
|
# upload files to a folder in that course or group.
|
|
def create_file
|
|
@folder = Folder.find(params[:folder_id])
|
|
params[:parent_folder_id] = @folder.id
|
|
@context = @folder.context
|
|
@attachment = Attachment.new(:context => @context)
|
|
if authorized_action(@attachment, @current_user, :create)
|
|
api_attachment_preflight(@context, request, params: params, check_quota: true)
|
|
end
|
|
end
|
|
|
|
# @API Copy a file
|
|
#
|
|
# Copy a file from elsewhere in Canvas into a folder.
|
|
#
|
|
# Copying a file across contexts (between courses and users) is permitted,
|
|
# but the source and destination must belong to the same institution.
|
|
#
|
|
# @argument source_file_id [Required, String]
|
|
# The id of the source file
|
|
#
|
|
# @argument on_duplicate [Optional, String, "overwrite"|"rename"]
|
|
# What to do if a file with the same name already exists at the destination.
|
|
# If such a file exists and this parameter is not given, the call will fail.
|
|
#
|
|
# "overwrite":: Replace an existing file with the same name
|
|
# "rename":: Add a qualifier to make the new filename unique
|
|
#
|
|
# @example_request
|
|
#
|
|
# curl 'https://<canvas>/api/v1/folders/123/copy_file' \
|
|
# -H 'Authorization: Bearer <token>'
|
|
# -F 'source_file_id=456'
|
|
#
|
|
# @returns File
|
|
def copy_file
|
|
unless params[:source_file_id].present?
|
|
return render :json => {:message => "source_file_id must be provided"}, :status => :bad_request
|
|
end
|
|
@dest_folder = Folder.find(params[:dest_folder_id])
|
|
return unless authorized_action(@dest_folder, @current_user, :manage_contents)
|
|
@context = @dest_folder.context
|
|
@source_file = Attachment.find(params[:source_file_id])
|
|
unless @source_file.shard == @dest_folder.shard
|
|
return render :json => {:message => "cannot copy across institutions"}, :status => :bad_request
|
|
end
|
|
if authorized_action(@source_file, @current_user, :download)
|
|
@attachment = @context.attachments.build(folder: @dest_folder)
|
|
if authorized_action(@attachment, @current_user, :create)
|
|
on_duplicate, name = params[:on_duplicate].presence, params[:name].presence
|
|
duplicate_options = (on_duplicate == 'rename' && name) ? {name: name} : {}
|
|
return render :json => {:message => "on_duplicate must be 'overwrite' or 'rename'"}, :status => :bad_request if on_duplicate && %w(overwrite rename).exclude?(on_duplicate)
|
|
if on_duplicate.nil? && @dest_folder.active_file_attachments.where(display_name: @source_file.display_name).exists?
|
|
return render :json => {:message => "file already exists; set on_duplicate to 'rename' or 'overwrite'"}, :status => :conflict
|
|
end
|
|
@attachment = @source_file.clone_for(@context, @attachment, force_copy: true)
|
|
if @attachment.save
|
|
# default to rename on race condition (if a file happened to be created after the check above, and on_duplicate was not given)
|
|
@attachment.handle_duplicates(on_duplicate == 'overwrite' ? :overwrite : :rename, duplicate_options)
|
|
render :json => attachment_json(@attachment, @current_user, {}, { omit_verifier_in_app: true })
|
|
else
|
|
render :json => @attachment.errors
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API Copy a folder
|
|
#
|
|
# Copy a folder (and its contents) from elsewhere in Canvas into a folder.
|
|
#
|
|
# Copying a folder across contexts (between courses and users) is permitted,
|
|
# but the source and destination must belong to the same institution.
|
|
# If the source and destination folders are in the same context, the
|
|
# source folder may not contain the destination folder. A folder will be
|
|
# renamed at its destination if another folder with the same name already
|
|
# exists.
|
|
#
|
|
# @argument source_folder_id [Required, String]
|
|
# The id of the source folder
|
|
#
|
|
# @example_request
|
|
#
|
|
# curl 'https://<canvas>/api/v1/folders/123/copy_folder' \
|
|
# -H 'Authorization: Bearer <token>'
|
|
# -F 'source_file_id=789'
|
|
#
|
|
# @returns Folder
|
|
def copy_folder
|
|
unless params[:source_folder_id].present?
|
|
return render :json => {:message => "source_folder_id must be provided"}, :status => :bad_request
|
|
end
|
|
@dest_folder = Folder.find(params[:dest_folder_id])
|
|
return unless authorized_action(@dest_folder, @current_user, :manage_contents)
|
|
@context = @dest_folder.context
|
|
@source_folder = Folder.find(params[:source_folder_id])
|
|
unless @source_folder.shard == @dest_folder.shard
|
|
return render :json => {:message => "cannot copy across institutions"}, :status => :bad_request
|
|
end
|
|
if @source_folder.context == @context && (@dest_folder.full_name + '/').start_with?(@source_folder.full_name + '/')
|
|
return render :json => {:message => "source folder may not contain destination folder"}, :status => :bad_request
|
|
end
|
|
if authorized_action(@source_folder.context, @current_user, :manage_files)
|
|
@folder = @context.folders.build(parent_folder: @dest_folder)
|
|
if authorized_action(@folder, @current_user, :create)
|
|
@folder = @source_folder.clone_for(@context, @folder, everything: true, force_copy: true)
|
|
if @folder.save
|
|
render :json => folder_json(@folder, @current_user, session)
|
|
else
|
|
render :json => @folder.errors
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|