579 lines
21 KiB
Ruby
579 lines
21 KiB
Ruby
#
|
|
# Copyright (C) 2011 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 Pages
|
|
#
|
|
# Pages are rich content associated with Courses and Groups in Canvas.
|
|
# The Pages API allows you to create, retrieve, update, and delete pages.
|
|
#
|
|
# @object Page
|
|
# {
|
|
# // the unique locator for the page
|
|
# "url": "my-page-title",
|
|
#
|
|
# // the title of the page
|
|
# "title": "My Page Title",
|
|
#
|
|
# // the creation date for the page
|
|
# "created_at": "2012-08-06T16:46:33-06:00",
|
|
#
|
|
# // the date the page was last updated
|
|
# "updated_at": "2012-08-08T14:25:20-06:00",
|
|
#
|
|
# // whether this page is hidden from students
|
|
# // (note: students will never see this true; pages hidden from them will be omitted from results)
|
|
# "hide_from_students": false,
|
|
#
|
|
# // roles allowed to edit the page; comma-separated list comprising a combination of
|
|
# // 'teachers', 'students', and/or 'public'
|
|
# // if not supplied, course defaults are used
|
|
# "editing_roles": "teachers,students",
|
|
#
|
|
# // the User who last edited the page
|
|
# // (this may not be present if the page was imported from another system)
|
|
# "last_edited_by": {
|
|
# "id": 133,
|
|
# "display_name": "Rey del Pueblo",
|
|
# "avatar_image_url": "https://canvas.example.com/images/thumbnails/bm90aGluZyBoZXJl",
|
|
# "html_url": "https://canvas.example.com/courses/789/users/133"
|
|
# },
|
|
#
|
|
# // the page content, in HTML
|
|
# // (present when requesting a single page; omitted when listing pages)
|
|
# "body": "<p>Page Content</p>",
|
|
#
|
|
# // whether the page is published
|
|
# "published": true,
|
|
#
|
|
# // whether this page is the front page for the wiki
|
|
# "front_page": false,
|
|
#
|
|
# // Whether or not this is locked for the user.
|
|
# "locked_for_user": false,
|
|
#
|
|
# // (Optional) Information for the user about the lock. Present when locked_for_user is true.
|
|
# "lock_info": {
|
|
# // Asset string for the object causing the lock
|
|
# "asset_string": "wiki_page_1",
|
|
#
|
|
# // (Optional) Context module causing the lock.
|
|
# "context_module": {}
|
|
# },
|
|
#
|
|
# // (Optional) An explanation of why this is locked for the user. Present when locked_for_user is true.
|
|
# "lock_explanation": "This page is locked until September 1 at 12:00am"
|
|
# }
|
|
#
|
|
# @object PageRevision
|
|
# {
|
|
# // an identifier for this revision of the page
|
|
# "revision_id": 7,
|
|
#
|
|
# // the time when this revision was saved
|
|
# "updated_at": "2012-08-07T11:23:58-06:00",
|
|
#
|
|
# // the User who saved this revision, if applicable
|
|
# // (this may not be present if the page was imported from another system)
|
|
# "edited_by": {
|
|
# "id": 1123,
|
|
# "display_name": "Leonardo Fibonacci",
|
|
# "avatar_image_url": "https://canvas.example.com/images/thumbnails/bWVhbmluZ2xlc3M=",
|
|
# "html_url": "https://canvas.example.com/courses/789/users/1123"
|
|
# },
|
|
#
|
|
# // the following fields are not included in the index action
|
|
# // and may be omitted from the show action via summary=1
|
|
#
|
|
# // the historic url of the page
|
|
# "url": "old-page-title",
|
|
#
|
|
# // the historic page title
|
|
# "title": "Old Page Title",
|
|
#
|
|
# // the historic page contents
|
|
# "body": "<p>Old Page Content</p>"
|
|
# }
|
|
class WikiPagesApiController < ApplicationController
|
|
before_filter :require_context
|
|
before_filter :get_wiki_page, :except => [:create, :index]
|
|
before_filter :was_front_page, :except => [:index]
|
|
|
|
include Api::V1::WikiPage
|
|
|
|
# @API List pages
|
|
#
|
|
# List the wiki pages associated with a course or group
|
|
#
|
|
# @argument sort [Optional, String, "title"|"created_at"|"updated_at"]
|
|
# Sort results by this field.
|
|
#
|
|
# @argument order [Optional, String, "asc"|"desc"]
|
|
# The sorting order. Defaults to 'asc'.
|
|
#
|
|
# @argument search_term [Optional, String]
|
|
# The partial title of the pages to match and return.
|
|
#
|
|
# @example_request
|
|
# curl -H 'Authorization: Bearer <token>' \
|
|
# https://<canvas>/api/v1/courses/123/pages?sort=title&order=asc
|
|
#
|
|
# @returns [Page]
|
|
def index
|
|
if authorized_action(@context.wiki, @current_user, :read)
|
|
pages_route = polymorphic_url([:api_v1, @context, :wiki_pages])
|
|
# omit body from selection, since it's not included in index results
|
|
scope = @context.wiki.wiki_pages.select(WikiPage.column_names - ['body']).includes(:user)
|
|
scope = @context.grants_right?(@current_user, session, :view_unpublished_items) ? scope.not_deleted : scope.active
|
|
scope = scope.not_hidden unless @context.grants_right?(@current_user, session, :view_hidden_items)
|
|
|
|
scope = WikiPage.search_by_attribute(scope, :title, params[:search_term])
|
|
|
|
order_clause = case params[:sort]
|
|
when 'title'
|
|
WikiPage.title_order_by_clause
|
|
when 'created_at'
|
|
'wiki_pages.created_at'
|
|
when 'updated_at'
|
|
'wiki_pages.updated_at'
|
|
else
|
|
'wiki_pages.id'
|
|
end
|
|
order_clause += ' DESC' if params[:order] == 'desc'
|
|
scope = scope.order(order_clause)
|
|
|
|
wiki_pages = Api.paginate(scope, self, pages_route)
|
|
render :json => wiki_pages_json(wiki_pages, @current_user, session)
|
|
end
|
|
end
|
|
|
|
# @API Show page
|
|
#
|
|
# Retrieve the content of a wiki page
|
|
#
|
|
# @argument url [String]
|
|
# The unique identifier for a page.
|
|
#
|
|
# @example_request
|
|
# curl -H 'Authorization: Bearer <token>' \
|
|
# https://<canvas>/api/v1/courses/123/pages/my-page-url
|
|
#
|
|
# @example_request
|
|
# curl -H 'Authorization: Bearer <token>' \
|
|
# https://<canvas>/api/v1/groups/456/front_page
|
|
#
|
|
# @returns Page
|
|
def show
|
|
if authorized_action(@page, @current_user, :read)
|
|
@page.increment_view_count(@current_user, @context)
|
|
log_asset_access(@page, "wiki", @wiki)
|
|
render :json => wiki_page_json(@page, @current_user, session)
|
|
end
|
|
end
|
|
|
|
# @API Create page
|
|
#
|
|
# Create a new wiki page
|
|
#
|
|
# @argument wiki_page[title] [String]
|
|
# The title for the new page.
|
|
#
|
|
# @argument wiki_page[body] [String]
|
|
# The content for the new page.
|
|
#
|
|
# @argument wiki_page[hide_from_students] [Boolean]
|
|
# Whether the page should be hidden from students.
|
|
#
|
|
# *Note:* when draft state is enabled, attempts to set +hide_from_students+
|
|
# will be ignored and the value returned will always be the inverse of the
|
|
# +published+ value.
|
|
#
|
|
# @argument wiki_page[editing_roles] [Optional, String, "teachers"|"students"|"members"|"public"]
|
|
# Which user roles are allowed to edit this page. Any combination
|
|
# of these roles is allowed (separated by commas).
|
|
#
|
|
# "teachers":: Allows editing by teachers in the course.
|
|
# "students":: Allows editing by students in the course.
|
|
# "members":: For group wikis, allows editing by members of the group.
|
|
# "public":: Allows editing by any user.
|
|
#
|
|
# @argument wiki_page[notify_of_update] [Boolean]
|
|
# Whether participants should be notified when this page changes.
|
|
#
|
|
# @argument wiki_page[published] [Optional, Boolean]
|
|
# Whether the page is published (true) or draft state (false).
|
|
#
|
|
# *Note:* when draft state is disabled, attempts to set +published+
|
|
# will be ignored and the value returned will always be true.
|
|
#
|
|
# @argument wiki_page[front_page] [Optional, Boolean]
|
|
# Set an unhidden page as the front page (if true)
|
|
#
|
|
# @example_request
|
|
# curl -X POST -H 'Authorization: Bearer <token>' \
|
|
# https://<canvas>/api/v1/courses/123/pages?wiki_page[title]=New+page&wiki_page[body]=New+body+text
|
|
#
|
|
# @returns Page
|
|
def create
|
|
@page = @context.wiki.wiki_pages.build
|
|
@wiki = @context.wiki
|
|
if authorized_action(@page, @current_user, :create)
|
|
update_params = get_update_params(Set[:title, :body])
|
|
if !update_params.is_a?(Symbol) && @page.update_attributes(update_params) && process_front_page
|
|
log_asset_access(@page, "wiki", @wiki, 'participate')
|
|
render :json => wiki_page_json(@page, @current_user, session)
|
|
else
|
|
render :json => @page.errors.to_json, :status => update_params.is_a?(Symbol) ? update_params : :bad_request
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API Update page
|
|
#
|
|
# Update the title or contents of a wiki page
|
|
#
|
|
# @argument url [String]
|
|
# The unique identifier for a page.
|
|
#
|
|
# @argument wiki_page[title] [String]
|
|
# The title for the new page. NOTE: changing a page's title will change its
|
|
# url. The updated url will be returned in the result.
|
|
#
|
|
# @argument wiki_page[body] [String]
|
|
# The content for the new page.
|
|
#
|
|
# @argument wiki_page[hide_from_students] [Boolean]
|
|
# Whether the page should be hidden from students.
|
|
#
|
|
# *Note:* when draft state is enabled, attempts to set +hide_from_students+
|
|
# will be ignored and the value returned will always be the inverse of the
|
|
# +published+ value.
|
|
#
|
|
# @argument wiki_page[editing_roles] [Optional, String, "teachers"|"students"|"members"|"public"]
|
|
# Which user roles are allowed to edit this page. Any combination
|
|
# of these roles is allowed (separated by commas).
|
|
#
|
|
# "teachers":: Allows editing by teachers in the course.
|
|
# "students":: Allows editing by students in the course.
|
|
# "members":: For group wikis, allows editing by members of the group.
|
|
# "public":: Allows editing by any user.
|
|
#
|
|
# @argument wiki_page[notify_of_update] [Boolean]
|
|
# Whether participants should be notified when this page changes.
|
|
#
|
|
# @argument wiki_page[published] [Optional, Boolean]
|
|
# Whether the page is published (true) or draft state (false).
|
|
#
|
|
# *Note:* when draft state is disabled, attempts to set +published+
|
|
# will be ignored and the value returned will always be true.
|
|
#
|
|
# @argument wiki_page[front_page] [Optional, Boolean]
|
|
# Set an unhidden page as the front page (if true)
|
|
#
|
|
# @example_request
|
|
# curl -X PUT -H 'Authorization: Bearer <token>' \
|
|
# https://<canvas>/api/v1/courses/123/pages/the-page-url?wiki_page[body]=Updated+body+text
|
|
#
|
|
# @example_request
|
|
# curl -X PUT -H 'Authorization: Bearer <token>' \
|
|
# https://<canvas>/api/v1/courses/123/front_page?wiki_page[body]=Updated+body+text
|
|
#
|
|
# @returns Page
|
|
def update
|
|
if authorized_action(@page, @current_user, [:update, :update_content])
|
|
update_params = get_update_params
|
|
if !update_params.is_a?(Symbol) && @page.update_attributes(update_params) && process_front_page
|
|
log_asset_access(@page, "wiki", @wiki, 'participate')
|
|
@page.context_module_action(@current_user, @context, :contributed)
|
|
render :json => wiki_page_json(@page, @current_user, session)
|
|
else
|
|
render :json => @page.errors.to_json, :status => update_params.is_a?(Symbol) ? update_params : :bad_request
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API Delete page
|
|
#
|
|
# Delete a wiki page
|
|
#
|
|
# @argument url [String]
|
|
# the unique identifier for a page.
|
|
#
|
|
# @example_request
|
|
# curl -X DELETE -H 'Authorization: Bearer <token>' \
|
|
# https://<canvas>/api/v1/courses/123/pages/the-page-url
|
|
#
|
|
# @example_request
|
|
# curl -X DELETE -H 'Authorization: Bearer <token>' \
|
|
# https://<canvas>/api/v1/courses/123/front_page
|
|
#
|
|
# @returns Page
|
|
def destroy
|
|
if authorized_action(@page, @current_user, :delete)
|
|
if !@was_front_page
|
|
@page.workflow_state = 'deleted'
|
|
@page.save!
|
|
process_front_page
|
|
render :json => wiki_page_json(@page, @current_user, session)
|
|
else
|
|
@page.errors.add(:front_page, t(:cannot_delete_front_page, 'The front page cannot be deleted'))
|
|
render :json => @page.errors.to_json, :status => :bad_request
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API List revisions
|
|
#
|
|
# List the revisions of a page. Callers must have update rights on the page in order to see page history.
|
|
#
|
|
# @argument url [String]
|
|
# The unique identifier for a page
|
|
#
|
|
# @example_request
|
|
# curl -H 'Authorization: Bearer <token>' \
|
|
# https://<canvas>/api/v1/courses/123/pages/the-page-url/revisions
|
|
#
|
|
# @returns [PageRevision]
|
|
def revisions
|
|
if authorized_action(@page, @current_user, :read_revisions)
|
|
route = polymorphic_url([:api_v1, @context, @page, :revisions])
|
|
scope = @page.versions
|
|
revisions = Api.paginate(scope, self, route)
|
|
render :json => wiki_page_revisions_json(revisions, @current_user, session)
|
|
end
|
|
end
|
|
|
|
# @API Show revision
|
|
#
|
|
# Retrieve the metadata and optionally content of a revision of the page.
|
|
# Note that retrieving historic versions of pages requires edit rights.
|
|
#
|
|
# @argument url [String]
|
|
# The unique identifier for a page
|
|
#
|
|
# @argument summary [Optional, Boolean]
|
|
# If set, exclude page content from results
|
|
#
|
|
# @example_request
|
|
# curl -H 'Authorization: Bearer <token>' \
|
|
# https://<canvas>/api/v1/courses/123/pages/the-page-url/revisions/latest
|
|
#
|
|
# @example_request
|
|
# curl -H 'Authorization: Bearer <token>' \
|
|
# https://<canvas>/api/v1/courses/123/pages/the-page-url/revisions/4
|
|
#
|
|
# @returns PageRevision
|
|
def show_revision
|
|
if params.has_key?(:revision_id)
|
|
permission = :read_revisions
|
|
revision = @page.versions.find_by_number!(params[:revision_id].to_i)
|
|
else
|
|
permission = :read
|
|
revision = @page.versions.current
|
|
end
|
|
if authorized_action(@page, @current_user, permission)
|
|
include_content = if params.has_key?(:summary)
|
|
!value_to_boolean(params[:summary])
|
|
else
|
|
true
|
|
end
|
|
render :json => wiki_page_revision_json(revision, @current_user, session, include_content)
|
|
end
|
|
end
|
|
|
|
# @API Revert to revision
|
|
#
|
|
# Revert a page to a prior revision.
|
|
#
|
|
# @argument url [String]
|
|
# The unique identifier for the page
|
|
#
|
|
# @argument revision_id [Integer]
|
|
# The revision to revert to (use the
|
|
# {api:WikiPagesApiController#revisions List Revisions API} to see
|
|
# available revisions)
|
|
#
|
|
# @example_request
|
|
# curl -X POST -H 'Authorization: Bearer <token>' \
|
|
# https://<canvas>/api/v1/courses/123/pages/the-page-url/revisions/6
|
|
#
|
|
# @returns PageRevision
|
|
def revert
|
|
if authorized_action(@page, @current_user, :read_revisions) && authorized_action(@page, @current_user, :update)
|
|
revision_id = params[:revision_id].to_i
|
|
@revision = @page.versions.find_by_number!(revision_id).model
|
|
@page.body = @revision.body
|
|
@page.title = @revision.title
|
|
@page.url = @revision.url
|
|
@page.user_id = @current_user.id if @current_user
|
|
if @page.save
|
|
render :json => wiki_page_revision_json(@page.versions.current, @current_user, session, true)
|
|
else
|
|
render :json => @page.errors.to_json, :status => :bad_request
|
|
end
|
|
end
|
|
end
|
|
|
|
protected
|
|
|
|
def get_wiki_page
|
|
@wiki = @context.wiki
|
|
@wiki.check_has_front_page
|
|
|
|
url = params[:url]
|
|
if url.blank?
|
|
if @wiki.has_front_page?
|
|
url = @wiki.get_front_page_url
|
|
else
|
|
render :status => 404, :json => { :message => t(:no_wiki_front_page, "No front page has been set") }
|
|
return false
|
|
end
|
|
end
|
|
@page = @wiki.wiki_pages.not_deleted.find_by_url!(url)
|
|
end
|
|
|
|
def was_front_page
|
|
@was_front_page = false
|
|
@was_front_page = @page.is_front_page? if @page
|
|
end
|
|
|
|
def get_update_params(allowed_fields=Set[])
|
|
initialize_wiki_page
|
|
|
|
# normalize parameters
|
|
page_params = params[:wiki_page] || {}
|
|
if @context.draft_state_enabled?
|
|
page_params.slice!(*%w(title body notify_of_update published front_page editing_roles))
|
|
else
|
|
page_params.slice!(*%w(title body hide_from_students notify_of_update front_page editing_roles))
|
|
end
|
|
|
|
if page_params.has_key?(:published)
|
|
workflow_state = value_to_boolean(page_params.delete(:published)) ? 'active' : 'unpublished'
|
|
end
|
|
|
|
if page_params.has_key?(:editing_roles)
|
|
editing_roles = page_params[:editing_roles].split(',').map(&:strip)
|
|
invalid_roles = editing_roles.reject{|role| %w(teachers students members public).include?(role)}
|
|
unless invalid_roles.empty?
|
|
@page.errors.add(:editing_roles, t(:invalid_editing_roles, 'The provided editing roles are invalid'))
|
|
return :bad_request
|
|
end
|
|
|
|
page_params[:editing_roles] = editing_roles.join(',')
|
|
end
|
|
|
|
if page_params.has_key?(:front_page)
|
|
@set_front_page = true
|
|
@set_as_front_page = value_to_boolean(page_params.delete(:front_page))
|
|
end
|
|
change_front_page = @set_front_page && @was_front_page != @set_as_front_page
|
|
|
|
# check user permissions
|
|
rejected_fields = Set[]
|
|
if @wiki.grants_right?(@current_user, session, :manage)
|
|
allowed_fields.clear
|
|
else
|
|
rejected_fields << :published if workflow_state && workflow_state != @page.workflow_state
|
|
|
|
if editing_roles
|
|
existing_editing_roles = (@page.editing_roles || '').split(',')
|
|
editing_roles_changed = existing_editing_roles.reject{|role| editing_roles.include?(role)}.length > 0
|
|
editing_roles_changed |= editing_roles.reject{|role| existing_editing_roles.include?(role)}.length > 0
|
|
rejected_fields << :editing_roles if editing_roles_changed
|
|
end
|
|
|
|
rejected_fields << :hide_from_students if page_params.include?(:hide_from_students) && value_to_boolean(page_params[:hide_from_students]) != @page.hide_from_students
|
|
|
|
unless @page.grants_right?(@current_user, session, :update)
|
|
allowed_fields << :body
|
|
rejected_fields << :title if page_params.include?(:title) && page_params[:title] != @page.title
|
|
|
|
rejected_fields << :front_page if change_front_page && !@wiki.grants_right?(@current_user, session, :update)
|
|
end
|
|
end
|
|
|
|
# check rejected fields
|
|
rejected_fields = rejected_fields - allowed_fields
|
|
unless rejected_fields.empty?
|
|
@page.errors.add(:published, t(:cannot_update_published, 'You are not allowed to update the published state of this wiki page')) if rejected_fields.include?(:published)
|
|
@page.errors.add(:title, t(:cannot_update_title, 'You are not allowed to update the title of this wiki page')) if rejected_fields.include?(:title)
|
|
@page.errors.add(:hide_from_students, t(:cannot_update_hide_from_students, 'You are not allowed to update the hidden from students flag of this wiki page')) if rejected_fields.include?(:hide_from_students)
|
|
@page.errors.add(:editing_roles, t(:cannot_update_editing_roles, 'You are not allowed to update the editing roles of this wiki page')) if rejected_fields.include?(:editing_roles)
|
|
@page.errors.add(:front_page, t(:cannot_update_front_page, 'You are not allowed to change the wiki front page')) if rejected_fields.include?(:front_page)
|
|
|
|
return :unauthorized
|
|
end
|
|
|
|
# check for a valid front page
|
|
valid_front_page = true
|
|
|
|
if change_front_page || page_params.include?(:hide_from_students)
|
|
new_front_page = change_front_page ? @set_as_front_page : @page.is_front_page?
|
|
new_hide_from_students = page_params.include?(:hide_from_students) ? value_to_boolean(page_params[:hide_from_students]) : @page.hide_from_students
|
|
if new_front_page && new_hide_from_students
|
|
valid_front_page = false
|
|
error_message = t(:cannot_hide_front_page, 'The front page cannot be hidden from students')
|
|
@page.errors.add(:front_page, error_message) if change_front_page
|
|
@page.errors.add(:hide_from_students, error_message) if page_params.include?(:hide_from_students)
|
|
end
|
|
end
|
|
|
|
if change_front_page || workflow_state
|
|
new_front_page = change_front_page ? @set_as_front_page : @page.is_front_page?
|
|
new_workflow_state = workflow_state ? workflow_state : @page.workflow_state
|
|
valid_front_page = false if new_front_page && new_workflow_state != 'active'
|
|
if new_front_page && new_workflow_state != 'active'
|
|
valid_front_page = false
|
|
error_message = t(:cannot_have_unpublished_front_page, 'The front page cannot be unpublished')
|
|
@page.errors.add(:front_page, error_message) if change_front_page
|
|
@page.errors.add(:published, error_message) if workflow_state
|
|
end
|
|
end
|
|
|
|
return :bad_request unless valid_front_page
|
|
|
|
# limit to just the allowed fields
|
|
unless allowed_fields.empty?
|
|
page_params.slice!(*allowed_fields.to_a)
|
|
end
|
|
|
|
@page.workflow_state = workflow_state if workflow_state
|
|
|
|
page_params[:user_id] = @current_user.id if @current_user
|
|
page_params
|
|
end
|
|
|
|
def process_front_page
|
|
if @set_front_page
|
|
if @set_as_front_page && !@page.is_front_page?
|
|
return @page.set_as_front_page!
|
|
elsif !@set_as_front_page
|
|
return @page.wiki.unset_front_page!
|
|
end
|
|
elsif @was_front_page
|
|
if @page.deleted?
|
|
return @page.wiki.unset_front_page!
|
|
elsif !@page.is_front_page?
|
|
# if url changes, keep as front page
|
|
return @page.set_as_front_page!
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
end
|