2011-02-01 09:57:29 +08:00
#
2014-04-03 00:00:48 +08:00
# Copyright (C) 2011 - 2014 Instructure, Inc.
2011-02-01 09:57:29 +08:00
#
# 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/>.
#
2012-07-07 03:45:00 +08:00
# @API Files
# An API for managing files and folders
# See the File Upload Documentation for details on the file upload workflow.
#
2014-01-29 03:40:20 +08:00
# @model File
2012-07-07 03:45:00 +08:00
# {
2014-01-29 03:40:20 +08:00
# "id": "File",
# "description": "",
# "properties": {
# "size": {
# "example": 4,
# "type": "integer"
# },
# "content-type": {
# "example": "text/plain",
# "type": "string"
# },
# "url": {
# "example": "http://www.example.com/files/569/download?download_frd=1&verifier=c6HdZmxOZa0Fiin2cbvZeI8I5ry7yqD7RChQzb6P",
# "type": "string"
# },
# "id": {
# "example": 569,
# "type": "integer"
# },
# "display_name": {
# "example": "file.txt",
# "type": "string"
# },
# "created_at": {
# "example": "2012-07-06T14:58:50Z",
# "type": "datetime"
# },
# "updated_at": {
# "example": "2012-07-06T14:58:50Z",
# "type": "datetime"
# },
# "unlock_at": {
# "type": "datetime"
# },
# "locked": {
# "example": false,
# "type": "boolean"
# },
# "hidden": {
# "example": false,
# "type": "boolean"
# },
# "lock_at": {
# "type": "datetime"
# },
# "locked_for_user": {
# "example": false,
# "type": "boolean"
# },
# "lock_info": {
# "$ref": "LockInfo"
# },
# "lock_explanation": {
# "example": "This assignment is locked until September 1 at 12:00am",
# "type": "string"
# },
# "hidden_for_user": {
# "example": false,
# "type": "boolean"
# },
# "thumbnail_url": {
# "type": "string"
2014-06-11 14:49:14 +08:00
# },
# "preview_url": {
# "type": "string",
# "description": "optional: url to the document preview (only included in submission endpoints)"
2014-01-29 03:40:20 +08:00
# }
# }
2012-07-07 03:45:00 +08:00
# }
2014-01-29 03:40:20 +08:00
#
2011-02-01 09:57:29 +08:00
class FilesController < ApplicationController
2012-03-31 05:20:30 +08:00
before_filter :require_user , :only = > :create_pending
2014-05-28 03:56:56 +08:00
before_filter :require_context , :except = > [ :full_index , :assessment_question_show , :image_thumbnail , :show_thumbnail , :preflight , :create_pending , :s3_success , :show , :api_create , :api_create_success , :api_show , :api_index , :destroy , :api_update , :api_file_status , :public_url ]
2011-05-10 05:20:58 +08:00
before_filter :check_file_access_flags , :only = > [ :show_relative , :show ]
2011-03-08 05:02:56 +08:00
prepend_around_filter :load_pseudonym_from_policy , :only = > :create
2012-03-21 06:08:20 +08:00
skip_before_filter :verify_authenticity_token , :only = > :api_create
2014-04-03 00:00:48 +08:00
before_filter :verify_api_id , only : [ :api_show , :api_create_success , :api_file_status , :api_update , :destroy ]
2012-03-21 06:08:20 +08:00
include Api :: V1 :: Attachment
2012-03-28 06:53:55 +08:00
include Api :: V1 :: Avatar
2014-06-05 06:08:56 +08:00
include AttachmentHelper
2011-02-01 09:57:29 +08:00
before_filter { | c | c . active_tab = " files " }
2014-04-03 00:00:48 +08:00
def verify_api_id
raise ActiveRecord :: RecordNotFound unless params [ :id ] =~ Api :: ID_REGEX
end
2011-02-01 09:57:29 +08:00
def quota
get_quota
2014-02-20 04:07:15 +08:00
if authorized_action ( @context . attachments . scoped . new , @current_user , :create )
2011-02-01 09:57:29 +08:00
h = ActionView :: Base . new
h . extend ActionView :: Helpers :: NumberHelper
result = {
:quota = > h . number_to_human_size ( @quota ) ,
:quota_used = > h . number_to_human_size ( @quota_used ) ,
:quota_full = > ( @quota_used > = @quota )
}
2013-08-23 06:22:14 +08:00
render :json = > result
2011-02-01 09:57:29 +08:00
end
end
2012-12-05 06:22:59 +08:00
2014-03-07 07:54:34 +08:00
# @API Get quota information
# Returns the total and used storage quota for the course, group, or user.
#
# @example_request
#
# curl 'https://<canvas>/api/v1/courses/1/files/quota' \
# -H 'Authorization: Bearer <token>'
#
# @example_response
#
# { "quota": 524288000, "quota_used": 402653184 }
#
def api_quota
if authorized_action ( @context . attachments . build , @current_user , :create )
get_quota
render json : { quota : @quota , quota_used : @quota_used }
end
end
2011-02-01 09:57:29 +08:00
def check_file_access_flags
2011-05-14 23:04:35 +08:00
if params [ :user_id ] && params [ :ts ] && params [ :sf_verifier ]
2014-03-11 23:20:35 +08:00
user = api_find ( User , params [ :user_id ] ) if params [ :user_id ] . present?
2011-05-14 23:04:35 +08:00
if user && user . valid_access_verifier? ( params [ :ts ] , params [ :sf_verifier ] )
2014-05-03 00:35:29 +08:00
# attachment.rb checks for this session attribute when determining
# permissions, but it should be ignored by the rest of the models'
2011-02-01 09:57:29 +08:00
# permission checks
session [ 'file_access_user_id' ] = user . id
session [ 'file_access_expiration' ] = 1 . hour . from_now . to_i
2014-07-23 23:45:21 +08:00
session [ :permissions_key ] = CanvasUUID . generate
2011-02-01 09:57:29 +08:00
end
end
# These sessions won't get deleted when the user logs out since this
# is on a separate domain, so we've added our own (stricter) timeout.
if session && session [ 'file_access_user_id' ] && session [ 'file_access_expiration' ] . to_i > Time . now . to_i
session [ 'file_access_expiration' ] = 1 . hour . from_now . to_i
2014-07-23 23:45:21 +08:00
session [ :permissions_key ] = CanvasUUID . generate
2011-02-01 09:57:29 +08:00
end
true
end
protected :check_file_access_flags
2012-12-05 06:22:59 +08:00
2011-02-01 09:57:29 +08:00
def index
2014-03-21 04:17:43 +08:00
return ember_app if ( @context . feature_enabled? ( :better_file_browsing ) )
2011-03-02 03:17:42 +08:00
if request . format == :json
2013-01-16 05:59:18 +08:00
if authorized_action ( @context . attachments . build , @current_user , :read )
2011-03-02 03:17:42 +08:00
@current_folder = Folder . find_folder ( @context , params [ :folder_id ] )
if ! @current_folder || authorized_action ( @current_folder , @current_user , :read )
2011-02-01 09:57:29 +08:00
if params [ :folder_id ]
2011-03-02 03:17:42 +08:00
if @context . grants_right? ( @current_user , session , :manage_files )
2012-07-07 03:45:00 +08:00
@current_attachments = @current_folder . active_file_attachments . by_position_then_display_name
2011-03-02 03:17:42 +08:00
else
2012-07-07 03:45:00 +08:00
@current_attachments = @current_folder . visible_file_attachments . by_position_then_display_name
2011-03-02 03:17:42 +08:00
end
2013-03-08 07:49:24 +08:00
@current_attachments = @current_attachments . includes ( :thumbnail , :media_object )
2013-08-23 06:22:14 +08:00
render :json = > @current_attachments . map { | a | a . as_json ( methods : [ :readable_size , :currently_locked , :thumbnail_url ] , permissions : { user : @current_user , session : session } ) }
2011-02-01 09:57:29 +08:00
else
2013-08-31 00:54:59 +08:00
file_structure = {
:contexts = > [ @context . as_json ( permissions : { user : @current_user } ) ] ,
:collaborations = > [ ] ,
:folders = > @context . active_folders_with_sub_folders .
order ( " COALESCE(parent_folder_id, 0), COALESCE(position, 0), COALESCE(name, ''), created_at " ) . map { | f |
f . as_json ( permissions : { user : @current_user } , methods : [ :mime_class , :currently_locked ] )
} ,
:folders_with_subcontent = > [ ] ,
:files = > [ ]
}
if @current_user
file_structure [ :collaborations ] = @current_user . collaborations . for_context ( @context ) . active .
includes ( :user , :users ) . order ( " created_at DESC " ) . map { | c |
c . as_json ( permissions : { user : @current_user } , methods : [ :collaborator_ids ] )
}
end
render :json = > file_structure
2011-03-02 03:17:42 +08:00
end
2011-02-01 09:57:29 +08:00
end
end
2011-03-02 03:17:42 +08:00
else
full_index
2011-02-01 09:57:29 +08:00
end
end
2011-07-12 02:25:54 +08:00
2012-07-07 03:45:00 +08:00
# @API List files
2013-07-30 03:57:27 +08:00
# Returns the paginated list of files for the folder or course.
2012-07-07 03:45:00 +08:00
#
2014-08-06 23:29:54 +08:00
# @argument content_types[] [String]
2013-08-14 23:06:27 +08:00
# Filter results by content-type. You can specify type/subtype pairs (e.g.,
# 'image/jpeg'), or simply types (e.g., 'image', which will match
# 'image/gif', 'image/jpeg', etc.).
#
2014-08-06 23:29:54 +08:00
# @argument search_term [String]
2013-08-14 23:06:27 +08:00
# The partial name of the files to match and return.
2013-06-27 04:57:14 +08:00
#
2014-08-06 23:29:54 +08:00
# @argument include[] ["user"]
2014-04-19 08:02:54 +08:00
# Array of additional information to include.
#
# "user":: the user who uploaded the file or last edited its content
#
2014-08-06 23:29:54 +08:00
# @argument sort [String, "name"|"size"|"created_at"|"updated_at"|"content_type"|"user"]
2014-04-19 02:59:18 +08:00
# Sort results by this field. Defaults to 'name'. Note that `sort=user` implies `include[]=user`.
#
2014-08-06 23:29:54 +08:00
# @argument order [String, "asc"|"desc"]
2014-04-19 02:59:18 +08:00
# The sorting order. Defaults to 'asc'.
#
2012-07-07 03:45:00 +08:00
# @example_request
#
2013-06-27 04:57:14 +08:00
# curl 'https://<canvas>/api/v1/folders/<folder_id>/files?content_types[]=image&content_types[]=text/plain \
2012-07-07 03:45:00 +08:00
# -H 'Authorization: Bearer <token>'
#
# @returns [File]
def api_index
2013-07-30 03:57:27 +08:00
get_context
if @context
folder = Folder . root_folders ( @context ) . first
raise ActiveRecord :: RecordNotFound unless folder
context_index = true
else
2014-04-03 00:00:48 +08:00
verify_api_id
2013-07-30 03:57:27 +08:00
folder = Folder . find ( params [ :id ] )
end
2012-07-07 03:45:00 +08:00
if authorized_action ( folder , @current_user , :read_contents )
2013-07-30 03:57:27 +08:00
@context = folder . context unless context_index
2013-01-03 06:48:52 +08:00
can_manage_files = @context . grants_right? ( @current_user , session , :manage_files )
2014-04-19 02:59:18 +08:00
params [ :sort ] || = params [ :sort_by ] # :sort_by was undocumented; :sort is more consistent with other APIs such as wikis
2014-04-19 08:02:54 +08:00
params [ :include ] = Array ( params [ :include ] )
2014-04-19 02:59:18 +08:00
params [ :include ] << 'user' if params [ :sort ] == 'user'
2013-01-03 06:48:52 +08:00
2013-07-30 03:57:27 +08:00
if context_index
if can_manage_files
scope = @context . attachments . not_deleted
else
scope = @context . attachments . visible . not_hidden . not_locked . where (
:folder_id = > @context . active_folders . not_hidden . not_locked )
end
2012-07-07 03:45:00 +08:00
else
2013-07-30 03:57:27 +08:00
if can_manage_files
scope = folder . active_file_attachments
else
scope = folder . visible_file_attachments . not_hidden . not_locked
end
2012-07-07 03:45:00 +08:00
end
2014-04-19 02:59:18 +08:00
scope = scope . includes ( :user ) if params [ :include ] . include? 'user' && params [ :sort ] != 'user'
2013-07-30 03:57:27 +08:00
scope = Attachment . search_by_attribute ( scope , :display_name , params [ :search_term ] )
2014-04-19 02:59:18 +08:00
order_clause = case params [ :sort ]
when 'position' # undocumented; kept for compatibility
" attachments.position, #{ Attachment . display_name_order_by_clause ( 'attachments' ) } "
when 'size'
" attachments.size "
when 'created_at'
" attachments.created_at "
when 'updated_at'
" attachments.updated_at "
when 'content_type'
" attachments.content_type "
when 'user'
scope = scope . joins ( " LEFT OUTER JOIN users ON attachments.user_id=users.id " )
" users.sortable_name IS NULL, #{ User . sortable_name_order_by_clause ( 'users' ) } "
else
Attachment . display_name_order_by_clause ( 'attachments' )
2012-07-07 03:45:00 +08:00
end
2014-04-19 02:59:18 +08:00
order_clause += ' DESC' if params [ :order ] == 'desc'
scope = scope . order ( order_clause )
2013-06-27 04:57:14 +08:00
if params [ :content_types ] . present?
scope = scope . by_content_types ( Array ( params [ :content_types ] ) )
end
2013-07-30 03:57:27 +08:00
url = context_index ? context_files_url : api_v1_list_files_url ( folder )
@files = Api . paginate ( scope , self , url )
2014-04-19 08:02:54 +08:00
render :json = > attachments_json ( @files , @current_user , { } , :can_manage_files = > can_manage_files , :include = > params [ :include ] )
2012-07-07 03:45:00 +08:00
end
end
2011-07-12 02:25:54 +08:00
def images
2014-02-20 04:07:15 +08:00
if authorized_action ( @context . attachments . scoped . new , @current_user , :read )
2013-01-03 06:48:52 +08:00
if Folder . root_folders ( @context ) . first . grants_right? ( @current_user , session , :read_contents )
if @context . grants_right? ( @current_user , session , :manage_files )
@images = @context . active_images . paginate :page = > params [ :page ]
else
@images = @context . active_images . not_hidden . not_locked . where ( :folder_id = > @context . active_folders . not_hidden . not_locked ) . paginate :page = > params [ :page ]
end
else
@images = [ ] . paginate
end
2011-07-12 02:25:54 +08:00
headers [ 'X-Total-Pages' ] = @images . total_pages . to_s
render :partial = > " shared/wiki_image " , :collection = > @images
2011-02-01 09:57:29 +08:00
end
end
2014-03-21 04:17:43 +08:00
def ember_app
raise ActiveRecord :: RecordNotFound unless tab_enabled? ( @context . class :: TAB_FILES ) && @context . feature_enabled? ( :better_file_browsing )
clear_crumbs
2014-06-12 08:57:49 +08:00
@padless = true
@body_classes << 'full-width'
js_bundle :react_files
2014-03-21 04:17:43 +08:00
jammit_css :ember_files
render :text = > " " . html_safe , :layout = > true
end
2011-02-01 09:57:29 +08:00
def full_index
get_context
get_quota
2011-08-05 02:09:00 +08:00
add_crumb ( t ( '#crumbs.files' , " Files " ) , named_context_url ( @context , :context_files_url ) )
2011-02-01 09:57:29 +08:00
@contexts = [ @context ]
if ! @context . is_a? ( User ) || ( @context == @current_user && params [ :show_all_contexts ] )
2014-04-14 13:44:18 +08:00
get_all_pertinent_contexts ( include_groups : true )
2011-02-01 09:57:29 +08:00
end
@too_many_contexts = @contexts . length > 15
@contexts = @contexts [ 0 , 15 ]
2013-01-16 05:59:18 +08:00
if @contexts . length < = 1 && ! authorized_action ( @context . attachments . build , @current_user , :read )
2011-02-01 09:57:29 +08:00
return
end
2013-01-16 05:59:18 +08:00
2011-02-01 09:57:29 +08:00
return unless tab_enabled? ( @context . class :: TAB_FILES )
2011-03-02 03:17:42 +08:00
log_asset_access ( " files: #{ @context . asset_string } " , " files " , 'other' ) if @context
2011-02-01 09:57:29 +08:00
respond_to do | format |
2011-03-02 03:17:42 +08:00
if @contexts . empty?
2011-02-01 09:57:29 +08:00
format . html { redirect_to ! @context || @context == @current_user ? dashboard_url : named_context_url ( @context , :context_url ) }
else
2014-01-23 12:50:34 +08:00
js_env ( :contexts = >
@contexts . to_json ( :permissions = >
{ :user = > @current_user ,
:policies = >
[ :manage_files ,
:update ,
:manage_grades ,
:read_roster ]
} ,
:methods = > :asset_string ,
:include_root = > false ) )
2011-02-01 09:57:29 +08:00
format . html { render :action = > 'full_index' }
end
2013-08-23 06:22:14 +08:00
format . json { render :json = > @file_structures }
2011-02-01 09:57:29 +08:00
end
end
2012-12-05 06:22:59 +08:00
2011-02-01 09:57:29 +08:00
def text_show
@attachment = @context . attachments . find ( params [ :file_id ] )
if authorized_action ( @attachment , @current_user , :read )
2014-05-03 00:35:29 +08:00
if @attachment . grants_right? ( @current_user , :download )
2011-02-01 09:57:29 +08:00
@headers = false
render
else
show
end
end
end
2012-12-05 06:22:59 +08:00
2011-02-01 09:57:29 +08:00
def assessment_question_show
@context = AssessmentQuestion . find ( params [ :assessment_question_id ] )
@attachment = @context . attachments . find ( params [ :id ] )
@skip_crumb = true
if @attachment . deleted?
2011-06-16 00:41:22 +08:00
flash [ :notice ] = t 'notices.deleted' , " The file %{display_name} has been deleted " , :display_name = > @attachment . display_name
2014-01-09 03:20:52 +08:00
return redirect_to dashboard_url
2011-02-01 09:57:29 +08:00
end
show
end
2012-12-05 06:22:59 +08:00
2014-05-28 03:56:56 +08:00
# @API Get quota information
# Determine the URL that should be used for inline preview of the file.
#
# @example_request
#
# curl 'https://<canvas>/api/v1/files/1/public_url' \
# -H 'Authorization: Bearer <token>'
#
# @example_response
#
# { "public_url": "https://example-bucket.s3.amazonaws.com/example-namespace/attachments/1/example-filename?AWSAccessKeyId=example-key&Expires=1400000000&Signature=example-signature" }
#
2011-03-09 03:12:38 +08:00
def public_url
2014-02-11 09:36:37 +08:00
@attachment = Attachment . find ( params [ :id ] )
# if the attachment is part of a submisison, its 'context' will be the student that submmited the assignment. so if @current_user is a
# teacher authorized_action(@attachment, @current_user, :download) will be false, we need to actually check if they have perms to see the
# submission.
@submission = Submission . find ( params [ :submission_id ] ) if params [ :submission_id ]
# verify that the requested attachment belongs to the submission
return render_unauthorized_action if @submission && ! @submission . attachments . where ( :id = > params [ :id ] ) . any?
if @submission ? authorized_action ( @submission , @current_user , :read ) : authorized_action ( @attachment , @current_user , :download )
render :json = > { :public_url = > @attachment . authenticated_s3_url ( :secure = > request . ssl? ) }
2011-03-09 03:12:38 +08:00
end
end
2012-07-07 03:45:00 +08:00
# @API Get file
# Returns the standard attachment json object
#
2014-08-06 23:29:54 +08:00
# @argument include[] ["user"]
2014-04-19 08:02:54 +08:00
# Array of additional information to include.
#
# "user":: the user who uploaded the file or last edited its content
2012-07-07 03:45:00 +08:00
#
# @example_request
#
2014-04-03 00:00:48 +08:00
# curl 'https://<canvas>/api/v1/files/<file_id>' \
2012-07-07 03:45:00 +08:00
# -H 'Authorization: Bearer <token>'
#
# @returns File
def api_show
@attachment = Attachment . find ( params [ :id ] )
raise ActiveRecord :: RecordNotFound if @attachment . deleted?
2014-04-19 08:02:54 +08:00
params [ :include ] = Array ( params [ :include ] )
2012-07-07 03:45:00 +08:00
if authorized_action ( @attachment , @current_user , :read )
2014-04-19 08:02:54 +08:00
render :json = > attachment_json ( @attachment , @current_user , { } , { include : params [ :include ] } )
2012-07-07 03:45:00 +08:00
end
end
2012-12-05 06:22:59 +08:00
2011-02-01 09:57:29 +08:00
def show
2011-08-11 04:41:55 +08:00
original_params = params . dup
2011-02-01 09:57:29 +08:00
params [ :id ] || = params [ :file_id ]
2011-05-10 05:20:58 +08:00
get_context
2011-10-21 06:17:33 +08:00
# note that the /files/XXX URL implicitly uses the current user as the
# context, even though it doesn't search for the file using
# @current_user.attachments.find , since it might not actually be a user
# attachment.
# this implicit context magic happens in ApplicationController#get_context
2011-05-10 05:20:58 +08:00
if @context && ! @context . is_a? ( User )
@attachment = @context . attachments . find ( params [ :id ] )
else
@attachment = Attachment . find ( params [ :id ] )
2011-10-21 06:17:33 +08:00
@skip_crumb = true unless @context
2011-05-10 05:20:58 +08:00
end
2011-02-01 09:57:29 +08:00
params [ :download ] || = params [ :preview ]
2011-06-16 00:41:22 +08:00
add_crumb ( t ( '#crumbs.files' , " Files " ) , named_context_url ( @context , :context_files_url ) ) unless @skip_crumb
2011-02-01 09:57:29 +08:00
if @attachment . deleted?
2013-11-28 03:15:19 +08:00
return render_unauthorized_action unless @attachment . user_id == @current_user . id
2011-06-16 00:41:22 +08:00
flash [ :notice ] = t 'notices.deleted' , " The file %{display_name} has been deleted " , :display_name = > @attachment . display_name
2011-02-01 09:57:29 +08:00
if params [ :preview ] && @attachment . mime_class == 'image'
redirect_to '/images/blank.png'
elsif request . format == :json
2013-08-23 06:22:14 +08:00
render :json = > { :deleted = > true }
2011-02-01 09:57:29 +08:00
else
redirect_to named_context_url ( @context , :context_files_url )
end
return
end
2013-07-17 01:49:32 +08:00
if ( params [ :download ] && params [ :verifier ] && params [ :verifier ] == @attachment . uuid ) ||
@attachment . attachment_associations . where ( :context_type = > 'Submission' ) . any? { | aa | aa . context . grants_right? ( @current_user , session , :read ) } ||
authorized_action ( @attachment , @current_user , :read )
2011-02-01 09:57:29 +08:00
if params [ :download ]
if ( params [ :verifier ] && params [ :verifier ] == @attachment . uuid ) || ( @attachment . grants_right? ( @current_user , session , :download ) )
disable_page_views if params [ :preview ]
begin
2011-03-15 03:51:44 +08:00
send_attachment ( @attachment )
2011-02-01 09:57:29 +08:00
rescue = > e
2011-03-15 03:51:44 +08:00
@headers = false if params [ :ts ] && params [ :verifier ]
2011-06-16 00:41:22 +08:00
@not_found_message = t 'errors.not_found' , " It looks like something went wrong when this file was uploaded, and we can't find the actual file. You may want to notify the owner of the file and have them re-upload it. "
2011-02-01 09:57:29 +08:00
logger . error " Error downloading a file: #{ e } - #{ e . backtrace } "
render :template = > 'shared/errors/404_message' , :status = > :bad_request
end
return
elsif authorized_action ( @attachment , @current_user , :read )
2011-03-15 03:51:44 +08:00
render_attachment ( @attachment )
2011-02-01 09:57:29 +08:00
end
2011-03-15 03:51:44 +08:00
# This action is a callback used in our system to help record when
# a user views an inline preview of a file instead of downloading
# it, since this should also count as an access.
2011-02-01 09:57:29 +08:00
elsif params [ :inline ]
2011-03-15 03:51:44 +08:00
@attachment . context_module_action ( @current_user , :read ) if @current_user
2011-02-01 09:57:29 +08:00
log_asset_access ( @attachment , 'files' , 'files' )
2013-05-22 03:02:44 +08:00
@attachment . record_inline_view
2013-08-23 06:22:14 +08:00
render :json = > { :ok = > true }
2011-02-01 09:57:29 +08:00
else
2011-03-15 03:51:44 +08:00
render_attachment ( @attachment )
end
end
end
2012-12-05 06:22:59 +08:00
2011-03-15 03:51:44 +08:00
def render_attachment ( attachment )
respond_to do | format |
if params [ :preview ] && attachment . mime_class == 'image'
format . html { redirect_to '/images/lock.png' }
else
if @files_domain
@headers = false
@show_left_side = false
2011-02-01 09:57:29 +08:00
end
2011-03-15 03:51:44 +08:00
format . html { render :action = > 'show' }
end
if request . format == :json
2011-03-09 03:12:38 +08:00
options = { :permissions = > { :user = > @current_user } }
2014-07-12 08:14:09 +08:00
can_download = attachment . grants_right? ( @current_user , session , :download )
if can_download
2011-03-09 03:12:38 +08:00
# Right now we assume if they ask for json data on the attachment
2014-07-19 07:51:01 +08:00
# then that means they have viewed or are about to view the file in
# some form.
2014-05-10 18:21:52 +08:00
if @current_user &&
2014-07-19 07:51:01 +08:00
( attachment . canvadocable? ||
( service_enabled? ( :google_docs_previews ) && attachment . authenticated_s3_url ) )
2013-05-22 03:02:44 +08:00
attachment . context_module_action ( @current_user , :read )
request scribd re-render when no preview available
test plan:
1. work in an account with scribd enabled
and google doc previews disabled
(in console, add "-google_docs_previews" to the
Accounts's allowed_services)
2. upload a scribd-previewable file type in the files tab
(it needs to be a new file that is not already in
the canvas instance)
3. make sure it previews properly (it may take a minute
after uploading for the scribd rendering to complete)
4. delete the file
5. undelete the file (/courses/X/undelete)
6. reload the files tab, and click on the filename on the
left side.
- on the right, you should see a message like
"the preview for this document is unavailable;
please try again later".
7. reload the files tab again, then click on the filename.
- if the scribd rendering has completed, you should see
a scribd preview. if you don't, wait a few minutes and
reload the files page again, to give scribd some time.
8. test other areas documents can be previewed. (it's
permissible to simply delete the scribd_doc in the console
via attachment.delete_scribd_doc, then try to preview,
see that a "try again later" message appears, and the
scribd preview appears in later page views.)
a. documents embedded in rich text via the wiki sidebar
b. student submissions, as viewed by the student
("submission details" button, "preview" icon)
c. student submissions, as viewed by the teacher
in SpeedGrader(TM)
fixes CNVS-7019
Change-Id: I37be820a776637252e14b6a28a1be389b718ff9f
Reviewed-on: https://gerrit.instructure.com/22438
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Bracken Mosbacker <bracken@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Matt Goodwin <mattg@instructure.com>
2013-07-17 06:02:44 +08:00
attachment . record_inline_view
2013-05-22 03:02:44 +08:00
end
request scribd re-render when no preview available
test plan:
1. work in an account with scribd enabled
and google doc previews disabled
(in console, add "-google_docs_previews" to the
Accounts's allowed_services)
2. upload a scribd-previewable file type in the files tab
(it needs to be a new file that is not already in
the canvas instance)
3. make sure it previews properly (it may take a minute
after uploading for the scribd rendering to complete)
4. delete the file
5. undelete the file (/courses/X/undelete)
6. reload the files tab, and click on the filename on the
left side.
- on the right, you should see a message like
"the preview for this document is unavailable;
please try again later".
7. reload the files tab again, then click on the filename.
- if the scribd rendering has completed, you should see
a scribd preview. if you don't, wait a few minutes and
reload the files page again, to give scribd some time.
8. test other areas documents can be previewed. (it's
permissible to simply delete the scribd_doc in the console
via attachment.delete_scribd_doc, then try to preview,
see that a "try again later" message appears, and the
scribd preview appears in later page views.)
a. documents embedded in rich text via the wiki sidebar
b. student submissions, as viewed by the student
("submission details" button, "preview" icon)
c. student submissions, as viewed by the teacher
in SpeedGrader(TM)
fixes CNVS-7019
Change-Id: I37be820a776637252e14b6a28a1be389b718ff9f
Reviewed-on: https://gerrit.instructure.com/22438
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Bracken Mosbacker <bracken@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Matt Goodwin <mattg@instructure.com>
2013-07-17 06:02:44 +08:00
options [ :methods ] = [ ]
options [ :methods ] << :authenticated_s3_url if service_enabled? ( :google_docs_previews ) && attachment . authenticated_s3_url
log_asset_access ( attachment , " files " , " files " )
2011-03-09 03:12:38 +08:00
end
2011-02-01 09:57:29 +08:00
end
2014-05-10 18:21:52 +08:00
format . json {
render :json = > attachment . as_json ( options ) . tap { | json |
2014-07-12 08:14:09 +08:00
if can_download
json [ 'attachment' ] . merge! doc_preview_json ( attachment , @current_user )
end
2014-05-10 18:21:52 +08:00
}
}
2011-02-01 09:57:29 +08:00
end
end
2011-03-15 03:51:44 +08:00
protected :render_attachment
2011-02-01 09:57:29 +08:00
def show_relative
path = params [ :file_path ]
2013-06-18 06:08:36 +08:00
file_id = params [ :file_id ]
file_id = nil unless file_id . to_s =~ Api :: ID_REGEX
2011-02-01 09:57:29 +08:00
#if the relative path matches the given file id use that file
2014-08-21 06:35:07 +08:00
if file_id && @attachment = @context . attachments . where ( id : file_id ) . first
2013-06-18 06:08:36 +08:00
unless @attachment . matches_full_display_path? ( path ) || @attachment . matches_full_path? ( path )
2011-02-01 09:57:29 +08:00
@attachment = nil
end
end
2011-08-11 04:41:55 +08:00
@attachment || = Folder . find_attachment_in_context_with_path ( @context , path )
2011-02-01 09:57:29 +08:00
2011-05-20 23:38:33 +08:00
raise ActiveRecord :: RecordNotFound if ! @attachment
params [ :id ] = @attachment . id
2011-02-01 09:57:29 +08:00
params [ :download ] = '1'
show
end
2012-12-05 06:22:59 +08:00
2011-02-01 09:57:29 +08:00
# checks if for the current root account there's a 'files' domain
# defined and tried to use that. This way any files that we stream through
2014-05-03 00:35:29 +08:00
# a canvas URL are at least on a separate subdomain and the javascript
2011-02-01 09:57:29 +08:00
# won't be able to access or update data with AJAX requests.
def safer_domain_available?
2012-04-24 05:38:31 +08:00
if ! @files_domain && request . host_with_port != HostUrl . file_host ( @domain_root_account , request . host_with_port )
2012-11-09 04:10:01 +08:00
@safer_domain_host = HostUrl . file_host_with_shard ( @domain_root_account , request . host_with_port )
2011-02-01 09:57:29 +08:00
end
! ! @safer_domain_host
end
protected :safer_domain_available?
2012-12-05 06:22:59 +08:00
2011-02-01 09:57:29 +08:00
def attachment_content
@attachment = @context . attachments . active . find ( params [ :file_id ] )
if authorized_action ( @attachment , @current_user , :update )
# The files page lets you edit text content inline by firing off a json
# request to get the data.
2011-04-29 01:16:26 +08:00
# Protect ourselves against reading huge files into memory -- if the
# attachment is too big, don't return it.
2013-10-05 04:02:49 +08:00
if @attachment . size > Setting . get ( 'attachment_json_response_max_size' , 1 . megabyte . to_s ) . to_i
2013-08-23 06:22:14 +08:00
render :json = > { :error = > t ( 'errors.too_large' , " The file is too large to edit " ) }
2011-04-29 01:16:26 +08:00
return
2011-02-01 09:57:29 +08:00
end
2011-04-29 01:16:26 +08:00
stream = @attachment . open
2014-01-09 03:33:14 +08:00
json = { :body = > stream . read . force_encoding ( Encoding :: ASCII_8BIT ) }
render json : json
2014-04-03 00:00:48 +08:00
end
2011-02-01 09:57:29 +08:00
end
2012-12-05 06:22:59 +08:00
2011-03-15 03:51:44 +08:00
def send_attachment ( attachment )
2011-12-14 06:34:46 +08:00
# check for download_frd param and, if it's present, force the user to download the
# file and don't display it inline. we use download_frd instead of looking to the
# download param because the download param is used all over the place to mean stuff
# other than actually download the file. Long term we probably ought to audit the files
# controller, make download mean download, and remove download_frd.
if params [ :inline ] && ! params [ :download_frd ] && attachment . content_type && ( attachment . content_type . match ( / \ Atext / ) || attachment . mime_class == 'text' || attachment . mime_class == 'html' || attachment . mime_class == 'code' || attachment . mime_class == 'image' )
2011-03-15 03:51:44 +08:00
send_stored_file ( attachment )
2011-12-14 06:34:46 +08:00
elsif attachment . inline_content? && ! params [ :download_frd ] && ! @context . is_a? ( AssessmentQuestion )
2011-02-01 09:57:29 +08:00
if params [ :file_path ] || ! params [ :wrap ]
2011-03-15 03:51:44 +08:00
send_stored_file ( attachment )
2011-02-01 09:57:29 +08:00
else
2014-05-03 00:35:29 +08:00
# If the file is inlineable then redirect to the 'show' action
2011-02-01 09:57:29 +08:00
# so we can wrap it in all the Canvas header/footer stuff
redirect_to ( named_context_url ( @context , :context_file_url , attachment . id ) )
end
else
2011-03-15 03:51:44 +08:00
send_stored_file ( attachment , false , true )
end
end
protected :send_attachment
2012-12-05 06:22:59 +08:00
2011-03-15 03:51:44 +08:00
def send_stored_file ( attachment , inline = true , redirect_to_s3 = false )
2012-06-26 05:07:28 +08:00
user = @current_user
2014-03-11 23:20:35 +08:00
user || = api_find ( User , params [ :user_id ] ) if params [ :user_id ] . present?
2012-06-26 05:07:28 +08:00
attachment . context_module_action ( user , :read ) if user && ! params [ :preview ]
2011-03-15 03:51:44 +08:00
log_asset_access ( @attachment , " files " , " files " ) unless params [ :preview ]
2012-12-19 04:17:41 +08:00
set_cache_header ( attachment )
2011-03-15 03:51:44 +08:00
if safer_domain_available?
2011-12-14 06:34:46 +08:00
redirect_to safe_domain_file_url ( attachment , @safer_domain_host , params [ :verifier ] , ! inline )
2011-03-15 03:51:44 +08:00
elsif Attachment . local_storage?
@headers = false if @files_domain
2011-10-25 01:20:42 +08:00
send_file ( attachment . full_filename , :type = > attachment . content_type_with_encoding , :disposition = > ( inline ? 'inline' : 'attachment' ) )
2011-03-15 03:51:44 +08:00
elsif redirect_to_s3
2012-01-23 15:09:38 +08:00
redirect_to ( inline ? attachment . cacheable_s3_inline_url : attachment . cacheable_s3_download_url )
2011-03-15 03:51:44 +08:00
else
2013-03-08 03:28:42 +08:00
send_file_headers! ( :length = > attachment . s3object . content_length , :filename = > attachment . filename , :disposition = > 'inline' , :type = > attachment . content_type_with_encoding )
2014-03-20 06:33:30 +08:00
render :status = > 200 , :text = > attachment . s3object . read
2011-02-01 09:57:29 +08:00
end
end
2011-03-15 03:51:44 +08:00
protected :send_stored_file
2012-12-05 06:22:59 +08:00
2012-12-19 04:17:41 +08:00
def set_cache_header ( attachment )
unless attachment . content_type . match ( / \ Atext / ) || attachment . extension == '.html' || attachment . extension == '.htm'
cancel_cache_buster
#set cache to expoire in 1 day, max-age take seconds, and Expires takes a date
response . headers [ " Cache-Control " ] = " private, max-age=86400 "
response . headers [ " Expires " ] = 1 . day . from_now . httpdate
end
end
2011-02-01 09:57:29 +08:00
# GET /files/new
def new
@attachment = @context . attachments . build
if authorized_action ( @attachment , @current_user , :create )
end
end
2012-12-05 06:22:59 +08:00
2011-08-16 06:16:36 +08:00
def preflight
@context = Context . find_by_asset_string ( params [ :context_code ] )
if authorized_action ( @context , @current_user , :manage_files )
@current_folder = Folder . find_folder ( @context , params [ :folder_id ] )
if @current_folder
params [ :filenames ] = [ ] if params [ :filenames ] . blank?
return render :json = > {
:duplicates = > @current_folder . active_file_attachments . map ( & :display_name ) & params [ :filenames ]
}
end
end
end
2011-02-02 02:50:10 +08:00
def create_pending
@context = Context . find_by_asset_string ( params [ :attachment ] [ :context_code ] )
2011-06-29 03:06:25 +08:00
@asset = Context . find_asset_by_asset_string ( params [ :attachment ] [ :asset_string ] , @context ) if params [ :attachment ] [ :asset_string ]
2013-05-31 23:36:55 +08:00
@attachment = @context . attachments . build
2011-06-29 03:06:25 +08:00
@check_quota = true
2011-02-02 02:50:10 +08:00
permission_object = @attachment
permission = :create
2011-07-12 03:27:33 +08:00
intent = params [ :attachment ] [ :intent ]
2012-12-05 06:22:59 +08:00
2011-02-02 02:50:10 +08:00
# Using workflow_state we can keep track of the files that have been built
# but we don't know that there's an s3 component for yet (it's still being
# uploaded)
workflow_state = 'unattached'
# There are multiple reasons why we could be building a file. The default
# is to upload it to a context. In the other cases we need to check the
# permission related to the purpose to make sure the file isn't being
# uploaded just to disappear later
2011-07-12 03:27:33 +08:00
if @asset . is_a? ( Assignment ) && intent == 'comment'
2011-06-29 03:06:25 +08:00
permission_object = @asset
2011-02-02 02:50:10 +08:00
permission = :attach_submission_comment_files
2011-06-29 03:06:25 +08:00
@context = @asset
@check_quota = false
2011-07-12 03:27:33 +08:00
elsif @asset . is_a? ( Assignment ) && intent == 'submit'
permission_object = @asset
permission = ( @asset . submission_types || " " ) . match ( / online_upload / ) ? :submit : :nothing
2012-06-27 00:00:08 +08:00
@group = @asset . group_category . group_for ( @current_user ) if @asset . has_group_category?
@context = @group || @current_user
2011-06-29 03:06:25 +08:00
@check_quota = false
2011-07-12 03:27:33 +08:00
elsif @context && intent == 'attach_discussion_file'
2014-02-20 04:07:15 +08:00
permission_object = @context . discussion_topics . scoped . new
2011-02-02 02:50:10 +08:00
permission = :attach
2011-07-12 03:27:33 +08:00
elsif @context && intent == 'message'
2011-02-02 02:50:10 +08:00
permission_object = @context
permission = :send_messages
2011-06-29 03:06:25 +08:00
@check_quota = false
2011-07-12 03:27:33 +08:00
elsif @context && intent && intent != 'upload'
2011-02-02 02:50:10 +08:00
# In other cases (like unzipping a file, extracting a QTI, etc.
# we don't actually want the uploaded file to show up in the context's
# file listings. If you set its workflow_state to unattached_temporary
# then it will never be activated.
workflow_state = 'unattached_temporary'
2011-06-29 03:06:25 +08:00
@check_quota = false
2011-02-02 02:50:10 +08:00
end
2012-12-05 06:22:59 +08:00
2011-06-29 03:06:25 +08:00
@attachment . context = @context
2013-10-26 06:20:57 +08:00
@attachment . user = @current_user
2011-02-02 02:50:10 +08:00
if authorized_action ( permission_object , @current_user , permission )
2011-06-29 03:06:25 +08:00
if @context . respond_to? ( :is_a_context? ) && @check_quota
2011-02-02 02:50:10 +08:00
get_quota
return if quota_exceeded ( named_context_url ( @context , :context_files_url ) )
end
@attachment . filename = params [ :attachment ] [ :filename ]
@attachment . file_state = 'deleted'
@attachment . workflow_state = workflow_state
if @context . respond_to? ( :folders )
2011-04-27 11:55:24 +08:00
if params [ :attachment ] [ :folder_id ] . present?
2014-08-21 06:35:07 +08:00
@folder = @context . folders . active . where ( id : params [ :attachment ] [ :folder_id ] ) . first
2011-04-27 11:55:24 +08:00
end
2011-02-02 02:50:10 +08:00
@folder || = Folder . unfiled_folder ( @context )
@attachment . folder_id = @folder . id
end
@attachment . content_type = Attachment . mimetype ( @attachment . filename )
@attachment . save!
2011-05-20 04:14:01 +08:00
res = @attachment . ajax_upload_params ( @current_pseudonym ,
2011-08-16 06:16:36 +08:00
named_context_url ( @context , :context_files_url , :format = > :text , :duplicate_handling = > params [ :attachment ] [ :duplicate_handling ] ) ,
s3_success_url ( @attachment . id , :uuid = > @attachment . uuid , :duplicate_handling = > params [ :attachment ] [ :duplicate_handling ] ) ,
2011-05-20 04:14:01 +08:00
:no_redirect = > params [ :no_redirect ] ,
:upload_params = > {
'attachment[folder_id]' = > params [ :attachment ] [ :folder_id ] || '' ,
2011-08-06 02:57:48 +08:00
'attachment[unattached_attachment_id]' = > @attachment . id ,
'check_quota_after' = > @check_quota ? '1' : '0'
2011-05-20 04:14:01 +08:00
} ,
2014-05-16 05:49:34 +08:00
:default_content_type = > params [ :default_content_type ] ,
2011-05-20 04:14:01 +08:00
:ssl = > request . ssl? )
2013-08-23 06:22:14 +08:00
render :json = > res
2011-02-02 02:50:10 +08:00
end
end
2012-12-05 06:22:59 +08:00
2011-02-02 02:50:10 +08:00
def s3_success
2011-04-27 11:55:24 +08:00
if params [ :id ] . present?
2014-04-03 00:00:48 +08:00
verify_api_id
2014-08-21 06:35:07 +08:00
@attachment = Attachment . where ( id : params [ :id ] , workflow_state : 'unattached' , uuid : params [ :uuid ] ) . first
2011-04-27 11:55:24 +08:00
end
2013-03-08 03:28:42 +08:00
details = @attachment . s3object . head rescue nil
2011-02-02 02:50:10 +08:00
if @attachment && details
2011-08-16 06:16:36 +08:00
deleted_attachments = @attachment . handle_duplicates ( params [ :duplicate_handling ] )
2011-05-20 04:14:01 +08:00
@attachment . process_s3_details! ( details )
2012-09-14 03:30:29 +08:00
render_attachment_json ( @attachment , deleted_attachments )
2011-02-02 02:50:10 +08:00
else
2012-12-08 02:02:57 +08:00
render :json = > { :errors = > [ { :attribute = > 'attachment' , :message = > 'upload failed' } ] }
2011-02-02 02:50:10 +08:00
end
end
2011-02-01 09:57:29 +08:00
2012-03-21 06:08:20 +08:00
# for local file uploads
def api_create
@policy , @attachment = Attachment . decode_policy ( params [ :Policy ] , params [ :Signature ] )
if ! @policy
return render ( :nothing = > true , :status = > :bad_request )
end
@context = @attachment . context
@attachment . workflow_state = nil
2013-06-13 07:26:49 +08:00
@attachment . uploaded_data = params [ :file ] || params [ :attachment ] && params [ :attachment ] [ :uploaded_data ]
2012-03-21 06:08:20 +08:00
if @attachment . save
# for consistency with the s3 upload client flow, we redirect to the success url here to finish up
2012-07-08 06:01:34 +08:00
redirect_to api_v1_files_create_success_url ( @attachment , :uuid = > @attachment . uuid , :on_duplicate = > params [ :on_duplicate ] , :quota_exemption = > params [ :quota_exemption ] )
2012-03-21 06:08:20 +08:00
else
render ( :nothing = > true , :status = > :bad_request )
end
end
def api_create_success
2014-08-21 06:35:07 +08:00
@attachment = Attachment . where ( id : params [ :id ] , uuid : params [ :uuid ] ) . first
2012-03-21 06:08:20 +08:00
return render ( :nothing = > true , :status = > :bad_request ) unless @attachment . try ( :file_state ) == 'deleted'
2013-03-29 03:02:06 +08:00
duplicate_handling = check_duplicate_handling_option ( request . params )
2012-03-27 04:07:54 +08:00
return unless duplicate_handling
2012-07-08 06:01:34 +08:00
return unless check_quota_after_attachment ( request )
2012-03-21 06:08:20 +08:00
if Attachment . s3_storage?
return render ( :nothing = > true , :status = > :bad_request ) unless @attachment . state == :unattached
2013-03-08 03:28:42 +08:00
details = @attachment . s3object . head
2012-03-21 06:08:20 +08:00
@attachment . process_s3_details! ( details )
else
@attachment . file_state = 'available'
@attachment . save!
end
2012-03-27 04:07:54 +08:00
@attachment . handle_duplicates ( duplicate_handling )
2013-03-29 03:02:06 +08:00
if @attachment . context . respond_to? ( :file_upload_success_callback )
@attachment . context . file_upload_success_callback ( @attachment )
end
2013-03-22 00:10:33 +08:00
2013-05-07 03:16:06 +08:00
json = attachment_json ( @attachment , @current_user )
# render as_text for IE, otherwise it'll prompt
# to download the JSON response
render :json = > json , :as_text = > in_app?
2012-03-21 06:08:20 +08:00
end
2012-08-14 05:13:20 +08:00
def api_file_status
2014-08-21 06:35:07 +08:00
@attachment = Attachment . where ( id : params [ :id ] , uuid : params [ :uuid ] ) . first!
2012-08-14 05:13:20 +08:00
if @attachment . file_state == 'available'
render :json = > { :upload_status = > 'ready' , :attachment = > attachment_json ( @attachment , @current_user ) }
elsif @attachment . file_state == 'deleted'
render :json = > { :upload_status = > 'pending' }
else
render :json = > { :upload_status = > 'errored' , :message = > @attachment . upload_error_message }
end
end
2012-12-05 06:22:59 +08:00
2011-02-01 09:57:29 +08:00
def create
2011-04-27 11:55:24 +08:00
if ( folder_id = params [ :attachment ] . delete ( :folder_id ) ) && folder_id . present?
2014-08-21 06:35:07 +08:00
@folder = @context . folders . active . where ( id : folder_id ) . first
2011-04-27 11:55:24 +08:00
end
2011-02-01 09:57:29 +08:00
@folder || = Folder . unfiled_folder ( @context )
params [ :attachment ] [ :uploaded_data ] || = params [ :attachment_uploaded_data ]
2014-05-03 00:35:29 +08:00
params [ :attachment ] [ :uploaded_data ] || = params [ :file ]
2011-02-01 09:57:29 +08:00
params [ :attachment ] [ :user ] = @current_user
params [ :attachment ] . delete :context_id
params [ :attachment ] . delete :context_type
2011-08-16 06:16:36 +08:00
duplicate_handling = params . delete :duplicate_handling
2011-04-27 11:55:24 +08:00
if ( unattached_attachment_id = params [ :attachment ] . delete ( :unattached_attachment_id ) ) && unattached_attachment_id . present?
2014-08-21 06:35:07 +08:00
@attachment = @context . attachments . where ( id : unattached_attachment_id , workflow_state : 'unattached' ) . first
2011-04-27 11:55:24 +08:00
end
2013-05-31 23:36:55 +08:00
@attachment || = @context . attachments . build
2011-02-01 09:57:29 +08:00
if authorized_action ( @attachment , @current_user , :create )
get_quota
2011-08-06 02:57:48 +08:00
return if ( params [ :check_quota_after ] . nil? || params [ :check_quota_after ] == '1' ) &&
quota_exceeded ( named_context_url ( @context , :context_files_url ) )
2011-02-01 09:57:29 +08:00
respond_to do | format |
2011-02-02 02:50:10 +08:00
@attachment . folder_id || = @folder . id
@attachment . workflow_state = nil
@attachment . file_state = 'available'
2011-02-01 09:57:29 +08:00
success = nil
if params [ :attachment ] && params [ :attachment ] [ :source_attachment_id ]
a = Attachment . find ( params [ :attachment ] . delete ( :source_attachment_id ) )
2014-08-21 06:35:07 +08:00
if a . root_attachment_id && att = @folder . attachments . where ( id : a . root_attachment_id ) . first
2011-02-01 09:57:29 +08:00
@attachment = att
success = true
elsif a . grants_right? ( @current_user , session , :download )
@attachment = a . clone_for ( @context , @attachment )
success = @attachment . save
end
end
2011-02-02 02:50:10 +08:00
if params [ :attachment ] [ :uploaded_data ]
success = @attachment . update_attributes ( params [ :attachment ] )
2013-11-09 07:54:05 +08:00
@attachment . errors . add ( :base , t ( 'errors.server_error' , " Upload failed, server error, please try again. " ) ) unless success
2011-02-02 02:50:10 +08:00
else
2013-11-09 07:54:05 +08:00
@attachment . errors . add ( :base , t ( 'errors.missing_field' , " Upload failed, expected form field missing " ) )
2011-02-02 02:50:10 +08:00
end
2011-08-16 06:16:36 +08:00
deleted_attachments = @attachment . handle_duplicates ( duplicate_handling )
2012-01-23 15:09:38 +08:00
unless @attachment . downloadable?
2011-02-01 09:57:29 +08:00
success = false
if ( params [ :attachment ] [ :uploaded_data ] . size == 0 rescue false )
2013-11-09 07:54:05 +08:00
@attachment . errors . add ( :base , t ( 'errors.empty_file' , " That file is empty. Please upload a different file. " ) )
2011-02-01 09:57:29 +08:00
else
2013-11-09 07:54:05 +08:00
@attachment . errors . add ( :base , t ( 'errors.upload_failed' , " Upload failed, please try again. " ) )
2011-02-01 09:57:29 +08:00
end
unless @attachment . new_record?
@attachment . destroy rescue @attachment . delete
end
end
if success
@attachment . move_to_bottom
format . html { return_to ( params [ :return_to ] , named_context_url ( @context , :context_files_url ) ) }
force rendering json as text in file-related actions
refs #6459
these actions were previously rendering json as text, before our recent
change to prefix the "while(1);" protection to json responses. in that
commit, they got changed to rendering the json as application/json,
which causes some browsers (IE, FF, maybe others) to try to download the
response. this adds an explicit as_text parameter to render to force
rendering as text, even if content-type isn't multipart, and adds
as_text to all the actions that used to render json as text.
testplan: in firefox, uploading to s3, try submitting homework and
uploading a content migration package (such as .imscc). the uploads
should succeed, and you shouldn't see any browser download prompt.
this fixes existing selenium specs for file_uploads_spec and others.
Change-Id: Iacf9093d233c8253a5dd5b30ecb9fcd860c5fcbf
Reviewed-on: https://gerrit.instructure.com/7190
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Brian Palmer <brianp@instructure.com>
2011-11-30 03:49:26 +08:00
format . json do
2012-09-14 03:30:29 +08:00
render_attachment_json ( @attachment , deleted_attachments , @folder )
force rendering json as text in file-related actions
refs #6459
these actions were previously rendering json as text, before our recent
change to prefix the "while(1);" protection to json responses. in that
commit, they got changed to rendering the json as application/json,
which causes some browsers (IE, FF, maybe others) to try to download the
response. this adds an explicit as_text parameter to render to force
rendering as text, even if content-type isn't multipart, and adds
as_text to all the actions that used to render json as text.
testplan: in firefox, uploading to s3, try submitting homework and
uploading a content migration package (such as .imscc). the uploads
should succeed, and you shouldn't see any browser download prompt.
this fixes existing selenium specs for file_uploads_spec and others.
Change-Id: Iacf9093d233c8253a5dd5b30ecb9fcd860c5fcbf
Reviewed-on: https://gerrit.instructure.com/7190
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Brian Palmer <brianp@instructure.com>
2011-11-30 03:49:26 +08:00
end
format . text do
2012-09-14 03:30:29 +08:00
render_attachment_json ( @attachment , deleted_attachments , @folder )
force rendering json as text in file-related actions
refs #6459
these actions were previously rendering json as text, before our recent
change to prefix the "while(1);" protection to json responses. in that
commit, they got changed to rendering the json as application/json,
which causes some browsers (IE, FF, maybe others) to try to download the
response. this adds an explicit as_text parameter to render to force
rendering as text, even if content-type isn't multipart, and adds
as_text to all the actions that used to render json as text.
testplan: in firefox, uploading to s3, try submitting homework and
uploading a content migration package (such as .imscc). the uploads
should succeed, and you shouldn't see any browser download prompt.
this fixes existing selenium specs for file_uploads_spec and others.
Change-Id: Iacf9093d233c8253a5dd5b30ecb9fcd860c5fcbf
Reviewed-on: https://gerrit.instructure.com/7190
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Brian Palmer <brianp@instructure.com>
2011-11-30 03:49:26 +08:00
end
2011-02-01 09:57:29 +08:00
else
format . html { render :action = > " new " }
2013-08-23 06:22:14 +08:00
format . json { render :json = > @attachment . errors }
format . text { render :json = > @attachment . errors }
2011-02-01 09:57:29 +08:00
end
end
end
end
def update
@attachment = @context . attachments . find ( params [ :id ] )
@folder = @context . folders . active . find ( params [ :attachment ] [ :folder_id ] ) rescue nil
@folder || = @attachment . folder
@folder || = Folder . unfiled_folder ( @context )
if authorized_action ( @attachment , @current_user , :update )
respond_to do | format |
just_hide = params [ :attachment ] [ :just_hide ]
hidden = params [ :attachment ] [ :hidden ]
2012-09-05 23:44:45 +08:00
params [ :attachment ] . delete_if { | k , v | ! [ :display_name , :locked , :lock_at , :unlock_at , :uploaded_data , :hidden ] . include? ( k . to_sym ) }
2011-02-01 09:57:29 +08:00
# Need to be careful on this one... we can't let students turn in a
# file and then edit it after the fact...
params [ :attachment ] . delete ( :uploaded_data ) if @context . is_a? ( User )
2014-04-18 05:00:41 +08:00
@attachment . user = @current_user if params [ :attachment ] [ :uploaded_data ] . present?
2011-02-01 09:57:29 +08:00
@attachment . attributes = params [ :attachment ]
if just_hide == '1'
@attachment . locked = false
@attachment . hidden = true
elsif hidden && ( hidden . empty? || hidden == " 0 " )
@attachment . hidden = false
end
@attachment . folder = @folder
@folder_id_changed = @attachment . folder_id_changed?
if @attachment . save
@attachment . move_to_bottom if @folder_id_changed
2011-06-16 00:41:22 +08:00
flash [ :notice ] = t 'notices.updated' , " File was successfully updated. "
2011-02-01 09:57:29 +08:00
format . html { redirect_to named_context_url ( @context , :context_files_url ) }
2013-08-23 06:22:14 +08:00
format . json { render :json = > @attachment . as_json ( :methods = > [ :readable_size , :mime_class , :currently_locked ] , :permissions = > { :user = > @current_user , :session = > session } ) , :status = > :ok }
2011-02-01 09:57:29 +08:00
else
format . html { render :action = > " edit " }
2013-08-23 06:22:14 +08:00
format . json { render :json = > @attachment . errors , :status = > :bad_request }
2011-02-01 09:57:29 +08:00
end
end
end
end
2012-07-07 03:45:00 +08:00
# @API Update file
# Update some settings on the specified file
#
2013-08-14 23:06:27 +08:00
# @argument name [String]
# The new display name of the file
#
# @argument parent_folder_id [String]
2014-05-03 00:35:29 +08:00
# The id of the folder to move this file into.
# The new folder must be in the same context as the original parent folder.
2012-07-07 03:45:00 +08:00
# If the file is in a context without folders this does not apply.
2013-08-14 23:06:27 +08:00
#
# @argument lock_at [DateTime]
# The datetime to lock the file at
#
# @argument unlock_at [DateTime]
# The datetime to unlock the file at
#
# @argument locked [Boolean]
# Flag the file as locked
#
# @argument hidden [Boolean]
# Flag the file as hidden
2012-07-07 03:45:00 +08:00
#
# @example_request
#
2014-05-03 00:35:29 +08:00
# curl -XPUT 'https://<canvas>/api/v1/files/<file_id>' \
# -F 'name=<new_name>' \
# -F 'locked=true' \
2012-07-07 03:45:00 +08:00
# -H 'Authorization: Bearer <token>'
#
# @returns File
def api_update
@attachment = Attachment . find ( params [ :id ] )
if authorized_action ( @attachment , @current_user , :update )
@context = @attachment . context
if @context && params [ :parent_folder_id ]
folder = @context . folders . active . find ( params [ :parent_folder_id ] )
if authorized_action ( folder , @current_user , :update )
@attachment . folder = folder
else
return
end
end
@attachment . attributes = process_attachment_params ( params )
if @attachment . save
2012-07-27 00:13:22 +08:00
render :json = > attachment_json ( @attachment , @current_user )
2012-07-07 03:45:00 +08:00
else
2013-08-23 06:22:14 +08:00
render :json = > @attachment . errors , :status = > :bad_request
2012-07-07 03:45:00 +08:00
end
end
end
2012-12-05 06:22:59 +08:00
2011-02-01 09:57:29 +08:00
def reorder
@folder = @context . folders . active . find ( params [ :folder_id ] )
if authorized_action ( @context , @current_user , :manage_files )
2012-07-07 03:45:00 +08:00
@folders = @folder . active_sub_folders . by_position
2011-02-01 09:57:29 +08:00
@folders . first && @folders . first . update_order ( ( params [ :folder_order ] || " " ) . split ( " , " ) )
2012-07-24 05:21:54 +08:00
@folder . file_attachments . by_position_then_display_name . first && @folder . file_attachments . first . update_order ( ( params [ :order ] || " " ) . split ( " , " ) )
2011-02-01 09:57:29 +08:00
@folder . reload
2013-08-23 06:22:14 +08:00
render :json = > @folder . subcontent . map { | f | f . as_json ( methods : :readable_size , permissions : { user : @current_user , session : session } ) }
2011-02-01 09:57:29 +08:00
end
end
2012-07-07 03:45:00 +08:00
# @API Delete file
# Remove the specified file
2014-04-03 00:00:48 +08:00
#
# curl -XDELETE 'https://<canvas>/api/v1/files/<file_id>' \
2012-07-07 03:45:00 +08:00
# -H 'Authorization: Bearer <token>'
2011-02-01 09:57:29 +08:00
def destroy
@attachment = Attachment . find ( params [ :id ] )
if authorized_action ( @attachment , @current_user , :delete )
@attachment . destroy
respond_to do | format |
2012-07-07 03:45:00 +08:00
format . html {
require_context
redirect_to named_context_url ( @context , :context_files_url )
}
if api_request?
2012-07-27 00:13:22 +08:00
format . json { render :json = > attachment_json ( @attachment , @current_user ) }
2012-07-07 03:45:00 +08:00
else
2013-08-23 06:22:14 +08:00
format . json { render :json = > @attachment }
2012-07-07 03:45:00 +08:00
end
2011-02-01 09:57:29 +08:00
end
end
end
2012-12-05 06:22:59 +08:00
2011-02-01 09:57:29 +08:00
def image_thumbnail
cancel_cache_buster
2012-05-17 06:06:29 +08:00
url = Rails . cache . fetch ( [ 'thumbnail_url' , params [ :uuid ] , params [ :size ] ] . cache_key , :expires_in = > 30 . minutes ) do
2014-08-21 06:35:07 +08:00
attachment = Attachment . active . where ( id : params [ :id ] , uuid : params [ :uuid ] ) . first if params [ :id ] . present?
2012-05-17 06:06:29 +08:00
thumb_opts = params . slice ( :size )
url = attachment . thumbnail_url ( thumb_opts ) rescue nil
2011-02-01 09:57:29 +08:00
url || = '/images/no_pic.gif'
url
end
redirect_to url
end
2011-05-05 22:30:05 +08:00
# when using local storage, the image_thumbnail action redirects here rather
# than to a s3 url
def show_thumbnail
if Attachment . local_storage?
2011-05-25 13:27:43 +08:00
cancel_cache_buster
2014-08-21 06:35:07 +08:00
thumbnail = Thumbnail . where ( id : params [ :id ] , uuid : params [ :uuid ] ) . first if params [ :id ] . present?
2011-05-05 22:30:05 +08:00
raise ActiveRecord :: RecordNotFound unless thumbnail
send_file thumbnail . full_filename , :content_type = > thumbnail . content_type
else
image_thumbnail
end
end
2012-09-14 03:30:29 +08:00
private
2012-12-05 06:22:59 +08:00
2012-09-14 03:30:29 +08:00
def render_attachment_json ( attachment , deleted_attachments , folder = attachment . folder )
2013-08-23 06:22:14 +08:00
json = {
:attachment = > attachment . as_json (
allow : :uuid ,
2014-07-19 07:51:01 +08:00
methods : [ :uuid , :readable_size , :mime_class , :currently_locked , :thumbnail_url ] ,
2013-08-23 06:22:14 +08:00
permissions : { user : @current_user , session : session } ,
include_root : false
) ,
:deleted_attachment_ids = > deleted_attachments . map ( & :id )
}
2012-09-14 03:30:29 +08:00
if folder . name == 'profile pictures'
json [ :avatar ] = avatar_json ( @current_user , attachment , { :type = > 'attachment' } )
end
2013-08-23 06:22:14 +08:00
render :json = > json , :as_text = > true
2012-09-14 03:30:29 +08:00
end
2012-12-05 06:22:59 +08:00
2011-02-01 09:57:29 +08:00
end