Website : rimsha.abasa.com
backdoor
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
var
/
canvas
/
app
/
models
/
Filename :
asset_user_access.rb
back
Copy
# 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/>. # # asset_code is used to specify the 'asset' or idea being accessed # asset_group_code is for the group # so, for example, the asset could be an assignment, the group would be the assignment_group class AssetUserAccess < ActiveRecord::Base extend RootAccountResolver belongs_to :context, polymorphic: %i[account course group user], polymorphic_prefix: true belongs_to :user has_many :page_views # if you add any more callbacks, be sure to update #log before_save :infer_defaults before_save :infer_root_account_id resolves_root_account through: ->(instance) { instance.infer_root_account_id } scope :for_context, ->(context) { where(context_id: context, context_type: context.class.to_s) } scope :for_user, ->(user) { where(user_id: user) } scope :participations, -> { where(action_level: "participate") } scope :most_recent, -> { order("updated_at DESC") } def infer_root_account_id(asset_for_root_account_id = nil) self.root_account_id ||= if context_type != "User" context&.resolved_root_account_id || 0 elsif asset_for_root_account_id.is_a?(User) # Unfillable. Point to the dummy root account with id=0. 0 else asset_for_root_account_id.try(:resolved_root_account_id) || asset_for_root_account_id.try(:root_account_id) || 0 # We could default `asset_for_root_account_id ||= asset`, but AUAs shouldn't # ever be created outside of .log(), and calling `asset` would add a DB hit end end def category asset_category end def infer_defaults self.display_name = asset_display_name end def category=(val) self.asset_category = val end def display_name # repair existing AssetUserAccesses that have bad display_names if read_attribute(:display_name) == asset_code better_display_name = asset_display_name if better_display_name != asset_code update_attribute(:display_name, better_display_name) end end read_attribute(:display_name) end def asset_display_name return nil unless asset if asset.respond_to?(:title) && !asset.title.nil? asset.title elsif asset.is_a? Enrollment asset.user.name elsif asset.respond_to?(:name) && !asset.name.nil? asset.name else asset_code end end def context_code "#{context_type.underscore}_#{context_id}" rescue nil end def readable_name(include_group_name: true) if asset_code&.include?(":") split = asset_code.split(":") if split[1].match?(/course_\d+/) case split[0] when "announcements" t("Course Announcements") when "assignments" t("Course Assignments") when "calendar_feed" t("Course Calendar") when "collaborations" t("Course Collaborations") when "conferences" t("Course Conferences") when "files" t("Course Files") when "grades" t("Course Grades") when "home" t("Course Home") when "modules" t("Course Modules") when "outcomes" t("Course Outcomes") when "pages" t("Course Pages") when "quizzes" t("Course Quizzes") when "roster" t("Course People") when "speed_grader" t("SpeedGrader") when "syllabus" t("Course Syllabus") when "topics" t("Course Discussions") else "Course #{split[0].titleize}" end elsif (match = split[1].match(/group_(\d+)/)) && (group = Group.where(id: match[1]).first) case split[0] when "announcements" include_group_name ? t("%{group_name} - Group Announcements", group_name: group.name) : t("Group Announcements") when "calendar_feed" include_group_name ? t("%{group_name} - Group Calendar", group_name: group.name) : t("Group Calendar") when "collaborations" include_group_name ? t("%{group_name} - Group Collaborations", group_name: group.name) : t("Group Collaborations") when "conferences" include_group_name ? t("%{group_name} - Group Conferences", group_name: group.name) : t("Group Conferences") when "files" include_group_name ? t("%{group_name} - Group Files", group_name: group.name) : t("Group Files") when "home" include_group_name ? t("%{group_name} - Group Home", group_name: group.name) : t("Group Home") when "pages" include_group_name ? t("%{group_name} - Group Pages", group_name: group.name) : t("Group Pages") when "roster" include_group_name ? t("%{group_name} - Group People", group_name: group.name) : t("Group People") when "topics" include_group_name ? t("%{group_name} - Group Discussions", group_name: group.name) : t("Group Discussions") else "#{include_group_name ? "#{group.name} - " : ""}Group #{split[0].titleize}" end elsif split[1].match?(/user_\d+/) case split[0] when "files" t("User Files") else display_name end else display_name end else re = Regexp.new("#{asset_code} - ") display_name.nil? ? "" : display_name.gsub(re, "") end end def asset unless @asset return nil unless asset_code asset_code, = self.asset_code.split(":").reverse @asset = Context.find_asset_by_asset_string(asset_code, context) @asset ||= (match = asset_code.match(/enrollment_(\d+)/)) && Enrollment.where(id: match[1]).first end @asset end def asset_class_name name = asset.class.name.underscore if asset name = "Quiz" if name == "Quizzes::Quiz" name end def self.get_correct_context(context, accessed_asset) if accessed_asset[:category] == "files" && accessed_asset[:code]&.starts_with?("attachment") attachment_id = accessed_asset[:code].match(/\A\w+_(\d+)\z/)[1] asset = accessed_asset[:asset_for_root_account_id] return asset.context if asset.is_a?(Attachment) && asset.id == attachment_id Attachment.find_by(id: attachment_id)&.context elsif context.is_a?(UserProfile) context.user elsif context.is_a?(AssessmentQuestion) context.context else context end end def self.log(user, context, accessed_asset) return unless user && accessed_asset[:code] correct_context = get_correct_context(context, accessed_asset) return unless correct_context && Context::CONTEXT_TYPES.include?(correct_context.class_name.to_sym) GuardRail.activate(:secondary) do @access = AssetUserAccess.where(user:, asset_code: accessed_asset[:code], context: correct_context).first_or_initialize end accessed_asset[:level] ||= "view" @access.log correct_context, accessed_asset end def log(kontext, accessed) self.asset_category ||= accessed[:category] self.asset_group_code ||= accessed[:group_code] self.membership_type ||= accessed[:membership_type] self.context = kontext self.updated_at = self.last_access = Time.now.utc log_action(accessed[:level]) # manually call callbacks to avoid transactions. this saves a BEGIN/COMMIT per request infer_defaults infer_root_account_id(accessed[:asset_for_root_account_id]) if self.class.use_log_compaction_for_views? && eligible_for_log_path? # Since this is JUST a view bump, we'll write it to the # view log and let periodic jobs compact them later # (this is intentionally trading off more latency for less I/O pressure) AssetUserAccessLog.put_view(self) else save_without_transaction end self end def eligible_for_log_path? # in general we want writes to go to the table right now. # view count updates happen a LOT though, so if the setting is # configured such that we're allowed to use the log path, check # if this set of changes is "just" a view update. change_hash = changes_to_save updated_key_set = changes_to_save.keys.to_set return false unless updated_key_set.include?("view_score") return false unless (updated_key_set - Set.new(%w[updated_at last_access view_score])).empty? # ASSUMPTION: All view_score updates are a single increment. # If this is violated, rather than failing to capture, we should accept the # write through the row update for now (by returning false from here). view_delta = change_hash["view_score"].compact # ^array with old and new value, which CAN be null, hence compact return false if view_delta.empty? return (view_delta[0] - 1.0).abs < Float::EPSILON if view_delta.size == 1 (view_delta[1] - view_delta[0]).abs == 1 # this is an increment, if true end def log_action(level) increment(:view_score) if %w[view participate].include?(level) increment(:participate_score) if %w[participate submit].include?(level) if action_level != "participate" self.action_level = (level == "submit") ? "participate" : level end end def self.use_log_compaction_for_views? view_counting_method.to_s == "log" end def self.view_counting_method Canvas::Plugin.find(:asset_user_access_logs).settings[:write_path] end def self.infer_asset(code) asset_code, = code.split(":").reverse Context.find_asset_by_asset_string(asset_code) end # For Quizzes, we want the view score not to include the participation score # so it reflects the number of times a student really just browsed the quiz. def corrected_view_score deductible_points = 0 if self.asset_group_code == "quizzes" deductible_points = participate_score || 0 end self.view_score ||= 0 self.view_score -= deductible_points end # Includes both the icon name and the associated screenreader label for the icon ICON_MAP = { announcements: ["icon-announcement", t("Announcement")].freeze, assignments: ["icon-assignment", t("Assignment")].freeze, calendar: ["icon-calendar-month", t("Calendar")].freeze, collaborations: ["icon-document", t("Collaboration")].freeze, conferences: ["icon-group", t("Conference")].freeze, external_tools: ["icon-link", t("App")].freeze, files: ["icon-download", t("File")].freeze, grades: ["icon-gradebook", t("Grades")].freeze, home: ["icon-home", t("Home")].freeze, inbox: ["icon-message", t("Inbox")].freeze, modules: ["icon-module", t("Module")].freeze, outcomes: ["icon-outcomes", t("Outcome")].freeze, pages: ["icon-document", t("Page")].freeze, quizzes: ["icon-quiz", t("Quiz")].freeze, roster: ["icon-user", t("People")].freeze, syllabus: ["icon-syllabus", t("Syllabus")].freeze, topics: ["icon-discussion", t("Discussion")].freeze, wiki: ["icon-document", t("Page")].freeze }.freeze def icon ICON_MAP[asset_category.to_sym]&.[](0) || "icon-question" end def readable_category ICON_MAP[asset_category.to_sym]&.[](1) || "" end def self.expiration_date 2.years.ago end DELETE_BATCH_SIZE = 10_000 DELETE_BATCH_SLEEP = 5 def self.delete_old_records loop do count = AssetUserAccess.connection.with_max_update_limit(DELETE_BATCH_SIZE) do where(last_access: ..expiration_date).limit(DELETE_BATCH_SIZE).delete_all end break if count.zero? sleep(DELETE_BATCH_SLEEP) # rubocop:disable Lint/NoSleep end end private def increment(attribute) incremented_value = (send(attribute) || 0) + 1 send(:"#{attribute}=", incremented_value) end end