canvas-lms/app/models/account.rb

1081 lines
41 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/>.
#
class Account < ActiveRecord::Base
include Context
attr_accessible :name, :turnitin_account_id,
:turnitin_shared_secret, :turnitin_comments, :turnitin_pledge,
:default_time_zone, :parent_account, :settings, :default_storage_quota,
:default_storage_quota_mb, :storage_quota, :ip_filters, :default_locale
include Workflow
belongs_to :parent_account, :class_name => 'Account'
belongs_to :root_account, :class_name => 'Account'
authenticates_many :pseudonym_sessions
has_many :courses
has_many :all_courses, :class_name => 'Course', :foreign_key => 'root_account_id'
has_many :group_categories, :as => :context, :conditions => ['deleted_at IS NULL']
has_many :all_group_categories, :class_name => 'GroupCategory', :as => :context
has_many :groups, :as => :context
has_many :enrollment_terms, :foreign_key => 'root_account_id'
has_many :enrollments, :foreign_key => 'root_account_id'
has_many :sub_accounts, :class_name => 'Account', :foreign_key => 'parent_account_id', :conditions => ['workflow_state != ?', 'deleted']
has_many :all_accounts, :class_name => 'Account', :foreign_key => 'root_account_id', :order => 'name'
has_many :account_users, :dependent => :destroy
has_many :course_sections, :foreign_key => 'root_account_id'
has_many :learning_outcomes, :as => :context
has_many :sis_batches
has_many :abstract_courses, :class_name => 'AbstractCourse', :foreign_key => 'account_id'
has_many :root_abstract_courses, :class_name => 'AbstractCourse', :foreign_key => 'root_account_id'
has_many :users, :through => :account_users
has_many :pseudonyms, :include => :user
has_many :role_overrides, :as => :context
has_many :rubrics, :as => :context
has_many :rubric_associations, :as => :context, :include => :rubric, :dependent => :destroy
has_many :course_account_associations
has_many :associated_courses, :through => :course_account_associations, :source => :course, :select => 'DISTINCT courses.*'
has_many :child_courses, :through => :course_account_associations, :source => :course, :conditions => ['course_account_associations.depth = 0']
has_many :attachments, :as => :context, :dependent => :destroy
has_many :active_assignments, :as => :context, :class_name => 'Assignment', :conditions => ['assignments.workflow_state != ?', 'deleted']
has_many :folders, :as => :context, :dependent => :destroy, :order => 'folders.name'
has_many :active_folders, :class_name => 'Folder', :as => :context, :conditions => ['folders.workflow_state != ?', 'deleted'], :order => 'folders.name'
has_many :active_folders_with_sub_folders, :class_name => 'Folder', :as => :context, :include => [:active_sub_folders], :conditions => ['folders.workflow_state != ?', 'deleted'], :order => 'folders.name'
has_many :active_folders_detailed, :class_name => 'Folder', :as => :context, :include => [:active_sub_folders, :active_file_attachments], :conditions => ['folders.workflow_state != ?', 'deleted'], :order => 'folders.name'
has_many :account_authorization_configs, :order => 'id'
has_many :account_reports
has_many :grading_standards, :as => :context
has_many :assessment_questions, :through => :assessment_question_banks
has_many :assessment_question_banks, :as => :context, :include => [:assessment_questions, :assessment_question_bank_users]
def inherited_assessment_question_banks(include_self = false, *additional_contexts)
sql = []
conds = []
contexts = additional_contexts + account_chain
contexts.delete(self) unless include_self
contexts.each { |c|
sql << "context_type = ? AND context_id = ?"
conds += [c.class.to_s, c.id]
}
conds.unshift(sql.join(" OR "))
AssessmentQuestionBank.scoped :conditions => conds
end
has_many :context_external_tools, :as => :context, :dependent => :destroy, :order => 'name'
has_many :learning_outcomes, :as => :context
has_many :learning_outcome_groups, :as => :context
has_many :created_learning_outcomes, :class_name => 'LearningOutcome', :as => :context
has_many :learning_outcome_tags, :class_name => 'ContentTag', :as => :context, :conditions => ['content_tags.tag_type = ? AND workflow_state != ?', 'learning_outcome_association', 'deleted']
has_many :associated_learning_outcomes, :through => :learning_outcome_tags, :source => :learning_outcome
has_many :page_views
has_many :error_reports
has_many :account_notifications
has_many :alerts, :as => :context, :include => :criteria
has_many :associated_alerts, :through => :associated_courses, :source => :alerts, :include => :criteria
has_many :user_account_associations
before_validation :verify_unique_sis_source_id
before_save :ensure_defaults
before_save :set_update_account_associations_if_changed
after_save :update_account_associations_if_changed
after_create :default_enrollment_term
serialize :settings, Hash
scopes_custom_fields
validates_locale :default_locale, :allow_nil => true
include StickySisFields
are_sis_sticky :name
def default_locale(recurse = false)
read_attribute(:default_locale) ||
(recurse && parent_account ? parent_account.default_locale(true) : nil)
end
cattr_accessor :account_settings_options
self.account_settings_options = {}
# I figure we're probably going to be adding more account-level
# settings in the future (and moving some of the column attributes
# to the settings hash), so it makes sense to have a general way
# of defining what settings are allowed when. Somebody please tell
# me if I'm overarchitecting...
def self.add_setting(setting, opts=nil)
self.account_settings_options[setting.to_sym] = opts || {}
if (opts && opts[:boolean] && opts.has_key?(:default))
if opts[:default]
self.class_eval "def #{setting}?; settings[:#{setting}] != false; end"
else
self.class_eval "def #{setting}?; !!settings[:#{setting}]; end"
end
end
end
# these settings either are or could be easily added to
# the account settings page
add_setting :global_javascript, :condition => :global_includes, :root_only => true
add_setting :global_stylesheet, :condition => :global_includes, :root_only => true
add_setting :error_reporting, :hash => true, :values => [:action, :email, :url, :subject_param, :body_param], :root_only => true
add_setting :prevent_course_renaming_by_teachers, :boolean => true, :root_only => true
add_setting :teachers_can_create_courses, :boolean => true, :root_only => true, :default => false
add_setting :students_can_create_courses, :boolean => true, :root_only => true, :default => false
add_setting :no_enrollments_can_create_courses, :boolean => true, :root_only => true, :default => false
add_setting :allow_sending_scores_in_emails, :boolean => true, :root_only => true
add_setting :support_url, :root_only => true
add_setting :self_enrollment
add_setting :equella_endpoint
add_setting :equella_teaser
add_setting :enable_alerts, :boolean => true, :root_only => true
add_setting :enable_eportfolios, :boolean => true, :root_only => true
add_setting :users_can_edit_name, :boolean => true, :root_only => true
add_setting :open_registration, :boolean => true, :root_only => true, :default => false
def settings=(hash)
if hash.is_a?(Hash)
hash.each do |key, val|
if account_settings_options && account_settings_options[key.to_sym]
opts = account_settings_options[key.to_sym]
if (opts[:root_only] && root_account_id) || (opts[:condition] && !settings[opts[:condition].to_sym])
settings.delete key.to_sym
elsif opts[:boolean]
settings[key.to_sym] = (val == true || val == 'true' || val == '1' || val == 'on')
elsif opts[:hash]
new_hash = {}
if val.is_a?(Hash)
val.each do |inner_key, inner_val|
if opts[:values].include?(inner_key.to_sym)
new_hash[inner_key.to_sym] = inner_val.to_s
end
end
end
settings[key.to_sym] = new_hash.empty? ? nil : new_hash
else
settings[key.to_sym] = val.to_s
end
end
end
end
settings
end
def ip_filters=(params)
filters = {}
require 'ipaddr'
params.each do |key, str|
ips = []
vals = str.split(/,/)
vals.each do |val|
ip = IPAddr.new(val) rescue nil
# right now the ip_filter column on quizzes is just a string,
# so it has a max length. I figure whatever we set it to this
# setter should at the very least limit stored values to that
# length.
ips << val if ip && val.length <= 255
end
filters[key] = ips.join(',') unless ips.empty?
end
settings[:ip_filters] = filters
end
def ensure_defaults
self.uuid ||= AutoHandle.generate_securish_uuid
end
def verify_unique_sis_source_id
return true unless self.sis_source_id
root = self.root_account || self
existing_account = Account.find_by_root_account_id_and_sis_source_id(root.id, self.sis_source_id)
if self.root_account?
return true if !existing_account
elsif root.sis_source_id != self.sis_source_id
return true if !existing_account || existing_account.id == self.id
end
self.errors.add(:sis_source_id, t('#account.sis_id_in_use', "SIS ID \"%{sis_id}\" is already in use", :sis_id => self.sis_source_id))
false
end
def set_update_account_associations_if_changed
self.root_account_id ||= self.parent_account.root_account_id if self.parent_account
self.root_account_id ||= self.parent_account_id
self.parent_account_id ||= self.root_account_id
Account.invalidate_cache(self.id) if self.id
@should_update_account_associations = self.parent_account_id_changed? || self.root_account_id_changed?
true
end
def update_account_associations_if_changed
send_later_if_production(:update_account_associations) if @should_update_account_associations
end
def equella_settings
endpoint = self.settings[:equella_endpoint] || self.equella_endpoint
if !endpoint.blank?
OpenObject.new({
:endpoint => endpoint,
:default_action => self.settings[:equella_action] || 'selectOrAdd',
:teaser => self.settings[:equella_teaser]
})
else
nil
end
end
def settings
result = self.read_attribute(:settings)
return result if result
return self.write_attribute(:settings, {}) unless frozen?
{}.freeze
end
def domain
HostUrl.context_host(self)
end
def root_account?
!self.root_account_id
end
def sub_accounts_as_options(indent = 0, preloaded_accounts = nil)
unless preloaded_accounts
preloaded_accounts = {}
(self.root_account || self).all_accounts.active.each do |account|
(preloaded_accounts[account.parent_account_id] ||= []) << account
end
end
res = [[("&nbsp;&nbsp;" * indent).html_safe + self.name, self.id]]
if preloaded_accounts[self.id]
preloaded_accounts[self.id].each do |account|
res += account.sub_accounts_as_options(indent + 1, preloaded_accounts)
end
end
res
end
def users_name_like(query="")
@cached_users_name_like ||= {}
@cached_users_name_like[query] ||= self.fast_all_users.name_like(query)
end
def fast_course_base(opts)
columns = "courses.id, courses.name, courses.workflow_state, courses.course_code, courses.sis_source_id"
associated_courses = self.associated_courses.active
associated_courses = associated_courses.with_enrollments if opts[:hide_enrollmentless_courses]
associated_courses = associated_courses.for_term(opts[:term]) if opts[:term].present?
associated_courses = yield associated_courses if block_given?
associated_courses.limit(opts[:limit]).active_first.find(:all, :select => columns, :group => columns)
end
def fast_all_courses(opts={})
@cached_fast_all_courses ||= {}
@cached_fast_all_courses[opts] ||= self.fast_course_base(opts)
end
def all_users(limit=250)
@cached_all_users ||= {}
@cached_all_users[limit] ||= User.of_account(self).scoped(:limit=>limit)
end
def fast_all_users(limit=nil)
@cached_fast_all_users ||= {}
@cached_fast_all_users[limit] ||= self.all_users(limit).active.order_by_sortable_name.scoped(:select => "users.id, users.name")
end
def users_not_in_groups_sql(groups, opts={})
["SELECT u.id, u.name
FROM users u
INNER JOIN user_account_associations uaa on uaa.user_id = u.id
WHERE uaa.account_id = ? AND u.workflow_state != 'deleted'
#{"AND NOT EXISTS (SELECT *
FROM group_memberships gm
WHERE gm.user_id = u.id AND
gm.group_id IN (#{groups.map(&:id).join ','}))" unless groups.empty?}
#{"ORDER BY #{opts[:order_by]}" if opts[:order_by].present?}", self.id]
end
def users_not_in_groups(groups)
User.find_by_sql(users_not_in_groups_sql(groups))
end
def paginate_users_not_in_groups(groups, page, per_page = 15)
User.paginate_by_sql(users_not_in_groups_sql(groups, :order_by => "#{User.sortable_name_order_by_clause('u')} ASC"),
:page => page, :per_page => per_page)
end
def courses_name_like(query="", opts={})
opts[:limit] ||= 200
@cached_courses_name_like ||= {}
@cached_courses_name_like[[query, opts]] ||= self.fast_course_base(opts) {|q| q.name_like(query)}
end
def file_namespace
root = self.root_account || self
"account_#{root.id}"
end
def self.account_lookup_cache_key(id)
['_account_lookup2', id].cache_key
end
def find_user_by_unique_id(unique_id)
self.pseudonyms.find_by_unique_id(unique_id_or_email).user rescue nil
end
def clear_cache_keys!
Rails.cache.delete(self.id)
true
end
def self.invalidate_cache(id)
Rails.cache.delete(account_lookup_cache_key(id)) if id
rescue
nil
end
def quota
Rails.cache.fetch(['current_quota', self].cache_key) do
read_attribute(:storage_quota) ||
(self.parent_account.default_storage_quota rescue nil) ||
Setting.get_cached('account_default_quota', 500.megabytes.to_s).to_i
end
end
def default_storage_quota
read_attribute(:default_storage_quota) ||
(self.parent_account.default_storage_quota rescue nil) ||
Setting.get_cached('account_default_quota', 500.megabytes.to_s).to_i
end
def default_storage_quota_mb
default_storage_quota / 1.megabyte
end
def default_storage_quota_mb=(val)
self.default_storage_quota = val.try(:to_i).try(:megabytes)
end
def default_storage_quota=(val)
val = val.to_f
val = nil if val <= 0
# If the value is the same as the inherited value, then go
# ahead and blank it so it keeps using the inherited value
if parent_account && parent_account.default_storage_quota == val
val = nil
end
write_attribute(:default_storage_quota, val)
end
def has_outcomes?
self.learning_outcomes.count > 0
end
def turnitin_shared_secret=(secret)
return if secret.blank?
self.turnitin_crypted_secret, self.turnitin_salt = Canvas::Security.encrypt_password(secret, 'instructure_turnitin_secret_shared')
end
def turnitin_shared_secret
return nil unless self.turnitin_salt && self.turnitin_crypted_secret
Canvas::Security.decrypt_password(self.turnitin_crypted_secret, self.turnitin_salt, 'instructure_turnitin_secret_shared')
end
def account_chain(opts = {})
res = [self]
account = self
while account.parent_account
account = account.parent_account
res << account
end
res << self.root_account unless res.include?(self.root_account)
res << Account.site_admin if opts[:include_site_admin] && !self.site_admin?
res.compact
end
def account_chain_ids(opts={})
account_chain(opts).map(&:id)
end
memoize :account_chain_ids
def all_page_views
PageView.of_account(self)
end
def membership_for_user(user)
self.account_users.find_by_user_id(user && user.id)
end
def page_views_by_day(*args)
dates = (!args.empty? && args) || [1.year.ago, Time.now ]
PageView.count(
:group => "date(created_at)",
:order => "date(created_at)",
:conditions => {
:account_id => self_and_all_sub_accounts,
:created_at => (dates.first)..(dates.last)
}
)
end
memoize :page_views_by_day
def page_views_by_hour(*args)
dates = (!args.empty? && args) || [1.year.ago, Time.now ]
group = case PageView.connection.adapter_name
when "SQLite"
"strftime('%H', created_at)"
else
"extract(hour from created_at)"
end
PageView.count(
:group => group,
:order => group,
:conditions => {
:account_id => self_and_all_sub_accounts,
:created_at => (dates.first)..(dates.last)
}
)
end
memoize :page_views_by_hour
def page_view_hourly_report(*args)
# if they dont supply a date range then use the first day returned by page_views_by_day (which should be the first day that there is pageview statistics gathered)
hours = []
max = page_views_by_hour(*args).map{|key, val| val}.compact.max
24.times do |hour|
utc_hour = ActiveSupport::TimeWithZone.new(Time.parse("#{hour}:00"), Time.zone).utc.hour
hours << [hour, ((page_views_by_hour(*args)[utc_hour.to_s].to_f / max.to_f * 100.0).to_i rescue 0) ]
end
hours
end
def page_view_data(*args)
# if they dont supply a date range then use the first day returned by page_views_by_day (which should be the first day that there is pageview statistics gathered)
dates = args.empty? ? [page_views_by_day.sort.first.first.to_datetime, Time.now] : args
days = []
dates.first.to_datetime.upto(dates.last) do |d|
# this * 1000 part is because the Highcharts expects something like what Date.UTC(2006, 2, 28) would give you,
# which is MILLISECONDS from the unix epoch, ruby's to_f gives you SECONDS since then.
days << [ (d.at_beginning_of_day.to_f * 1000).to_i , page_views_by_day[d.to_date.to_s].to_i ]
end
days
rescue
return []
end
memoize :page_view_data
def most_popular_courses(options={})
conditions = {
:account_id => self_and_all_sub_accounts
}
if options[:dates]
conditions.merge!({
:created_at => (options[:dates].first)..(options[:dates].last)
})
end
PageView.scoped(
:select => 'count(*) AS page_views_count, context_type, context_id',
:group => "context_type, context_id",
:conditions => conditions,
:order => "page_views_count DESC"
).map do |context|
context.attributes.merge({"page_views_count" => context.page_views_count.to_i}).with_indifferent_access
end
end
memoize :most_popular_courses
def popularity_of(context)
index = most_popular_courses.index( most_popular_courses.detect { |i|
i[:context_type] == context.class.to_s && i[:context_id] == context.id
})
index ?
{ :rank => index, :page_views_count => most_popular_courses[index][:page_views_count] } :
{ :rank => courses.count, :page_views_count => 0 }
end
memoize :popularity_of
def account_membership_types
res = ['AccountAdmin']
res += self.parent_account.account_membership_types if self.parent_account
res += (self.membership_types || "").split(",").select{|t| !t.empty? }
res.uniq
end
def add_account_membership_type(type)
types = account_membership_types
types += type.split(",")
self.membership_types = types.join(',')
self.save
end
def remove_account_membership_type(type)
self.membership_types = self.account_membership_types.select{|t| t != type}.join(',')
self.save
end
def account_authorization_config
# We support multiple auth configs per account, but several places we assume there is only one.
# This is for compatibility with those areas. TODO: migrate everything to supporting multiple
# auth configs
self.account_authorization_configs.first
end
def login_handle_name_is_customized?
self.account_authorization_config && self.account_authorization_config.login_handle_name.present?
end
def login_handle_name
login_handle_name_is_customized? ? self.account_authorization_config.login_handle_name :
(self.delegated_authentication? ? AccountAuthorizationConfig.default_delegated_login_handle_name :
AccountAuthorizationConfig.default_login_handle_name)
end
def self_and_all_sub_accounts
@self_and_all_sub_accounts ||= Account.connection.select_all("SELECT id FROM accounts WHERE accounts.root_account_id = #{self.id} OR accounts.parent_account_id = #{self.id}").map{|ref| ref['id'].to_i}.uniq + [self.id]
end
def default_time_zone
read_attribute(:default_time_zone) || "Mountain Time (US & Canada)"
end
workflow do
state :active
state :deleted
end
def account_users_for(user)
@account_chain_ids ||= self.account_chain(:include_site_admin => true).map { |a| a.active? ? a.id : nil }.compact
@account_users_cache ||= {}
@account_users_cache[user] ||= AccountUser.find(:all, :conditions => { :account_id => @account_chain_ids, :user_id => user.id }) if user
@account_users_cache[user] ||= []
@account_users_cache[user]
end
set_policy do
RoleOverride.permissions.each_key do |permission|
given { |user| self.account_users_for(user).any? { |au| au.has_permission_to?(permission) } }
can permission
end
given { |user| !self.account_users_for(user).empty? }
can :read and can :manage and can :update and can :delete
given { |user|
root_account = self.root_account || self
result = false
site_admin = self.site_admin?
if !site_admin && user && root_account.teachers_can_create_courses?
count = user.enrollments.scoped(:select=>'id', :conditions=>"enrollments.type IN ('TeacherEnrollment', 'DesignerEnrollment') AND (enrollments.workflow_state != 'deleted') AND root_account_id = #{root_account.id}").count
result = true if count > 0
end
if !site_admin && user && !result && root_account.students_can_create_courses?
count = user.enrollments.scoped(:select=>'id', :conditions=>"enrollments.type IN ('StudentEnrollment', 'ObserverEnrollment') AND (enrollments.workflow_state != 'deleted') AND root_account_id = #{root_account.id}").count
result = true if count > 0
end
if !site_admin && user && !result && root_account.no_enrollments_can_create_courses?
count = user.enrollments.scoped(:select=>'id', :conditions=>"enrollments.workflow_state != 'deleted' AND root_account_id = #{root_account.id}").count
result = true if count == 0
end
result
}
can :create_courses
end
alias_method :destroy!, :destroy
def destroy
self.workflow_state = 'deleted'
self.deleted_at = Time.now
save!
end
def self.site_admin_user?(user, permission = :site_admin)
!!(user && Account.site_admin.grants_right?(user, permission))
end
def to_atom
Atom::Entry.new do |entry|
entry.title = self.name
entry.updated = self.updated_at
entry.published = self.created_at
entry.links << Atom::Link.new(:rel => 'alternate',
:href => "/accounts/#{self.id}")
end
end
def default_enrollment_term
return @default_enrollment_term if @default_enrollment_term
unless self.root_account_id
@default_enrollment_term = self.enrollment_terms.active.find_or_create_by_name(EnrollmentTerm::DEFAULT_TERM_NAME)
end
end
def add_user(user, membership_type = nil)
return nil unless user && user.is_a?(User)
membership_type ||= 'AccountAdmin'
au = self.account_users.find_by_user_id_and_membership_type(user.id, membership_type)
au ||= self.account_users.create(:user => user, :membership_type => membership_type)
end
def context_code
raise "DONT USE THIS, use .short_name instead" unless ENV['RAILS_ENV'] == "production"
end
def short_name
name
end
def email_pseudonyms
false
end
def password_authentication?
!!(!self.account_authorization_config || self.account_authorization_config.password_authentication?)
end
def delegated_authentication?
!!(self.account_authorization_config && self.account_authorization_config.delegated_authentication?)
end
def forgot_password_external_url
account_authorization_config.try(:change_password_url)
end
def cas_authentication?
!!(self.account_authorization_config && self.account_authorization_config.cas_authentication?)
end
def ldap_authentication?
!!(self.account_authorization_config && self.account_authorization_config.ldap_authentication?)
end
def saml_authentication?
!!(self.account_authorization_config && self.account_authorization_config.saml_authentication?)
end
def require_account_pseudonym?
false
end
# When a user is invited to a course, do we let them see a preview of the
# course even without registering? This is part of the free-for-teacher
# account perks, since anyone can invite anyone to join any course, and it'd
# be nice to be able to see the course first if you weren't expecting the
# invitation.
def allow_invitation_previews?
self == Account.default
end
def pseudonym_session_scope
self.require_account_pseudonym? ? self.pseudonym_sessions : PseudonymSession
end
def find_courses(string)
self.all_courses.select{|c| c.name.match(string) }
end
def find_users(string)
self.pseudonyms.map{|p| p.user }.select{|u| u.name.match(string) }
end
def self.site_admin
get_special_account('site_admin', 'Site Admin')
end
def self.default
get_special_account('default', 'Default Account')
end
def self.get_special_account(special_account_type, default_account_name)
@special_accounts ||= {}
if Rails.env.test?
# TODO: we have to do this because tests run in transactions. maybe it'd
# be good to create some sort of of memoize_if_safe method, that only
# memoizes when we're caching classes and not in test mode? I dunno. But
# this stinks.
@special_accounts[special_account_type] = Account.find_by_parent_account_id_and_name(nil, default_account_name)
return @special_accounts[special_account_type] ||= Account.create(:parent_account => nil, :name => default_account_name)
end
account = @special_accounts[special_account_type]
return account if account
if (account_id = Setting.get("#{special_account_type}_account_id", nil)) && account_id.present?
account = Account.find_by_id(account_id)
end
return @special_accounts[special_account_type] = account if account
# TODO i18n
t '#account.default_site_administrator_account_name', 'Site Admin'
t '#account.default_account_name', 'Default Account'
account = Account.create!(:name => default_account_name)
Setting.set("#{special_account_type}_account_id", account.id)
return @special_accounts[special_account_type] = account
end
def site_admin?
self == Account.site_admin
end
def display_name
self.name
end
# Updates account associations for all the courses and users associated with this account
def update_account_associations
account_chain_cache = {}
all_user_ids = []
all_user_ids += Course.update_account_associations(self.associated_courses, :skip_user_account_associations => true, :account_chain_cache => account_chain_cache)
# Make sure we have all users with existing account associations.
# (This should catch users with Pseudonyms associated with the account.)
all_user_ids += UserAccountAssociation.scoped(:select => 'user_id', :conditions => { :account_id => self.id }).map(&:user_id)
# Update the users' associations as well
User.update_account_associations(all_user_ids.uniq, :account_chain_cache => account_chain_cache)
end
# this will take an account and make it a sub_account of
# itself. Also updates all it's descendant accounts to point to
# the correct root account, and updates the pseudonyms to
# points to the new root account as well.
def consume_account(account)
account.all_accounts.each do |sub_account|
sub_account.root_account = self.root_account || self
sub_account.save!
end
account.parent_account = self
account.root_account = self.root_account || self
account.save!
account.pseudonyms.each do |pseudonym|
pseudonym.account = self.root_account || self
pseudonym.save!
end
end
def self.root_account_id_for(obj)
res = nil
if obj.respond_to?(:root_account_id)
res = obj.root_account_id
elsif obj.respond_to?(:context)
res = obj.context.root_account_id rescue nil
end
raise "Root account ID is undiscoverable for #{obj.inspect}" unless res
end
def course_count
self.child_courses.not_deleted.count('DISTINCT course_id')
end
memoize :course_count
def sub_account_count
self.sub_accounts.active.count
end
memoize :sub_account_count
def user_count
self.user_account_associations.count
end
memoize :user_count
def current_sis_batch
if (current_sis_batch_id = self.read_attribute(:current_sis_batch_id)) && current_sis_batch_id.present?
self.sis_batches.find_by_id(current_sis_batch_id)
end
end
def turnitin_settings
if self.turnitin_account_id && self.turnitin_shared_secret && !self.turnitin_account_id.empty? && !self.turnitin_shared_secret.empty?
[self.turnitin_account_id, self.turnitin_shared_secret]
else
self.parent_account.turnitin_settings rescue nil
end
end
def closest_turnitin_pledge
if self.turnitin_pledge && !self.turnitin_pledge.empty?
self.turnitin_pledge
else
res = self.account.turnitin_pledge rescue nil
res ||= t('#account.turnitin_pledge', "This assignment submission is my own, original work")
end
end
def closest_turnitin_comments
if self.turnitin_comments && !self.turnitin_comments.empty?
self.turnitin_comments
else
self.parent_account.closest_turnitin_comments rescue nil
end
end
def self_enrollment_allowed?(course)
if !settings[:self_enrollment].blank?
!!(settings[:self_enrollment] == 'any' || (!course.sis_source_id && settings[:self_enrollment] == 'manually_created'))
else
!!(parent_account && parent_account.self_enrollment_allowed?(course))
end
end
TAB_COURSES = 0
TAB_STATISTICS = 1
TAB_PERMISSIONS = 2
TAB_SUB_ACCOUNTS = 3
TAB_TERMS = 4
TAB_AUTHENTICATION = 5
TAB_USERS = 6
TAB_OUTCOMES = 7
TAB_RUBRICS = 8
TAB_SETTINGS = 9
TAB_FACULTY_JOURNAL = 10
TAB_SIS_IMPORT = 11
TAB_GRADING_STANDARDS = 12
TAB_QUESTION_BANKS = 13
def external_tool_tabs(opts)
tools = ContextExternalTool.find_all_for(self, :account_navigation)
tools.sort_by(&:id).map do |tool|
{
:id => tool.asset_string,
:label => tool.label_for(:account_navigation, opts[:language]),
:css_class => tool.asset_string,
:href => :account_external_tool_path,
:external => true,
:args => [self.id, tool.id]
}
end
end
def tabs_available(user=nil, opts={})
manage_settings = user && self.grants_right?(user, nil, :manage_account_settings)
if site_admin?
tabs = []
tabs << { :id => TAB_PERMISSIONS, :label => t('#account.tab_permissions', "Permissions"), :css_class => 'permissions', :href => :account_permissions_path } if user && self.grants_right?(user, nil, :manage_role_overrides)
else
tabs = []
tabs << { :id => TAB_COURSES, :label => t('#account.tab_courses', "Courses"), :css_class => 'courses', :href => :account_path } if user && self.grants_right?(user, nil, :read_course_list)
tabs << { :id => TAB_USERS, :label => t('#account.tab_users', "Users"), :css_class => 'users', :href => :account_users_path } if user && self.grants_right?(user, nil, :read_roster)
tabs << { :id => TAB_STATISTICS, :label => t('#account.tab_statistics', "Statistics"), :css_class => 'statistics', :href => :statistics_account_path } if user && self.grants_right?(user, nil, :view_statistics)
tabs << { :id => TAB_PERMISSIONS, :label => t('#account.tab_permissions', "Permissions"), :css_class => 'permissions', :href => :account_permissions_path } if user && self.grants_right?(user, nil, :manage_role_overrides)
if user && self.grants_right?(user, nil, :manage_outcomes)
tabs << { :id => TAB_OUTCOMES, :label => t('#account.tab_outcomes', "Outcomes"), :css_class => 'outcomes', :href => :account_outcomes_path }
tabs << { :id => TAB_RUBRICS, :label => t('#account.tab_rubrics', "Rubrics"), :css_class => 'rubrics', :href => :account_rubrics_path }
end
tabs << { :id => TAB_GRADING_STANDARDS, :label => t('#account.tab_grading_standards', "Grading Schemes"), :css_class => 'grading_standards', :href => :account_grading_standards_path } if user && self.grants_right?(user, nil, :manage_grades)
tabs << { :id => TAB_QUESTION_BANKS, :label => t('#account.tab_question_banks', "Question Banks"), :css_class => 'question_banks', :href => :account_question_banks_path } if user && self.grants_right?(user, nil, :manage_grades)
tabs << { :id => TAB_SUB_ACCOUNTS, :label => t('#account.tab_sub_accounts', "Sub-Accounts"), :css_class => 'sub_accounts', :href => :account_sub_accounts_path } if manage_settings
tabs << { :id => TAB_FACULTY_JOURNAL, :label => t('#account.tab_faculty_journal', "Faculty Journal"), :css_class => 'faculty_journal', :href => :account_user_notes_path} if self.enable_user_notes && user && self.grants_right?(user, nil, :manage_user_notes)
tabs << { :id => TAB_TERMS, :label => t('#account.tab_terms', "Terms"), :css_class => 'terms', :href => :account_terms_path } if !self.root_account_id && manage_settings
tabs << { :id => TAB_AUTHENTICATION, :label => t('#account.tab_authentication', "Authentication"), :css_class => 'authentication', :href => :account_account_authorization_configs_path } if self.parent_account_id.nil? && manage_settings
tabs << { :id => TAB_SIS_IMPORT, :label => t('#account.tab_sis_import', "SIS Import"), :css_class => 'sis_import', :href => :account_sis_import_path } if self.root_account? && self.allow_sis_import && user && self.grants_right?(user, nil, :manage_sis)
end
tabs += external_tool_tabs(opts)
tabs << { :id => TAB_SETTINGS, :label => t('#account.tab_settings', "Settings"), :css_class => 'settings', :href => :account_settings_path }
tabs
end
def is_a_context?
true
end
def custom_feedback_links
[]
end
def self.allowable_services
{
:google_docs => {
:name => "Google Docs",
:description => "",
:expose_to_ui => !!GoogleDocs.config
},
:google_docs_previews => {
:name => "Google Docs Previews",
:description => "",
:expose_to_ui => true
},
:facebook => {
:name => "Facebook",
:description => "",
:expose_to_ui => !!Facebook.config
},
:skype => {
:name => "Skype",
:description => "",
:expose_to_ui => true
},
:linked_in => {
:name => "LinkedIn",
:description => "",
:expose_to_ui => !!LinkedIn.config
},
:twitter => {
:name => "Twitter",
:description => "",
:expose_to_ui => !!Twitter.config
},
:delicious => {
:name => "Delicious",
:description => "",
:expose_to_ui => true
},
:diigo => {
:name => "Diigo",
:description => "",
:expose_to_ui => true
},
# TODO: move avatars to :settings hash, it makes more sense there
:avatars => {
:name => "User Avatars",
:description => "",
:default => false,
:expose_to_ui => true
}
}.freeze
end
def self.default_allowable_services
self.allowable_services.reject {|s, info| info[:default] == false }
end
def set_service_availability(service, enable)
service = service.to_sym
raise "Invalid Service" unless Account.allowable_services[service]
allowed_service_names = (self.allowed_services || "").split(",").compact
if allowed_service_names.count > 0 and not [ '+', '-' ].member?(allowed_service_names[0][0,1])
# This account has a hard-coded list of services, so handle accordingly
allowed_service_names.reject! { |flag| flag.match("^[+-]?#{service}$") }
allowed_service_names << service if enable
else
allowed_service_names.reject! { |flag| flag.match("^[+-]?#{service}$") }
if enable
# only enable if it is not enabled by default
allowed_service_names << "+#{service}" unless Account.default_allowable_services[service]
else
# only disable if it is not enabled by default
allowed_service_names << "-#{service}" if Account.default_allowable_services[service]
end
end
@allowed_services_hash = nil
self.allowed_services = allowed_service_names.empty? ? nil : allowed_service_names.join(",")
end
def enable_service(service)
set_service_availability(service, true)
end
def disable_service(service)
set_service_availability(service, false)
end
def allowed_services_hash
return @allowed_services_hash if @allowed_services_hash
account_allowed_services = Account.default_allowable_services
if self.allowed_services
allowed_service_names = self.allowed_services.split(",").compact
if allowed_service_names.count > 0
unless [ '+', '-' ].member?(allowed_service_names[0][0,1])
# This account has a hard-coded list of services, so we clear out the defaults
account_allowed_services = { }
end
allowed_service_names.each do |service_switch|
if service_switch =~ /\A([+-]?)(.*)\z/
flag = $1
service_name = $2.to_sym
if flag == '-'
account_allowed_services.delete(service_name)
else
account_allowed_services[service_name] = Account.allowable_services[service_name]
end
end
end
end
end
@allowed_services_hash = account_allowed_services
end
def self.services_exposed_to_ui_hash
self.allowable_services.reject { |key, setting| !setting[:expose_to_ui] }
end
def service_enabled?(service)
service = service.to_sym
case service
when :none
self.allowed_services_hash.empty?
else
self.allowed_services_hash.has_key?(service)
end
end
def self.all_accounts_for(context)
if context.respond_to?(:account)
context.account.account_chain
elsif context.respond_to?(:parent_account)
context.account_chain
else
[]
end
end
def self.serialization_excludes; [:uuid]; end
# This could be much faster if we implement a SQL tree for the account tree
# structure.
def find_child(child_id)
child_id = child_id.to_i
child_ids = self.class.connection.select_values("SELECT id FROM accounts WHERE parent_account_id = #{self.id}").map(&:to_i)
until child_ids.empty?
if child_ids.include?(child_id)
return self.class.find(child_id)
end
child_ids = self.class.connection.select_values("SELECT id FROM accounts WHERE parent_account_id IN (#{child_ids.join(",")})").map(&:to_i)
end
return false
end
def manually_created_courses_account
(self.root_account || self).sub_accounts.find_or_create_by_name(t('#account.manually_created_courses', "Manually-Created Courses"))
end
named_scope :sis_sub_accounts, lambda{|account, *sub_account_source_ids|
{:conditions => {:root_account_id => account.id, :sis_source_id => sub_account_source_ids}, :order => :sis_source_id}
}
named_scope :root_accounts, lambda{
{:conditions => {:root_account_id => nil} }
}
named_scope :needs_parent_account, lambda{|account, limit|
{:conditions => {:parent_account_id => nil, :root_account_id => account.id}, :limit => limit }
}
named_scope :processing_sis_batch, lambda{
{:conditions => ['accounts.current_sis_batch_id IS NOT NULL'], :order => :updated_at}
}
named_scope :name_like, lambda { |name|
{ :conditions => wildcard('accounts.name', name) }
}
named_scope :active, lambda {
{ :conditions => ['accounts.workflow_state != ?', 'deleted'] }
}
named_scope :limit, lambda {|limit|
{:limit => limit}
}
end