canvas-lms/lib/user_content.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

260 lines
8.3 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
#
# 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/>.
require "nokogiri"
require "ritex"
module UserContent
Don't use new math handling when editing quizzes closes LS-1639 flag=none The change to SyllabusBehaviors puts code back that existed before the new math handling was introduced, and should have been behind its flag. If the new_math_equation_handling flag is on, turn it off if we're editing a quiz, and skip having the backend inject the hidden mathml, which is part of the legacy equation handling. test plan: === With the new math_equation_handling flag off ==== - create a quiz, add a multiple choice question with an equation as an answer - save the question, save the quiz - edit the quiz, edit the question, do not edit the answer > look at the DOM. there should be no moe than 1 <span class="hidden-readable"> just after the equation image in the answer - save the question, save the quiz - preview the quiz > expect just 1 <span class="hidden-readable"> in the DOM just after the equation image - no combination of edit, save, edit, ... should cause > 1 <span class="hidden-readable"> after the equation image === with the new_math_equation_handling flag on === - preview and edit the quiz you created with the flag off > expect it to look A-OK - create a new quiz - use the rce's equation editor to put an equation everywhere you can possible think of in a quiz - the text - answers - comments on the answers > expect the equations to look right no matter what - edit the quiz and all the places where there are equations > yep still ok - save the qustion > still ok - save the quiz > still ok - preview the quiz, to completion to see answer comments > looks good _and_ equations are mathjaxified - edit everything again > still looks good everywhere Change-Id: I1319d007509f6e8cbc9c9af81e3939e365b0fa92 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/253507 Reviewed-by: Jackson Howe <jackson.howe@instructure.com> Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> QA-Review: Robin Kuss <rkuss@instructure.com> Product-Review: Ed Schiebel <eschiebel@instructure.com>
2020-11-21 22:28:44 +08:00
def self.escape(
str,
current_host = nil,
use_updated_math_rendering = true
Don't use new math handling when editing quizzes closes LS-1639 flag=none The change to SyllabusBehaviors puts code back that existed before the new math handling was introduced, and should have been behind its flag. If the new_math_equation_handling flag is on, turn it off if we're editing a quiz, and skip having the backend inject the hidden mathml, which is part of the legacy equation handling. test plan: === With the new math_equation_handling flag off ==== - create a quiz, add a multiple choice question with an equation as an answer - save the question, save the quiz - edit the quiz, edit the question, do not edit the answer > look at the DOM. there should be no moe than 1 <span class="hidden-readable"> just after the equation image in the answer - save the question, save the quiz - preview the quiz > expect just 1 <span class="hidden-readable"> in the DOM just after the equation image - no combination of edit, save, edit, ... should cause > 1 <span class="hidden-readable"> after the equation image === with the new_math_equation_handling flag on === - preview and edit the quiz you created with the flag off > expect it to look A-OK - create a new quiz - use the rce's equation editor to put an equation everywhere you can possible think of in a quiz - the text - answers - comments on the answers > expect the equations to look right no matter what - edit the quiz and all the places where there are equations > yep still ok - save the qustion > still ok - save the quiz > still ok - preview the quiz, to completion to see answer comments > looks good _and_ equations are mathjaxified - edit everything again > still looks good everywhere Change-Id: I1319d007509f6e8cbc9c9af81e3939e365b0fa92 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/253507 Reviewed-by: Jackson Howe <jackson.howe@instructure.com> Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> QA-Review: Robin Kuss <rkuss@instructure.com> Product-Review: Ed Schiebel <eschiebel@instructure.com>
2020-11-21 22:28:44 +08:00
)
html = Nokogiri::HTML5.fragment(str, nil, **CanvasSanitize::SANITIZE[:parser_options])
find_user_content(html) do |obj, uc|
uuid = SecureRandom.uuid
child = Nokogiri::XML::Node.new("iframe", html)
child["class"] = "user_content_iframe"
child["name"] = uuid
child["style"] = "width: #{uc.width}; height: #{uc.height}"
child["frameborder"] = "0"
form = Nokogiri::XML::Node.new("form", html)
form["action"] = "//#{HostUrl.file_host(@domain_root_account || Account.default, current_host)}/object_snippet"
form["method"] = "post"
form["class"] = "user_content_post_form"
form["target"] = uuid
form["id"] = "form-#{uuid}"
input = Nokogiri::XML::Node.new("input", html)
input["type"] = "hidden"
input["name"] = "object_data"
input["value"] = uc.node_string
form.add_child(input)
s_input = Nokogiri::XML::Node.new("input", html)
s_input["type"] = "hidden"
s_input["name"] = "s"
s_input["value"] = uc.node_hmac
form.add_child(s_input)
obj.replace(child)
child.add_next_sibling(form)
end
find_equation_images(html) do |node|
equation = node["data-equation-content"] || node["alt"]
Update math equation rendering This commit changes nothing in the RCE, but changes how canvas renders equations in the resulting page. Rather than adding mathml in a hidden span adjacent to the equation image for MathJax to process, this change replaces the image with a span containing the LaTex source for MathJax to format. This is better for a couple reasons. MathJax is not intended for formatting equations as they are being edited and dealing with MathJax processed equations in the RCE when you may want to edit an existing equation is never going to work well. The visible MathJax-ified equations in the resulting page provides the accessiblity we require. This approach will update all existing content with math images, so old content gets the benefit too. Contrary to what some believe, you will not be able to select, copy and paste parts of an equation. closes: LS-1401 flag=new_math_equation_handling test plan: - with new_math_equation_handling flag on - insert a math equation in the RCE > notice that when you click on the equation's image, you do not get the "Image Options" popup button - click on the equation > expect the "Edit Equation" popup button, not "Image Options" - edit the equation and save > expect it to be updated - save the page > expect the equation to fade into view after being processed by MathJax. > expect the MathJax menu when you right click in it > expect screenreaders read it nicely - edit the page again > expect the equation as an image again. > expect to be able to edit the equation - switch your user to a different language - open a page with an equation and right-click in the eq. > expect the mathjax context menu to be in the user's language (assuming mathjax supports it) Change-Id: Ieac6785d51c0cab475b1176712f46fc2c964ff71 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/247471 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Jackson Howe <jackson.howe@instructure.com> QA-Review: Daniel Sasaki <dsasaki@instructure.com> Product-Review: Peyton Craighill <pcraighill@instructure.com>
2020-09-12 01:15:52 +08:00
next if equation.blank?
Don't use new math handling when editing quizzes closes LS-1639 flag=none The change to SyllabusBehaviors puts code back that existed before the new math handling was introduced, and should have been behind its flag. If the new_math_equation_handling flag is on, turn it off if we're editing a quiz, and skip having the backend inject the hidden mathml, which is part of the legacy equation handling. test plan: === With the new math_equation_handling flag off ==== - create a quiz, add a multiple choice question with an equation as an answer - save the question, save the quiz - edit the quiz, edit the question, do not edit the answer > look at the DOM. there should be no moe than 1 <span class="hidden-readable"> just after the equation image in the answer - save the question, save the quiz - preview the quiz > expect just 1 <span class="hidden-readable"> in the DOM just after the equation image - no combination of edit, save, edit, ... should cause > 1 <span class="hidden-readable"> after the equation image === with the new_math_equation_handling flag on === - preview and edit the quiz you created with the flag off > expect it to look A-OK - create a new quiz - use the rce's equation editor to put an equation everywhere you can possible think of in a quiz - the text - answers - comments on the answers > expect the equations to look right no matter what - edit the quiz and all the places where there are equations > yep still ok - save the qustion > still ok - save the quiz > still ok - preview the quiz, to completion to see answer comments > looks good _and_ equations are mathjaxified - edit everything again > still looks good everywhere Change-Id: I1319d007509f6e8cbc9c9af81e3939e365b0fa92 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/253507 Reviewed-by: Jackson Howe <jackson.howe@instructure.com> Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> QA-Review: Robin Kuss <rkuss@instructure.com> Product-Review: Ed Schiebel <eschiebel@instructure.com>
2020-11-21 22:28:44 +08:00
# there are places in canvas (e.g. classic quizzes) that
# inadvertently saved the hidden-readable span, causing
# them to multiply everytime the entity is edited.
# Strip the ones that shouldn't be there before adding a new one
node.next_element.remove while node.next_element && node.next_element["class"] == "hidden-readable"
Don't use new math handling when editing quizzes closes LS-1639 flag=none The change to SyllabusBehaviors puts code back that existed before the new math handling was introduced, and should have been behind its flag. If the new_math_equation_handling flag is on, turn it off if we're editing a quiz, and skip having the backend inject the hidden mathml, which is part of the legacy equation handling. test plan: === With the new math_equation_handling flag off ==== - create a quiz, add a multiple choice question with an equation as an answer - save the question, save the quiz - edit the quiz, edit the question, do not edit the answer > look at the DOM. there should be no moe than 1 <span class="hidden-readable"> just after the equation image in the answer - save the question, save the quiz - preview the quiz > expect just 1 <span class="hidden-readable"> in the DOM just after the equation image - no combination of edit, save, edit, ... should cause > 1 <span class="hidden-readable"> after the equation image === with the new_math_equation_handling flag on === - preview and edit the quiz you created with the flag off > expect it to look A-OK - create a new quiz - use the rce's equation editor to put an equation everywhere you can possible think of in a quiz - the text - answers - comments on the answers > expect the equations to look right no matter what - edit the quiz and all the places where there are equations > yep still ok - save the qustion > still ok - save the quiz > still ok - preview the quiz, to completion to see answer comments > looks good _and_ equations are mathjaxified - edit everything again > still looks good everywhere Change-Id: I1319d007509f6e8cbc9c9af81e3939e365b0fa92 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/253507 Reviewed-by: Jackson Howe <jackson.howe@instructure.com> Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> QA-Review: Robin Kuss <rkuss@instructure.com> Product-Review: Ed Schiebel <eschiebel@instructure.com>
2020-11-21 22:28:44 +08:00
unless use_updated_math_rendering
Update math equation rendering This commit changes nothing in the RCE, but changes how canvas renders equations in the resulting page. Rather than adding mathml in a hidden span adjacent to the equation image for MathJax to process, this change replaces the image with a span containing the LaTex source for MathJax to format. This is better for a couple reasons. MathJax is not intended for formatting equations as they are being edited and dealing with MathJax processed equations in the RCE when you may want to edit an existing equation is never going to work well. The visible MathJax-ified equations in the resulting page provides the accessiblity we require. This approach will update all existing content with math images, so old content gets the benefit too. Contrary to what some believe, you will not be able to select, copy and paste parts of an equation. closes: LS-1401 flag=new_math_equation_handling test plan: - with new_math_equation_handling flag on - insert a math equation in the RCE > notice that when you click on the equation's image, you do not get the "Image Options" popup button - click on the equation > expect the "Edit Equation" popup button, not "Image Options" - edit the equation and save > expect it to be updated - save the page > expect the equation to fade into view after being processed by MathJax. > expect the MathJax menu when you right click in it > expect screenreaders read it nicely - edit the page again > expect the equation as an image again. > expect to be able to edit the equation - switch your user to a different language - open a page with an equation and right-click in the eq. > expect the mathjax context menu to be in the user's language (assuming mathjax supports it) Change-Id: Ieac6785d51c0cab475b1176712f46fc2c964ff71 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/247471 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Jackson Howe <jackson.howe@instructure.com> QA-Review: Daniel Sasaki <dsasaki@instructure.com> Product-Review: Peyton Craighill <pcraighill@instructure.com>
2020-09-12 01:15:52 +08:00
mathml = UserContent.latex_to_mathml(equation)
next if mathml.blank?
mathml_span = node.fragment(
Update math equation rendering This commit changes nothing in the RCE, but changes how canvas renders equations in the resulting page. Rather than adding mathml in a hidden span adjacent to the equation image for MathJax to process, this change replaces the image with a span containing the LaTex source for MathJax to format. This is better for a couple reasons. MathJax is not intended for formatting equations as they are being edited and dealing with MathJax processed equations in the RCE when you may want to edit an existing equation is never going to work well. The visible MathJax-ified equations in the resulting page provides the accessiblity we require. This approach will update all existing content with math images, so old content gets the benefit too. Contrary to what some believe, you will not be able to select, copy and paste parts of an equation. closes: LS-1401 flag=new_math_equation_handling test plan: - with new_math_equation_handling flag on - insert a math equation in the RCE > notice that when you click on the equation's image, you do not get the "Image Options" popup button - click on the equation > expect the "Edit Equation" popup button, not "Image Options" - edit the equation and save > expect it to be updated - save the page > expect the equation to fade into view after being processed by MathJax. > expect the MathJax menu when you right click in it > expect screenreaders read it nicely - edit the page again > expect the equation as an image again. > expect to be able to edit the equation - switch your user to a different language - open a page with an equation and right-click in the eq. > expect the mathjax context menu to be in the user's language (assuming mathjax supports it) Change-Id: Ieac6785d51c0cab475b1176712f46fc2c964ff71 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/247471 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Jackson Howe <jackson.howe@instructure.com> QA-Review: Daniel Sasaki <dsasaki@instructure.com> Product-Review: Peyton Craighill <pcraighill@instructure.com>
2020-09-12 01:15:52 +08:00
"<span class=\"hidden-readable\">#{mathml}</span>"
).children.first
Update math equation rendering This commit changes nothing in the RCE, but changes how canvas renders equations in the resulting page. Rather than adding mathml in a hidden span adjacent to the equation image for MathJax to process, this change replaces the image with a span containing the LaTex source for MathJax to format. This is better for a couple reasons. MathJax is not intended for formatting equations as they are being edited and dealing with MathJax processed equations in the RCE when you may want to edit an existing equation is never going to work well. The visible MathJax-ified equations in the resulting page provides the accessiblity we require. This approach will update all existing content with math images, so old content gets the benefit too. Contrary to what some believe, you will not be able to select, copy and paste parts of an equation. closes: LS-1401 flag=new_math_equation_handling test plan: - with new_math_equation_handling flag on - insert a math equation in the RCE > notice that when you click on the equation's image, you do not get the "Image Options" popup button - click on the equation > expect the "Edit Equation" popup button, not "Image Options" - edit the equation and save > expect it to be updated - save the page > expect the equation to fade into view after being processed by MathJax. > expect the MathJax menu when you right click in it > expect screenreaders read it nicely - edit the page again > expect the equation as an image again. > expect to be able to edit the equation - switch your user to a different language - open a page with an equation and right-click in the eq. > expect the mathjax context menu to be in the user's language (assuming mathjax supports it) Change-Id: Ieac6785d51c0cab475b1176712f46fc2c964ff71 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/247471 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Jackson Howe <jackson.howe@instructure.com> QA-Review: Daniel Sasaki <dsasaki@instructure.com> Product-Review: Peyton Craighill <pcraighill@instructure.com>
2020-09-12 01:15:52 +08:00
node.add_next_sibling(mathml_span)
end
end
html.to_s.html_safe
end
def self.latex_to_mathml(latex)
enable use of MathMan for latex => mathml conversion fixes CNVS-59514 Also adds a MathMan module that wraps some convenience methods for 1- figuring out if it's ok to use mathman; 2- constructing urls for hitting mathman's endpoints. test plan: *With Ritex* - Add an assignment (or some type of content with a description editable via tinymce / rcs). - Update the description to include a latex equation using the equation editor. - Upon saving the assignment, observe that an image of the equation is visible. - Using the browser's elemnt / DOM inspector, select the equation image, and observe that there is a hidden span that contain a math ml representation. *With Mathman* - Navigate to an account's plugin page, and select the MathMan plugin. - On the edit screen, enable the plugin, and provide a working mathman base url and check the 'Use for mml' checkbox. Save the changes. - Add an assignment (or some type of content with a description editable via tinymce / rcs). - Update the description to include a latex equation using the equation editor. - Upon saving the assignment, observe that an image of the equation is visible. - Using the browser's elemnt / DOM inspector, select the equation image, and observe that there is a hidden span that contain a math ml representation. Change-Id: I194d155b339123f7ed1948cf29070c1d17fc7f17 Reviewed-on: https://gerrit.instructure.com/84031 Tested-by: Jenkins Reviewed-by: Simon Williams <simon@instructure.com> QA-Review: Benjamin Christian Nelson <bcnelson@instructure.com> Product-Review: John Corrigan <jcorrigan@instructure.com>
2016-08-02 03:28:43 +08:00
Latex.to_math_ml(latex: latex)
end
Node = Struct.new(:width, :height, :node_string, :node_hmac)
# for each user content in the nokogiri document, yields |nokogiri_node, UserContent::Node|
def self.find_user_content(html)
html.css("object,embed").each do |obj|
styles = {}
params = {}
obj.css("param").each do |param|
params[param["key"]] = param["value"]
end
(obj["style"] || "").split(";").each do |attr|
key, value = attr.split(":").map(&:strip)
styles[key] = value
end
width = css_size(obj["width"])
width ||= css_size(params["width"])
width ||= css_size(styles["width"])
width ||= "400px"
height = css_size(obj["height"])
height ||= css_size(params["height"])
height ||= css_size(styles["height"])
height ||= "300px"
snippet = Base64.encode64(obj.to_s).delete("\n")
hmac = Canvas::Security.hmac_sha1(snippet)
uc = Node.new(width, height, snippet, hmac)
yield obj, uc
end
end
def self.find_equation_images(html, &block)
html.css("img.equation_image").each(&block)
end
# TODO: try and discover the motivation behind the "huhs"
def self.css_size(val)
to_f = TextHelper.round_if_whole(val.to_f)
if !val || to_f == 0
# no value, non-numeric value, or 0 value (whether "0", "0px", "0%",
# etc.); ignore
nil
elsif val == "#{to_f}%" || val == "#{to_f}px"
# numeric percentage or specific px value; use as is
val
elsif to_f.to_s == val
# unadorned numeric value; make px (after adding 10... huh?)
(to_f + 10).to_s + "px"
else
# numeric value embedded, but has additional text we didn't recognize;
# just extract the numeric part (without a px... huh?)
to_f.to_s
end
end
class HtmlRewriter
AssetTypes = {
"assignments" => :Assignment,
"announcements" => :Announcement,
"calendar_events" => :CalendarEvent,
"courses" => :Course,
"discussion_topics" => :DiscussionTopic,
"collaborations" => :Collaboration,
"files" => :Attachment,
"conferences" => :WebConference,
"quizzes" => :"Quizzes::Quiz",
"groups" => :Group,
"wiki" => :WikiPage,
"pages" => :WikiPage,
"grades" => nil,
"users" => nil,
"external_tools" => nil,
"file_contents" => nil,
"modules" => :ContextModule,
"items" => :ContentTag
}.freeze
DefaultAllowedTypes = AssetTypes.keys
def initialize(context, user, contextless_types: [])
raise(ArgumentError, "context required") unless context
@context = context
@user = user
@contextless_types = contextless_types
@context_prefix = "/#{context.class.name.tableize}/#{context.id}"
@absolute_part = '(https?://[\w-]+(?:\.[\w-]+)*(?:\:\d{1,5})?)?'
@toplevel_regex = %r{#{@absolute_part}(#{@context_prefix})?/(\w+)(?:/([^\s"<'?/]*)([^\s"<']*))?}
@handlers = {}
@default_handler = nil
@unknown_handler = nil
@allowed_types = DefaultAllowedTypes
end
attr_reader :user, :context
UriMatch = Struct.new(:url, :type, :obj_class, :obj_id, :rest, :prefix) do
def query
rest && rest[/\?.*/]
end
end
# specify a url type like "assignments" or "file_contents"
def set_handler(type, &handler)
@handlers[type] = handler
end
def set_default_handler(&handler)
@default_handler = handler
end
def set_unknown_handler(&handler)
@unknown_handler = handler
end
def allowed_types=(new_types)
@allowed_types = Array(new_types)
end
def translate_content(html)
return html if html.blank?
asset_types = AssetTypes.slice(*@allowed_types)
html.gsub(@toplevel_regex) do |url|
_absolute_part, prefix, type, obj_id, rest = [$1, $2, $3, $4, $5]
next url if !@contextless_types.include?(type) && prefix != @context_prefix && url != @context_prefix
if type != "wiki" && type != "pages"
if obj_id.to_i > 0
obj_id = obj_id.to_i
else
rest = "/#{obj_id}#{rest}" if obj_id.present? || rest.present?
obj_id = nil
end
end
if (module_item = rest.try(:match, %r{/items/(\d+)}))
type = "items"
obj_id = module_item[1].to_i
end
if asset_types.key?(type)
klass = asset_types[type]
klass = klass.to_s.constantize if klass
match = UriMatch.new(url, type, klass, obj_id, rest, prefix)
handler = @handlers[type] || @default_handler
handler&.call(match) || url
else
match = UriMatch.new(url, type)
@unknown_handler&.call(match) || url
end
end
end
# if content is nil, it'll query the block for the content if needed (lazy content load)
def user_can_view_content?(content = nil)
return false if user.blank? && content.respond_to?(:locked?) && content.locked?
return true unless user
# if user given, check that the user is allowed to manage all
# context content, or read that specific item (and it's not locked)
@read_as_admin = context.grants_right?(user, :read_as_admin) if @read_as_admin.nil?
return true if @read_as_admin
content ||= yield
allow = true if content.respond_to?(:grants_right?) && content.grants_right?(user, :read)
allow = false if allow && content.respond_to?(:locked_for?) && content.locked_for?(user)
allow
end
end
end