snuffle 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/CODE_OF_CONDUCT.md +12 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +45 -0
- data/Rakefile +2 -0
- data/bin/snuffle +3 -0
- data/lib/snuffle.rb +23 -0
- data/lib/snuffle/cli.rb +74 -0
- data/lib/snuffle/cohort.rb +42 -0
- data/lib/snuffle/elements/hash.rb +37 -0
- data/lib/snuffle/formatters/base.rb +57 -0
- data/lib/snuffle/formatters/csv.rb +31 -0
- data/lib/snuffle/formatters/html.rb +48 -0
- data/lib/snuffle/formatters/html_index.rb +39 -0
- data/lib/snuffle/formatters/templates/index.html.haml +71 -0
- data/lib/snuffle/formatters/templates/output.html.haml +56 -0
- data/lib/snuffle/formatters/text.rb +30 -0
- data/lib/snuffle/line_of_code.rb +23 -0
- data/lib/snuffle/node.rb +46 -0
- data/lib/snuffle/source_file.rb +112 -0
- data/lib/snuffle/summary.rb +13 -0
- data/lib/snuffle/util/histogram.rb +17 -0
- data/lib/snuffle/version.rb +3 -0
- data/snuffle.gemspec +35 -0
- data/spec/fixtures/account.rb +1390 -0
- data/spec/fixtures/program_1.rb +31 -0
- data/spec/fixtures/program_2.rb +67 -0
- data/spec/fixtures/program_3.rb +68 -0
- data/spec/snuffle/elements/hash_spec.rb +16 -0
- data/spec/snuffle/line_of_code_spec.rb +38 -0
- data/spec/snuffle/source_file_spec.rb +50 -0
- data/spec/snuffle/util/histogram_spec.rb +25 -0
- data/spec/spec_helper.rb +7 -0
- data/test.rb +5 -0
- metadata +260 -0
@@ -0,0 +1,1390 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2011 - 2013 Instructure, Inc.
|
3
|
+
#
|
4
|
+
# This file is part of Canvas.
|
5
|
+
#
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
8
|
+
# Software Foundation, version 3 of the License.
|
9
|
+
#
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
13
|
+
# details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
#
|
18
|
+
|
19
|
+
class Account < ActiveRecord::Base
|
20
|
+
include Context
|
21
|
+
attr_accessible :name, :turnitin_account_id, :turnitin_shared_secret,
|
22
|
+
:turnitin_host, :turnitin_comments, :turnitin_pledge,
|
23
|
+
:default_time_zone, :parent_account, :settings, :default_storage_quota,
|
24
|
+
:default_storage_quota_mb, :storage_quota, :ip_filters, :default_locale,
|
25
|
+
:default_user_storage_quota_mb, :default_group_storage_quota_mb, :integration_id
|
26
|
+
|
27
|
+
EXPORTABLE_ATTRIBUTES = [:id, :name, :created_at, :updated_at, :workflow_state, :deleted_at,
|
28
|
+
:membership_types, :default_time_zone, :external_status, :storage_quota,
|
29
|
+
:enable_user_notes, :allowed_services, :turnitin_pledge, :turnitin_comments,
|
30
|
+
:turnitin_account_id, :allow_sis_import, :sis_source_id, :equella_endpoint,
|
31
|
+
:settings, :uuid, :default_locale, :default_user_storage_quota, :turnitin_host,
|
32
|
+
:created_by_id, :lti_guid, :default_group_storage_quota, :lti_context_id
|
33
|
+
]
|
34
|
+
|
35
|
+
EXPORTABLE_ASSOCIATIONS = [
|
36
|
+
:courses, :group_categories, :groups, :enrollment_terms, :enrollments, :account_users, :course_sections,
|
37
|
+
:pseudonyms, :attachments, :folders, :active_assignments, :grading_standards, :assessment_question_banks,
|
38
|
+
:roles, :announcements, :alerts, :course_account_associations, :user_account_associations
|
39
|
+
]
|
40
|
+
|
41
|
+
INSTANCE_GUID_SUFFIX = 'canvas-lms'
|
42
|
+
|
43
|
+
include Workflow
|
44
|
+
belongs_to :parent_account, :class_name => 'Account'
|
45
|
+
belongs_to :root_account, :class_name => 'Account'
|
46
|
+
authenticates_many :pseudonym_sessions
|
47
|
+
has_many :courses
|
48
|
+
has_many :all_courses, :class_name => 'Course', :foreign_key => 'root_account_id'
|
49
|
+
has_many :group_categories, :as => :context, :conditions => ['deleted_at IS NULL']
|
50
|
+
has_many :all_group_categories, :class_name => 'GroupCategory', :as => :context
|
51
|
+
has_many :groups, :as => :context
|
52
|
+
has_many :all_groups, :class_name => 'Group', :foreign_key => 'root_account_id'
|
53
|
+
has_many :enrollment_terms, :foreign_key => 'root_account_id'
|
54
|
+
has_many :enrollments, :foreign_key => 'root_account_id', :conditions => ["enrollments.type != 'StudentViewEnrollment'"]
|
55
|
+
has_many :sub_accounts, :class_name => 'Account', :foreign_key => 'parent_account_id', :conditions => ['workflow_state != ?', 'deleted']
|
56
|
+
has_many :all_accounts, :class_name => 'Account', :foreign_key => 'root_account_id', :order => 'name'
|
57
|
+
has_many :account_users, :dependent => :destroy
|
58
|
+
has_many :course_sections, :foreign_key => 'root_account_id'
|
59
|
+
has_many :sis_batches
|
60
|
+
has_many :abstract_courses, :class_name => 'AbstractCourse', :foreign_key => 'account_id'
|
61
|
+
has_many :root_abstract_courses, :class_name => 'AbstractCourse', :foreign_key => 'root_account_id'
|
62
|
+
has_many :users, :through => :account_users
|
63
|
+
has_many :pseudonyms, :include => :user
|
64
|
+
has_many :role_overrides, :as => :context
|
65
|
+
has_many :course_account_associations
|
66
|
+
has_many :child_courses, :through => :course_account_associations, :source => :course, :conditions => ['course_account_associations.depth = 0']
|
67
|
+
has_many :attachments, :as => :context, :dependent => :destroy
|
68
|
+
has_many :active_assignments, :as => :context, :class_name => 'Assignment', :conditions => ['assignments.workflow_state != ?', 'deleted']
|
69
|
+
has_many :folders, :as => :context, :dependent => :destroy, :order => 'folders.name'
|
70
|
+
has_many :active_folders, :class_name => 'Folder', :as => :context, :conditions => ['folders.workflow_state != ?', 'deleted'], :order => 'folders.name'
|
71
|
+
has_many :active_folders_with_sub_folders, :class_name => 'Folder', :as => :context, :include => [:active_sub_folders], :conditions => ['folders.workflow_state != ?', 'deleted'], :order => 'folders.name'
|
72
|
+
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'
|
73
|
+
has_many :account_authorization_configs, :order => "position"
|
74
|
+
has_many :account_reports
|
75
|
+
has_many :grading_standards, :as => :context, :conditions => ['workflow_state != ?', 'deleted']
|
76
|
+
has_many :assessment_questions, :through => :assessment_question_banks
|
77
|
+
has_many :assessment_question_banks, :as => :context, :include => [:assessment_questions, :assessment_question_bank_users]
|
78
|
+
has_many :roles
|
79
|
+
has_many :all_roles, :class_name => 'Role', :foreign_key => 'root_account_id'
|
80
|
+
has_many :progresses, :as => :context
|
81
|
+
has_many :content_migrations, :as => :context
|
82
|
+
|
83
|
+
def inherited_assessment_question_banks(include_self = false, *additional_contexts)
|
84
|
+
sql = []
|
85
|
+
conds = []
|
86
|
+
contexts = additional_contexts + account_chain
|
87
|
+
contexts.delete(self) unless include_self
|
88
|
+
contexts.each { |c|
|
89
|
+
sql << "context_type = ? AND context_id = ?"
|
90
|
+
conds += [c.class.to_s, c.id]
|
91
|
+
}
|
92
|
+
conds.unshift(sql.join(" OR "))
|
93
|
+
AssessmentQuestionBank.where(conds)
|
94
|
+
end
|
95
|
+
|
96
|
+
include LearningOutcomeContext
|
97
|
+
include RubricContext
|
98
|
+
|
99
|
+
has_many :context_external_tools, :as => :context, :dependent => :destroy, :order => 'name'
|
100
|
+
has_many :error_reports
|
101
|
+
has_many :announcements, :class_name => 'AccountNotification'
|
102
|
+
has_many :alerts, :as => :context, :include => :criteria
|
103
|
+
has_many :user_account_associations
|
104
|
+
has_many :report_snapshots
|
105
|
+
|
106
|
+
before_validation :verify_unique_sis_source_id
|
107
|
+
before_save :ensure_defaults
|
108
|
+
before_save :set_update_account_associations_if_changed
|
109
|
+
after_save :update_account_associations_if_changed
|
110
|
+
after_create :default_enrollment_term
|
111
|
+
|
112
|
+
serialize :settings, Hash
|
113
|
+
include TimeZoneHelper
|
114
|
+
|
115
|
+
time_zone_attribute :default_time_zone, default: "America/Denver"
|
116
|
+
def default_time_zone_with_root_account
|
117
|
+
if read_attribute(:default_time_zone) || root_account?
|
118
|
+
default_time_zone_without_root_account
|
119
|
+
else
|
120
|
+
root_account.default_time_zone
|
121
|
+
end
|
122
|
+
end
|
123
|
+
alias_method_chain :default_time_zone, :root_account
|
124
|
+
alias_method :time_zone, :default_time_zone
|
125
|
+
|
126
|
+
validates_locale :default_locale, :allow_nil => true
|
127
|
+
validates_length_of :name, :maximum => maximum_string_length, :allow_blank => true
|
128
|
+
validate :account_chain_loop, :if => :parent_account_id_changed?
|
129
|
+
validate :validate_auth_discovery_url
|
130
|
+
validates_presence_of :workflow_state
|
131
|
+
|
132
|
+
include StickySisFields
|
133
|
+
are_sis_sticky :name
|
134
|
+
|
135
|
+
include FeatureFlags
|
136
|
+
|
137
|
+
def default_locale(recurse = false)
|
138
|
+
read_attribute(:default_locale) ||
|
139
|
+
(recurse && parent_account ? parent_account.default_locale(true) : nil)
|
140
|
+
end
|
141
|
+
|
142
|
+
cattr_accessor :account_settings_options
|
143
|
+
self.account_settings_options = {}
|
144
|
+
|
145
|
+
def self.add_setting(setting, opts=nil)
|
146
|
+
self.account_settings_options[setting.to_sym] = opts || {}
|
147
|
+
if (opts && opts[:boolean] && opts.has_key?(:default))
|
148
|
+
if opts[:default]
|
149
|
+
# if the default is true, we want a nil result to evaluate to true.
|
150
|
+
# this prevents us from having to backfill true values into a
|
151
|
+
# serialized column, which would be expensive.
|
152
|
+
self.class_eval "def #{setting}?; settings[:#{setting}] != false; end"
|
153
|
+
else
|
154
|
+
# if the default is not true, we can fall back to a straight boolean.
|
155
|
+
self.class_eval "def #{setting}?; !!settings[:#{setting}]; end"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# these settings either are or could be easily added to
|
161
|
+
# the account settings page
|
162
|
+
add_setting :global_includes, :root_only => true, :boolean => true, :default => false
|
163
|
+
add_setting :global_javascript, :condition => :allow_global_includes
|
164
|
+
add_setting :global_stylesheet, :condition => :allow_global_includes
|
165
|
+
add_setting :sub_account_includes, :condition => :allow_global_includes, :boolean => true, :default => false
|
166
|
+
add_setting :error_reporting, :hash => true, :values => [:action, :email, :url, :subject_param, :body_param], :root_only => true
|
167
|
+
add_setting :custom_help_links, :root_only => true
|
168
|
+
add_setting :prevent_course_renaming_by_teachers, :boolean => true, :root_only => true
|
169
|
+
add_setting :restrict_student_future_view, :boolean => true, :root_only => true, :default => false
|
170
|
+
add_setting :teachers_can_create_courses, :boolean => true, :root_only => true, :default => false
|
171
|
+
add_setting :students_can_create_courses, :boolean => true, :root_only => true, :default => false
|
172
|
+
add_setting :restrict_quiz_questions, :boolean => true, :root_only => true, :default => false
|
173
|
+
add_setting :no_enrollments_can_create_courses, :boolean => true, :root_only => true, :default => false
|
174
|
+
add_setting :allow_sending_scores_in_emails, :boolean => true, :root_only => true
|
175
|
+
add_setting :support_url, :root_only => true
|
176
|
+
add_setting :self_enrollment
|
177
|
+
add_setting :equella_endpoint
|
178
|
+
add_setting :equella_teaser
|
179
|
+
add_setting :enable_alerts, :boolean => true, :root_only => true
|
180
|
+
add_setting :enable_eportfolios, :boolean => true, :root_only => true
|
181
|
+
add_setting :users_can_edit_name, :boolean => true, :root_only => true
|
182
|
+
add_setting :open_registration, :boolean => true, :root_only => true
|
183
|
+
add_setting :show_scheduler, :boolean => true, :root_only => true, :default => false
|
184
|
+
add_setting :enable_profiles, :boolean => true, :root_only => true, :default => false
|
185
|
+
add_setting :enable_manage_groups2, :boolean => true, :root_only => true, :default => true
|
186
|
+
add_setting :mfa_settings, :root_only => true
|
187
|
+
add_setting :canvas_authentication, :boolean => true, :root_only => true
|
188
|
+
add_setting :admins_can_change_passwords, :boolean => true, :root_only => true, :default => false
|
189
|
+
add_setting :admins_can_view_notifications, :boolean => true, :root_only => true, :default => false
|
190
|
+
add_setting :outgoing_email_default_name
|
191
|
+
add_setting :external_notification_warning, :boolean => true, :default => false
|
192
|
+
# Terms of Use and Privacy Policy settings for the root account
|
193
|
+
add_setting :terms_changed_at, :root_only => true
|
194
|
+
# When a user is invited to a course, do we let them see a preview of the
|
195
|
+
# course even without registering? This is part of the free-for-teacher
|
196
|
+
# account perks, since anyone can invite anyone to join any course, and it'd
|
197
|
+
# be nice to be able to see the course first if you weren't expecting the
|
198
|
+
# invitation.
|
199
|
+
add_setting :allow_invitation_previews, :boolean => true, :root_only => true, :default => false
|
200
|
+
add_setting :self_registration, :boolean => true, :root_only => true, :default => false
|
201
|
+
# if self_registration_type is 'observer', then only observers (i.e. parents) can self register.
|
202
|
+
# if self_registration_type is 'all' or nil, any user type can self register.
|
203
|
+
add_setting :self_registration_type, :root_only => true
|
204
|
+
add_setting :large_course_rosters, :boolean => true, :root_only => true, :default => false
|
205
|
+
add_setting :edit_institution_email, :boolean => true, :root_only => true, :default => true
|
206
|
+
add_setting :js_kaltura_uploader, :boolean => true, :root_only => true, :default => false
|
207
|
+
add_setting :google_docs_domain, root_only: true
|
208
|
+
add_setting :dashboard_url, root_only: true
|
209
|
+
add_setting :product_name, root_only: true
|
210
|
+
|
211
|
+
def settings=(hash)
|
212
|
+
if hash.is_a?(Hash)
|
213
|
+
hash.each do |key, val|
|
214
|
+
if account_settings_options && account_settings_options[key.to_sym]
|
215
|
+
opts = account_settings_options[key.to_sym]
|
216
|
+
if (opts[:root_only] && !self.root_account?) || (opts[:condition] && !self.send("#{opts[:condition]}?".to_sym))
|
217
|
+
settings.delete key.to_sym
|
218
|
+
elsif opts[:boolean]
|
219
|
+
settings[key.to_sym] = (val == true || val == 'true' || val == '1' || val == 'on')
|
220
|
+
elsif opts[:hash]
|
221
|
+
new_hash = {}
|
222
|
+
if val.is_a?(Hash)
|
223
|
+
val.each do |inner_key, inner_val|
|
224
|
+
if opts[:values].include?(inner_key.to_sym)
|
225
|
+
new_hash[inner_key.to_sym] = inner_val.to_s
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
settings[key.to_sym] = new_hash.empty? ? nil : new_hash
|
230
|
+
else
|
231
|
+
settings[key.to_sym] = val.to_s
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
settings
|
237
|
+
end
|
238
|
+
|
239
|
+
def product_name
|
240
|
+
settings[:product_name] || t("#product_name", "Canvas")
|
241
|
+
end
|
242
|
+
|
243
|
+
def allow_global_includes?
|
244
|
+
self.global_includes? || self.parent_account.try(:sub_account_includes?)
|
245
|
+
end
|
246
|
+
|
247
|
+
def global_includes_hash
|
248
|
+
includes = {}
|
249
|
+
if allow_global_includes?
|
250
|
+
includes = {}
|
251
|
+
includes[:js] = settings[:global_javascript] if settings[:global_javascript].present?
|
252
|
+
includes[:css] = settings[:global_stylesheet] if settings[:global_stylesheet].present?
|
253
|
+
end
|
254
|
+
includes.present? ? includes : nil
|
255
|
+
end
|
256
|
+
|
257
|
+
def mfa_settings
|
258
|
+
settings[:mfa_settings].try(:to_sym) || :disabled
|
259
|
+
end
|
260
|
+
|
261
|
+
def canvas_authentication?
|
262
|
+
settings[:canvas_authentication] != false || !self.account_authorization_config
|
263
|
+
end
|
264
|
+
|
265
|
+
def open_registration?
|
266
|
+
!!settings[:open_registration] && canvas_authentication?
|
267
|
+
end
|
268
|
+
|
269
|
+
def self_registration?
|
270
|
+
!!settings[:self_registration] && canvas_authentication?
|
271
|
+
end
|
272
|
+
|
273
|
+
def self_registration_type
|
274
|
+
settings[:self_registration_type]
|
275
|
+
end
|
276
|
+
|
277
|
+
def self_registration_allowed_for?(type)
|
278
|
+
return false unless self_registration?
|
279
|
+
return false if self_registration_type && self_registration_type != 'all' && type != self_registration_type
|
280
|
+
true
|
281
|
+
end
|
282
|
+
|
283
|
+
def terms_of_use_url
|
284
|
+
Setting.get('terms_of_use_url', 'http://www.canvaslms.com/policies/terms-of-use')
|
285
|
+
end
|
286
|
+
|
287
|
+
def privacy_policy_url
|
288
|
+
Setting.get('privacy_policy_url', 'http://www.canvaslms.com/policies/privacy-policy')
|
289
|
+
end
|
290
|
+
|
291
|
+
def terms_required?
|
292
|
+
Setting.get('terms_required', 'true') == 'true'
|
293
|
+
end
|
294
|
+
|
295
|
+
def require_acceptance_of_terms?(user)
|
296
|
+
return false if !terms_required?
|
297
|
+
return true if user.nil? || user.new_record?
|
298
|
+
terms_changed_at = settings[:terms_changed_at]
|
299
|
+
last_accepted = user.preferences[:accepted_terms]
|
300
|
+
return false if terms_changed_at.nil? && user.registered? # make sure existing users are grandfathered in
|
301
|
+
return false if last_accepted && (terms_changed_at.nil? || last_accepted > terms_changed_at)
|
302
|
+
true
|
303
|
+
end
|
304
|
+
|
305
|
+
def ip_filters=(params)
|
306
|
+
filters = {}
|
307
|
+
require 'ipaddr'
|
308
|
+
params.each do |key, str|
|
309
|
+
ips = []
|
310
|
+
vals = str.split(/,/)
|
311
|
+
vals.each do |val|
|
312
|
+
ip = IPAddr.new(val) rescue nil
|
313
|
+
# right now the ip_filter column on quizzes is just a string,
|
314
|
+
# so it has a max length. I figure whatever we set it to this
|
315
|
+
# setter should at the very least limit stored values to that
|
316
|
+
# length.
|
317
|
+
ips << val if ip && val.length <= 255
|
318
|
+
end
|
319
|
+
filters[key] = ips.join(',') unless ips.empty?
|
320
|
+
end
|
321
|
+
settings[:ip_filters] = filters
|
322
|
+
end
|
323
|
+
|
324
|
+
def ensure_defaults
|
325
|
+
self.uuid ||= CanvasSlug.generate_securish_uuid
|
326
|
+
self.lti_guid ||= "#{self.uuid}:#{INSTANCE_GUID_SUFFIX}" if self.respond_to?(:lti_guid)
|
327
|
+
end
|
328
|
+
|
329
|
+
def verify_unique_sis_source_id
|
330
|
+
return true unless self.sis_source_id
|
331
|
+
if self.root_account?
|
332
|
+
self.errors.add(:sis_source_id, t('#account.root_account_cant_have_sis_id', "SIS IDs cannot be set on root accounts"))
|
333
|
+
return false
|
334
|
+
end
|
335
|
+
|
336
|
+
root = self.root_account
|
337
|
+
existing_account = Account.find_by_root_account_id_and_sis_source_id(root.id, self.sis_source_id)
|
338
|
+
|
339
|
+
return true if !existing_account || existing_account.id == self.id
|
340
|
+
|
341
|
+
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))
|
342
|
+
false
|
343
|
+
end
|
344
|
+
|
345
|
+
def set_update_account_associations_if_changed
|
346
|
+
self.root_account_id ||= self.parent_account.root_account_id if self.parent_account
|
347
|
+
self.root_account_id ||= self.parent_account_id
|
348
|
+
self.parent_account_id ||= self.root_account_id
|
349
|
+
Account.invalidate_cache(self.id) if self.id
|
350
|
+
@should_update_account_associations = self.parent_account_id_changed? || self.root_account_id_changed?
|
351
|
+
true
|
352
|
+
end
|
353
|
+
|
354
|
+
def update_account_associations_if_changed
|
355
|
+
send_later_if_production(:update_account_associations) if @should_update_account_associations
|
356
|
+
end
|
357
|
+
|
358
|
+
def equella_settings
|
359
|
+
endpoint = self.settings[:equella_endpoint] || self.equella_endpoint
|
360
|
+
if !endpoint.blank?
|
361
|
+
OpenObject.new({
|
362
|
+
:endpoint => endpoint,
|
363
|
+
:default_action => self.settings[:equella_action] || 'selectOrAdd',
|
364
|
+
:teaser => self.settings[:equella_teaser]
|
365
|
+
})
|
366
|
+
else
|
367
|
+
nil
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
def settings
|
372
|
+
result = self.read_attribute(:settings)
|
373
|
+
return result if result
|
374
|
+
return write_attribute(:settings, {}) unless frozen?
|
375
|
+
{}.freeze
|
376
|
+
end
|
377
|
+
|
378
|
+
def domain
|
379
|
+
HostUrl.context_host(self)
|
380
|
+
end
|
381
|
+
|
382
|
+
def self.find_by_domain(domain)
|
383
|
+
self.default if HostUrl.default_host == domain
|
384
|
+
end
|
385
|
+
|
386
|
+
def root_account?
|
387
|
+
!self.root_account_id
|
388
|
+
end
|
389
|
+
|
390
|
+
def root_account_with_self
|
391
|
+
return self if self.root_account?
|
392
|
+
root_account_without_self
|
393
|
+
end
|
394
|
+
alias_method_chain :root_account, :self
|
395
|
+
|
396
|
+
def sub_accounts_as_options(indent = 0, preloaded_accounts = nil)
|
397
|
+
unless preloaded_accounts
|
398
|
+
preloaded_accounts = {}
|
399
|
+
self.root_account.all_accounts.active.each do |account|
|
400
|
+
(preloaded_accounts[account.parent_account_id] ||= []) << account
|
401
|
+
end
|
402
|
+
end
|
403
|
+
res = [[(" " * indent).html_safe + self.name, self.id]]
|
404
|
+
if preloaded_accounts[self.id]
|
405
|
+
preloaded_accounts[self.id].each do |account|
|
406
|
+
res += account.sub_accounts_as_options(indent + 1, preloaded_accounts)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
res
|
410
|
+
end
|
411
|
+
|
412
|
+
def users_visible_to(user)
|
413
|
+
self.grants_right?(user, :read) ? self.all_users : self.all_users.none
|
414
|
+
end
|
415
|
+
|
416
|
+
def users_name_like(query="")
|
417
|
+
@cached_users_name_like ||= {}
|
418
|
+
@cached_users_name_like[query] ||= self.fast_all_users.name_like(query)
|
419
|
+
end
|
420
|
+
|
421
|
+
def associated_courses
|
422
|
+
scope = if CANVAS_RAILS2
|
423
|
+
Course.shard(shard)
|
424
|
+
else
|
425
|
+
shard.activate do
|
426
|
+
Course.scoped
|
427
|
+
end
|
428
|
+
end
|
429
|
+
scope.where("EXISTS (SELECT 1 FROM course_account_associations WHERE course_id=courses.id AND account_id=?)", self)
|
430
|
+
end
|
431
|
+
|
432
|
+
def associated_user?(user)
|
433
|
+
user_account_associations.where(user_id: user).exists?
|
434
|
+
end
|
435
|
+
|
436
|
+
def fast_course_base(opts)
|
437
|
+
columns = "courses.id, courses.name, courses.workflow_state, courses.course_code, courses.sis_source_id, courses.enrollment_term_id"
|
438
|
+
associated_courses = self.associated_courses.active
|
439
|
+
associated_courses = associated_courses.with_enrollments if opts[:hide_enrollmentless_courses]
|
440
|
+
associated_courses = associated_courses.for_term(opts[:term]) if opts[:term].present?
|
441
|
+
associated_courses = yield associated_courses if block_given?
|
442
|
+
associated_courses.limit(opts[:limit]).active_first.select(columns).all
|
443
|
+
end
|
444
|
+
|
445
|
+
def fast_all_courses(opts={})
|
446
|
+
@cached_fast_all_courses ||= {}
|
447
|
+
@cached_fast_all_courses[opts] ||= self.fast_course_base(opts)
|
448
|
+
end
|
449
|
+
|
450
|
+
def all_users(limit=250)
|
451
|
+
@cached_all_users ||= {}
|
452
|
+
@cached_all_users[limit] ||= User.of_account(self).limit(limit)
|
453
|
+
end
|
454
|
+
|
455
|
+
def fast_all_users(limit=nil)
|
456
|
+
@cached_fast_all_users ||= {}
|
457
|
+
@cached_fast_all_users[limit] ||= self.all_users(limit).active.select("users.id, users.name, users.sortable_name").order_by_sortable_name
|
458
|
+
end
|
459
|
+
|
460
|
+
def users_not_in_groups(groups, opts={})
|
461
|
+
scope = User.active.joins(:user_account_associations).
|
462
|
+
where(user_account_associations: {account_id: self}).
|
463
|
+
where(Group.not_in_group_sql_fragment(groups.map(&:id))).
|
464
|
+
select("users.id, users.name")
|
465
|
+
scope = scope.select(opts[:order]).order(opts[:order]) if opts[:order]
|
466
|
+
scope
|
467
|
+
end
|
468
|
+
|
469
|
+
def courses_name_like(query="", opts={})
|
470
|
+
opts[:limit] ||= 200
|
471
|
+
@cached_courses_name_like ||= {}
|
472
|
+
@cached_courses_name_like[[query, opts]] ||= self.fast_course_base(opts) {|q| q.name_like(query)}
|
473
|
+
end
|
474
|
+
|
475
|
+
def self_enrollment_course_for(code)
|
476
|
+
all_courses.
|
477
|
+
where(:self_enrollment_code => code).
|
478
|
+
first
|
479
|
+
end
|
480
|
+
|
481
|
+
def file_namespace
|
482
|
+
Shard.birth.activate { "account_#{self.root_account.id}" }
|
483
|
+
end
|
484
|
+
|
485
|
+
def self.account_lookup_cache_key(id)
|
486
|
+
['_account_lookup2', id].cache_key
|
487
|
+
end
|
488
|
+
|
489
|
+
def self.invalidate_cache(id)
|
490
|
+
Rails.cache.delete(account_lookup_cache_key(id)) if id
|
491
|
+
rescue
|
492
|
+
nil
|
493
|
+
end
|
494
|
+
|
495
|
+
def quota
|
496
|
+
Rails.cache.fetch(['current_quota', self].cache_key) do
|
497
|
+
read_attribute(:storage_quota) ||
|
498
|
+
(self.parent_account.default_storage_quota rescue nil) ||
|
499
|
+
Setting.get('account_default_quota', 500.megabytes.to_s).to_i
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def default_storage_quota
|
504
|
+
read_attribute(:default_storage_quota) ||
|
505
|
+
(self.parent_account.default_storage_quota rescue nil) ||
|
506
|
+
Setting.get('account_default_quota', 500.megabytes.to_s).to_i
|
507
|
+
end
|
508
|
+
|
509
|
+
def default_storage_quota_mb
|
510
|
+
default_storage_quota / 1.megabyte
|
511
|
+
end
|
512
|
+
|
513
|
+
def default_storage_quota_mb=(val)
|
514
|
+
self.default_storage_quota = val.try(:to_i).try(:megabytes)
|
515
|
+
end
|
516
|
+
|
517
|
+
def default_storage_quota=(val)
|
518
|
+
val = val.to_f
|
519
|
+
val = nil if val <= 0
|
520
|
+
# If the value is the same as the inherited value, then go
|
521
|
+
# ahead and blank it so it keeps using the inherited value
|
522
|
+
if parent_account && parent_account.default_storage_quota == val
|
523
|
+
val = nil
|
524
|
+
end
|
525
|
+
write_attribute(:default_storage_quota, val)
|
526
|
+
end
|
527
|
+
|
528
|
+
def default_user_storage_quota
|
529
|
+
read_attribute(:default_user_storage_quota) ||
|
530
|
+
User.default_storage_quota
|
531
|
+
end
|
532
|
+
|
533
|
+
def default_user_storage_quota=(val)
|
534
|
+
val = val.to_i
|
535
|
+
val = nil if val == User.default_storage_quota || val <= 0
|
536
|
+
write_attribute(:default_user_storage_quota, val)
|
537
|
+
end
|
538
|
+
|
539
|
+
def default_user_storage_quota_mb
|
540
|
+
default_user_storage_quota / 1.megabyte
|
541
|
+
end
|
542
|
+
|
543
|
+
def default_user_storage_quota_mb=(val)
|
544
|
+
self.default_user_storage_quota = val.try(:to_i).try(:megabytes)
|
545
|
+
end
|
546
|
+
|
547
|
+
def default_group_storage_quota
|
548
|
+
read_attribute(:default_group_storage_quota) ||
|
549
|
+
Group.default_storage_quota
|
550
|
+
end
|
551
|
+
|
552
|
+
def default_group_storage_quota=(val)
|
553
|
+
val = val.to_i
|
554
|
+
val = nil if val == Group.default_storage_quota || val <= 0
|
555
|
+
write_attribute(:default_group_storage_quota, val)
|
556
|
+
end
|
557
|
+
|
558
|
+
def default_group_storage_quota_mb
|
559
|
+
default_group_storage_quota / 1.megabyte
|
560
|
+
end
|
561
|
+
|
562
|
+
def default_group_storage_quota_mb=(val)
|
563
|
+
self.default_group_storage_quota = val.try(:to_i).try(:megabytes)
|
564
|
+
end
|
565
|
+
|
566
|
+
def turnitin_shared_secret=(secret)
|
567
|
+
return if secret.blank?
|
568
|
+
self.turnitin_crypted_secret, self.turnitin_salt = Canvas::Security.encrypt_password(secret, 'instructure_turnitin_secret_shared')
|
569
|
+
end
|
570
|
+
|
571
|
+
def turnitin_shared_secret
|
572
|
+
return nil unless self.turnitin_salt && self.turnitin_crypted_secret
|
573
|
+
Canvas::Security.decrypt_password(self.turnitin_crypted_secret, self.turnitin_salt, 'instructure_turnitin_secret_shared')
|
574
|
+
end
|
575
|
+
|
576
|
+
def account_chain(opts = {})
|
577
|
+
unless @account_chain
|
578
|
+
res = [self]
|
579
|
+
if ActiveRecord::Base.configurations[Rails.env]['adapter'] == 'postgresql'
|
580
|
+
self.shard.activate do
|
581
|
+
res.concat Account.find_by_sql(<<-SQL) if self.parent_account_id
|
582
|
+
WITH RECURSIVE t AS (
|
583
|
+
SELECT * FROM accounts WHERE id=#{self.parent_account_id}
|
584
|
+
UNION
|
585
|
+
SELECT accounts.* FROM accounts INNER JOIN t ON accounts.id=t.parent_account_id
|
586
|
+
)
|
587
|
+
SELECT * FROM t
|
588
|
+
SQL
|
589
|
+
end
|
590
|
+
else
|
591
|
+
account = self
|
592
|
+
while account.parent_account
|
593
|
+
account = account.parent_account
|
594
|
+
res << account
|
595
|
+
end
|
596
|
+
end
|
597
|
+
res << self.root_account unless res.map(&:id).include?(self.root_account_id)
|
598
|
+
@account_chain = res.compact
|
599
|
+
end
|
600
|
+
results = @account_chain.dup
|
601
|
+
results << Account.site_admin if opts[:include_site_admin] && !self.site_admin?
|
602
|
+
results
|
603
|
+
end
|
604
|
+
|
605
|
+
def account_chain_loop
|
606
|
+
# this record hasn't been saved to the db yet, so if the the chain includes
|
607
|
+
# this account, it won't point to the new parent yet, and should still be
|
608
|
+
# valid
|
609
|
+
if self.parent_account.account_chain_ids.include?(self.id)
|
610
|
+
errors.add(:parent_account_id,
|
611
|
+
"Setting account #{self.sis_source_id || self.id}'s parent to #{self.parent_account.sis_source_id || self.parent_account_id} would create a loop")
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
# returns all sub_accounts recursively as far down as they go, in id order
|
616
|
+
# because this uses a custom sql query for postgresql, we can't use a normal
|
617
|
+
# named scope, so we pass the limit and offset into the method instead and
|
618
|
+
# build our own query string
|
619
|
+
def sub_accounts_recursive(limit, offset)
|
620
|
+
if ActiveRecord::Base.configurations[Rails.env]['adapter'] == 'postgresql'
|
621
|
+
Account.find_by_sql([<<-SQL, self.id, limit.to_i, offset.to_i])
|
622
|
+
WITH RECURSIVE t AS (
|
623
|
+
SELECT * FROM accounts
|
624
|
+
WHERE parent_account_id = ? AND workflow_state <>'deleted'
|
625
|
+
UNION
|
626
|
+
SELECT accounts.* FROM accounts
|
627
|
+
INNER JOIN t ON accounts.parent_account_id = t.id
|
628
|
+
WHERE accounts.workflow_state <>'deleted'
|
629
|
+
)
|
630
|
+
SELECT * FROM t ORDER BY parent_account_id, id LIMIT ? OFFSET ?
|
631
|
+
SQL
|
632
|
+
else
|
633
|
+
account_descendents = lambda do |id|
|
634
|
+
as = Account.where(:parent_account_id => id).active.order(:id)
|
635
|
+
as.empty? ?
|
636
|
+
[] :
|
637
|
+
as << as.map { |a| account_descendents.call(a.id) }
|
638
|
+
end
|
639
|
+
account_descendents.call(id).flatten[offset, limit]
|
640
|
+
end
|
641
|
+
end
|
642
|
+
|
643
|
+
def associated_accounts
|
644
|
+
self.account_chain
|
645
|
+
end
|
646
|
+
|
647
|
+
def account_chain_ids(opts={})
|
648
|
+
account_chain(opts).map(&:id)
|
649
|
+
end
|
650
|
+
|
651
|
+
def membership_for_user(user)
|
652
|
+
self.account_users.find_by_user_id(user && user.id)
|
653
|
+
end
|
654
|
+
|
655
|
+
def available_custom_account_roles
|
656
|
+
account_roles = roles.for_accounts.active
|
657
|
+
account_roles |= self.parent_account.available_custom_account_roles if self.parent_account
|
658
|
+
account_roles
|
659
|
+
end
|
660
|
+
|
661
|
+
def available_account_roles(user = nil)
|
662
|
+
account_roles = roles.for_accounts.active.map(&:name)
|
663
|
+
account_roles |= ['AccountAdmin']
|
664
|
+
account_roles |= self.parent_account.available_account_roles if self.parent_account
|
665
|
+
if user
|
666
|
+
account_roles.select! { |role| account_users.new.grants_right?(user, :create) }
|
667
|
+
end
|
668
|
+
account_roles
|
669
|
+
end
|
670
|
+
|
671
|
+
def available_course_roles(include_inactive=false)
|
672
|
+
available_course_roles_by_name(include_inactive).keys
|
673
|
+
end
|
674
|
+
|
675
|
+
def available_course_roles_by_name(include_inactive=false)
|
676
|
+
scope = include_inactive ? roles.for_courses.not_deleted : roles.for_courses.active
|
677
|
+
role_map = {}
|
678
|
+
scope.each { |role| role_map[role.name] = role }
|
679
|
+
role_map.reverse_merge!(parent_account.available_course_roles_by_name(include_inactive)) if parent_account
|
680
|
+
role_map
|
681
|
+
end
|
682
|
+
|
683
|
+
def get_course_role(role_name)
|
684
|
+
course_role = self.roles.for_courses.not_deleted.find_by_name(role_name)
|
685
|
+
course_role ||= self.parent_account.get_course_role(role_name) if self.parent_account
|
686
|
+
course_role
|
687
|
+
end
|
688
|
+
|
689
|
+
def has_role?(role_name)
|
690
|
+
!!roles.not_deleted.where(:roles => { :name => role_name}).first
|
691
|
+
end
|
692
|
+
|
693
|
+
def find_role(role_name)
|
694
|
+
roles.not_deleted.find_by_name(role_name) || (parent_account && parent_account.find_role(role_name))
|
695
|
+
end
|
696
|
+
|
697
|
+
def account_authorization_config
|
698
|
+
# We support multiple auth configs per account, but several places we assume there is only one.
|
699
|
+
# This is for compatibility with those areas. TODO: migrate everything to supporting multiple
|
700
|
+
# auth configs
|
701
|
+
self.account_authorization_configs.first
|
702
|
+
end
|
703
|
+
|
704
|
+
def login_handle_name_is_customized?
|
705
|
+
self.account_authorization_config && self.account_authorization_config.login_handle_name.present?
|
706
|
+
end
|
707
|
+
|
708
|
+
def login_handle_name
|
709
|
+
login_handle_name_is_customized? ? self.account_authorization_config.login_handle_name :
|
710
|
+
(self.delegated_authentication? ? AccountAuthorizationConfig.default_delegated_login_handle_name :
|
711
|
+
AccountAuthorizationConfig.default_login_handle_name)
|
712
|
+
end
|
713
|
+
|
714
|
+
def self_and_all_sub_accounts
|
715
|
+
@self_and_all_sub_accounts ||= Account.where("root_account_id=? OR parent_account_id=?", self, self).pluck(:id).uniq + [self.id]
|
716
|
+
end
|
717
|
+
|
718
|
+
workflow do
|
719
|
+
state :active
|
720
|
+
state :deleted
|
721
|
+
end
|
722
|
+
|
723
|
+
def account_users_for(user)
|
724
|
+
return [] unless user
|
725
|
+
@account_users_cache ||= {}
|
726
|
+
if self == Account.site_admin
|
727
|
+
shard.activate do
|
728
|
+
@account_users_cache[user.global_id] ||= Rails.cache.fetch('all_site_admin_account_users') do
|
729
|
+
self.account_users.all
|
730
|
+
end.select { |au| au.user_id == user.id }.each { |au| au.account = self }
|
731
|
+
end
|
732
|
+
else
|
733
|
+
@account_chain_ids ||= self.account_chain(:include_site_admin => true).map { |a| a.active? ? a.id : nil }.compact
|
734
|
+
@account_users_cache[user.global_id] ||= Shard.partition_by_shard(@account_chain_ids) do |account_chain_ids|
|
735
|
+
if account_chain_ids == [Account.site_admin.id]
|
736
|
+
Account.site_admin.account_users_for(user)
|
737
|
+
else
|
738
|
+
AccountUser.where(:account_id => account_chain_ids, :user_id => user).all
|
739
|
+
end
|
740
|
+
end
|
741
|
+
end
|
742
|
+
@account_users_cache[user.global_id] ||= []
|
743
|
+
@account_users_cache[user.global_id]
|
744
|
+
end
|
745
|
+
|
746
|
+
# returns all account users for this entire account tree
|
747
|
+
def all_account_users_for(user)
|
748
|
+
raise "must be a root account" unless self.root_account?
|
749
|
+
Shard.partition_by_shard([self, Account.site_admin].uniq) do |accounts|
|
750
|
+
AccountUser.includes(:account).joins(:account).where("user_id=? AND (root_account_id IN (?) OR account_id IN (?))", user, accounts, accounts)
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
set_policy do
|
755
|
+
enrollment_types = RoleOverride.enrollment_types.map { |role| role[:name] }
|
756
|
+
RoleOverride.permissions.each do |permission, details|
|
757
|
+
given { |user| self.account_users_for(user).any? { |au| au.has_permission_to?(self, permission) && (!details[:if] || send(details[:if])) } }
|
758
|
+
can permission
|
759
|
+
can :create_courses if permission == :manage_courses
|
760
|
+
|
761
|
+
next unless details[:account_only]
|
762
|
+
((details[:available_to] | details[:true_for]) & enrollment_types).each do |role|
|
763
|
+
given { |user| user && RoleOverride.permission_for(self, permission, role)[:enabled] &&
|
764
|
+
self.course_account_associations.joins('INNER JOIN enrollments ON course_account_associations.course_id=enrollments.course_id').
|
765
|
+
where("enrollments.type=? AND enrollments.workflow_state IN ('active', 'completed') AND user_id=?", role, user).first &&
|
766
|
+
(!details[:if] || send(details[:if])) }
|
767
|
+
can permission
|
768
|
+
end
|
769
|
+
end
|
770
|
+
|
771
|
+
given { |user| !self.account_users_for(user).empty? }
|
772
|
+
can :read and can :manage and can :update and can :delete and can :read_outcomes
|
773
|
+
|
774
|
+
given { |user|
|
775
|
+
result = false
|
776
|
+
|
777
|
+
if !site_admin? && user
|
778
|
+
scope = root_account.enrollments.active.where(user_id: user)
|
779
|
+
result = root_account.teachers_can_create_courses? &&
|
780
|
+
scope.where(:type => ['TeacherEnrollment', 'DesignerEnrollment']).exists?
|
781
|
+
result ||= root_account.students_can_create_courses? &&
|
782
|
+
scope.where(:type => ['StudentEnrollment', 'ObserverEnrollment']).exists?
|
783
|
+
result ||= root_account.no_enrollments_can_create_courses? &&
|
784
|
+
!scope.exists?
|
785
|
+
end
|
786
|
+
|
787
|
+
result
|
788
|
+
}
|
789
|
+
can :create_courses
|
790
|
+
|
791
|
+
# any logged in user can read global outcomes, but must be checked against the site admin
|
792
|
+
given{ |user| self.site_admin? && user }
|
793
|
+
can :read_global_outcomes
|
794
|
+
|
795
|
+
# any user with an association to this account can read the outcomes in the account
|
796
|
+
given{ |user| user && self.user_account_associations.find_by_user_id(user.id) }
|
797
|
+
can :read_outcomes
|
798
|
+
end
|
799
|
+
|
800
|
+
alias_method :destroy!, :destroy
|
801
|
+
def destroy
|
802
|
+
self.workflow_state = 'deleted'
|
803
|
+
self.deleted_at = Time.now.utc
|
804
|
+
save!
|
805
|
+
end
|
806
|
+
|
807
|
+
def to_atom
|
808
|
+
Atom::Entry.new do |entry|
|
809
|
+
entry.title = self.name
|
810
|
+
entry.updated = self.updated_at
|
811
|
+
entry.published = self.created_at
|
812
|
+
entry.links << Atom::Link.new(:rel => 'alternate',
|
813
|
+
:href => "/accounts/#{self.id}")
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
817
|
+
def default_enrollment_term
|
818
|
+
return @default_enrollment_term if @default_enrollment_term
|
819
|
+
if self.root_account?
|
820
|
+
@default_enrollment_term = self.enrollment_terms.active.find_or_create_by_name(EnrollmentTerm::DEFAULT_TERM_NAME)
|
821
|
+
end
|
822
|
+
end
|
823
|
+
|
824
|
+
def context_code
|
825
|
+
raise "DONT USE THIS, use .short_name instead" unless Rails.env.production?
|
826
|
+
end
|
827
|
+
|
828
|
+
def short_name
|
829
|
+
name
|
830
|
+
end
|
831
|
+
|
832
|
+
# can be set/overridden by plugin to enforce email pseudonyms
|
833
|
+
attr_accessor :email_pseudonyms
|
834
|
+
|
835
|
+
def password_policy
|
836
|
+
Canvas::PasswordPolicy.default_policy.merge(settings[:password_policy] || {})
|
837
|
+
end
|
838
|
+
|
839
|
+
def password_authentication?
|
840
|
+
!!(!self.account_authorization_config || self.account_authorization_config.password_authentication?)
|
841
|
+
end
|
842
|
+
|
843
|
+
def delegated_authentication?
|
844
|
+
!canvas_authentication? || !!(self.account_authorization_config && self.account_authorization_config.delegated_authentication?)
|
845
|
+
end
|
846
|
+
|
847
|
+
def forgot_password_external_url
|
848
|
+
account_authorization_config.try(:change_password_url)
|
849
|
+
end
|
850
|
+
|
851
|
+
def cas_authentication?
|
852
|
+
!!(self.account_authorization_config && self.account_authorization_config.cas_authentication?)
|
853
|
+
end
|
854
|
+
|
855
|
+
def ldap_authentication?
|
856
|
+
self.account_authorization_configs.any? { |aac| aac.ldap_authentication? }
|
857
|
+
end
|
858
|
+
|
859
|
+
def saml_authentication?
|
860
|
+
!!(self.account_authorization_config && self.account_authorization_config.saml_authentication?) && AccountAuthorizationConfig.saml_enabled
|
861
|
+
end
|
862
|
+
|
863
|
+
def multi_auth?
|
864
|
+
self.account_authorization_configs.count > 1
|
865
|
+
end
|
866
|
+
|
867
|
+
def auth_discovery_url=(url)
|
868
|
+
self.settings[:auth_discovery_url] = url
|
869
|
+
end
|
870
|
+
|
871
|
+
def auth_discovery_url
|
872
|
+
self.settings[:auth_discovery_url]
|
873
|
+
end
|
874
|
+
|
875
|
+
def validate_auth_discovery_url
|
876
|
+
return if self.settings[:auth_discovery_url].blank?
|
877
|
+
|
878
|
+
begin
|
879
|
+
value, uri = CanvasHttp.validate_url(self.settings[:auth_discovery_url])
|
880
|
+
self.auth_discovery_url = value
|
881
|
+
rescue URI::InvalidURIError, ArgumentError
|
882
|
+
errors.add(:discovery_url, t('errors.invalid_discovery_url', "The discovery URL is not valid" ))
|
883
|
+
end
|
884
|
+
end
|
885
|
+
|
886
|
+
def find_courses(string)
|
887
|
+
self.all_courses.select{|c| c.name.match(string) }
|
888
|
+
end
|
889
|
+
|
890
|
+
def find_users(string)
|
891
|
+
self.pseudonyms.map{|p| p.user }.select{|u| u.name.match(string) }
|
892
|
+
end
|
893
|
+
|
894
|
+
def self.site_admin
|
895
|
+
get_special_account(:site_admin, 'Site Admin')
|
896
|
+
end
|
897
|
+
|
898
|
+
def self.default
|
899
|
+
get_special_account(:default, 'Default Account')
|
900
|
+
end
|
901
|
+
|
902
|
+
def self.clear_special_account_cache!
|
903
|
+
@special_accounts = {}
|
904
|
+
end
|
905
|
+
|
906
|
+
# an opportunity for plugins to load some other stuff up before caching the account
|
907
|
+
def precache
|
908
|
+
end
|
909
|
+
|
910
|
+
def self.find_cached(id)
|
911
|
+
account = Rails.cache.fetch(account_lookup_cache_key(id)) do
|
912
|
+
account = Account.find_by_id(id)
|
913
|
+
account.precache if account
|
914
|
+
account || :nil
|
915
|
+
end
|
916
|
+
account = nil if account == :nil
|
917
|
+
account
|
918
|
+
end
|
919
|
+
|
920
|
+
def self.get_special_account(special_account_type, default_account_name)
|
921
|
+
Shard.birth.activate do
|
922
|
+
@special_account_ids ||= {}
|
923
|
+
@special_accounts ||= {}
|
924
|
+
|
925
|
+
account = @special_accounts[special_account_type]
|
926
|
+
unless account
|
927
|
+
special_account_id = @special_account_ids[special_account_type] ||= Setting.get("#{special_account_type}_account_id", nil)
|
928
|
+
account = @special_accounts[special_account_type] = Account.find_cached(special_account_id) if special_account_id
|
929
|
+
end
|
930
|
+
# another process (i.e. selenium spec) may have changed the setting
|
931
|
+
unless account
|
932
|
+
special_account_id = Setting.get("#{special_account_type}_account_id", nil)
|
933
|
+
if special_account_id && special_account_id != @special_account_ids[special_account_type]
|
934
|
+
@special_account_ids[special_account_type] = special_account_id
|
935
|
+
account = @special_accounts[special_account_type] = Account.find_by_id(special_account_id)
|
936
|
+
end
|
937
|
+
end
|
938
|
+
if !account && default_account_name
|
939
|
+
# TODO i18n
|
940
|
+
t '#account.default_site_administrator_account_name', 'Site Admin'
|
941
|
+
t '#account.default_account_name', 'Default Account'
|
942
|
+
account = @special_accounts[special_account_type] = Account.new(:name => default_account_name)
|
943
|
+
account.save!
|
944
|
+
Setting.set("#{special_account_type}_account_id", account.id)
|
945
|
+
@special_account_ids[special_account_type] = account.id
|
946
|
+
end
|
947
|
+
account
|
948
|
+
end
|
949
|
+
end
|
950
|
+
|
951
|
+
def site_admin?
|
952
|
+
self == Account.site_admin
|
953
|
+
end
|
954
|
+
|
955
|
+
def display_name
|
956
|
+
self.name
|
957
|
+
end
|
958
|
+
|
959
|
+
# Updates account associations for all the courses and users associated with this account
|
960
|
+
def update_account_associations
|
961
|
+
self.shard.activate do
|
962
|
+
account_chain_cache = {}
|
963
|
+
all_user_ids = Set.new
|
964
|
+
|
965
|
+
# make sure to use the non-associated_courses associations
|
966
|
+
# to catch courses that didn't ever have an association created
|
967
|
+
scopes = if root_account?
|
968
|
+
[all_courses,
|
969
|
+
associated_courses.
|
970
|
+
where("root_account_id<>?", self)]
|
971
|
+
else
|
972
|
+
[courses,
|
973
|
+
associated_courses.
|
974
|
+
where("courses.account_id<>?", self)]
|
975
|
+
end
|
976
|
+
# match the "batch" size in Course.update_account_associations
|
977
|
+
scopes.each do |scope|
|
978
|
+
scope.select([:id, :account_id]).find_in_batches(:batch_size => 500) do |courses|
|
979
|
+
all_user_ids.merge Course.update_account_associations(courses, :skip_user_account_associations => true, :account_chain_cache => account_chain_cache)
|
980
|
+
end
|
981
|
+
end
|
982
|
+
|
983
|
+
# Make sure we have all users with existing account associations.
|
984
|
+
all_user_ids.merge self.user_account_associations.pluck(:user_id)
|
985
|
+
if root_account?
|
986
|
+
all_user_ids.merge self.pseudonyms.active.pluck(:user_id)
|
987
|
+
end
|
988
|
+
|
989
|
+
# Update the users' associations as well
|
990
|
+
User.update_account_associations(all_user_ids.to_a, :account_chain_cache => account_chain_cache)
|
991
|
+
end
|
992
|
+
end
|
993
|
+
|
994
|
+
# this will take an account and make it a sub_account of
|
995
|
+
# itself. Also updates all it's descendant accounts to point to
|
996
|
+
# the correct root account, and updates the pseudonyms to
|
997
|
+
# points to the new root account as well.
|
998
|
+
def consume_account(account)
|
999
|
+
account.all_accounts.each do |sub_account|
|
1000
|
+
sub_account.root_account = self.root_account
|
1001
|
+
sub_account.save!
|
1002
|
+
end
|
1003
|
+
account.parent_account = self
|
1004
|
+
account.root_account = self.root_account
|
1005
|
+
account.save!
|
1006
|
+
account.pseudonyms.each do |pseudonym|
|
1007
|
+
pseudonym.account = self.root_account
|
1008
|
+
pseudonym.save!
|
1009
|
+
end
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
def course_count
|
1013
|
+
self.child_courses.not_deleted.count('DISTINCT course_id')
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
def sub_account_count
|
1017
|
+
self.sub_accounts.active.count
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
def user_count
|
1021
|
+
self.user_account_associations.count
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
def current_sis_batch
|
1025
|
+
if (current_sis_batch_id = self.read_attribute(:current_sis_batch_id)) && current_sis_batch_id.present?
|
1026
|
+
self.sis_batches.find_by_id(current_sis_batch_id)
|
1027
|
+
end
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
def turnitin_settings
|
1031
|
+
return @turnitin_settings if defined?(@turnitin_settings)
|
1032
|
+
if self.turnitin_account_id.present? && self.turnitin_shared_secret.present?
|
1033
|
+
@turnitin_settings = [self.turnitin_account_id, self.turnitin_shared_secret, self.turnitin_host]
|
1034
|
+
else
|
1035
|
+
@turnitin_settings = self.parent_account.try(:turnitin_settings)
|
1036
|
+
end
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
def closest_turnitin_pledge
|
1040
|
+
if self.turnitin_pledge && !self.turnitin_pledge.empty?
|
1041
|
+
self.turnitin_pledge
|
1042
|
+
else
|
1043
|
+
res = self.parent_account.try(:closest_turnitin_pledge)
|
1044
|
+
res ||= t('#account.turnitin_pledge', "This assignment submission is my own, original work")
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
def closest_turnitin_comments
|
1049
|
+
if self.turnitin_comments && !self.turnitin_comments.empty?
|
1050
|
+
self.turnitin_comments
|
1051
|
+
else
|
1052
|
+
self.parent_account.try(:closest_turnitin_comments)
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
def self_enrollment_allowed?(course)
|
1057
|
+
if !settings[:self_enrollment].blank?
|
1058
|
+
!!(settings[:self_enrollment] == 'any' || (!course.sis_source_id && settings[:self_enrollment] == 'manually_created'))
|
1059
|
+
else
|
1060
|
+
!!(parent_account && parent_account.self_enrollment_allowed?(course))
|
1061
|
+
end
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
TAB_COURSES = 0
|
1065
|
+
TAB_STATISTICS = 1
|
1066
|
+
TAB_PERMISSIONS = 2
|
1067
|
+
TAB_SUB_ACCOUNTS = 3
|
1068
|
+
TAB_TERMS = 4
|
1069
|
+
TAB_AUTHENTICATION = 5
|
1070
|
+
TAB_USERS = 6
|
1071
|
+
TAB_OUTCOMES = 7
|
1072
|
+
TAB_RUBRICS = 8
|
1073
|
+
TAB_SETTINGS = 9
|
1074
|
+
TAB_FACULTY_JOURNAL = 10
|
1075
|
+
TAB_SIS_IMPORT = 11
|
1076
|
+
TAB_GRADING_STANDARDS = 12
|
1077
|
+
TAB_QUESTION_BANKS = 13
|
1078
|
+
# site admin tabs
|
1079
|
+
TAB_PLUGINS = 14
|
1080
|
+
TAB_JOBS = 15
|
1081
|
+
TAB_DEVELOPER_KEYS = 16
|
1082
|
+
TAB_ADMIN_TOOLS = 17
|
1083
|
+
|
1084
|
+
def external_tool_tabs(opts)
|
1085
|
+
tools = ContextExternalTool.active.find_all_for(self, :account_navigation)
|
1086
|
+
tools.sort_by(&:id).map do |tool|
|
1087
|
+
{
|
1088
|
+
:id => tool.asset_string,
|
1089
|
+
:label => tool.label_for(:account_navigation, opts[:language]),
|
1090
|
+
:css_class => tool.asset_string,
|
1091
|
+
:href => :account_external_tool_path,
|
1092
|
+
:external => true,
|
1093
|
+
:args => [self.id, tool.id]
|
1094
|
+
}
|
1095
|
+
end
|
1096
|
+
end
|
1097
|
+
|
1098
|
+
def tabs_available(user=nil, opts={})
|
1099
|
+
manage_settings = user && self.grants_right?(user, :manage_account_settings)
|
1100
|
+
if site_admin?
|
1101
|
+
tabs = []
|
1102
|
+
tabs << { :id => TAB_USERS, :label => t('#account.tab_users', "Users"), :css_class => 'users', :href => :account_users_path } if user && self.grants_right?(user, :read_roster)
|
1103
|
+
tabs << { :id => TAB_PERMISSIONS, :label => t('#account.tab_permissions', "Permissions"), :css_class => 'permissions', :href => :account_permissions_path } if user && self.grants_right?(user, :manage_role_overrides)
|
1104
|
+
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
|
1105
|
+
tabs << { :id => TAB_AUTHENTICATION, :label => t('#account.tab_authentication', "Authentication"), :css_class => 'authentication', :href => :account_account_authorization_configs_path } if manage_settings
|
1106
|
+
tabs << { :id => TAB_PLUGINS, :label => t("#account.tab_plugins", "Plugins"), :css_class => "plugins", :href => :plugins_path, :no_args => true } if self.grants_right?(user, :manage_site_settings)
|
1107
|
+
tabs << { :id => TAB_JOBS, :label => t("#account.tab_jobs", "Jobs"), :css_class => "jobs", :href => :jobs_path, :no_args => true } if self.grants_right?(user, :view_jobs)
|
1108
|
+
tabs << { :id => TAB_DEVELOPER_KEYS, :label => t("#account.tab_developer_keys", "Developer Keys"), :css_class => "developer_keys", :href => :developer_keys_path, :no_args => true } if self.grants_right?(user, :manage_developer_keys)
|
1109
|
+
else
|
1110
|
+
tabs = []
|
1111
|
+
tabs << { :id => TAB_COURSES, :label => t('#account.tab_courses', "Courses"), :css_class => 'courses', :href => :account_path } if user && self.grants_right?(user, :read_course_list)
|
1112
|
+
tabs << { :id => TAB_USERS, :label => t('#account.tab_users', "Users"), :css_class => 'users', :href => :account_users_path } if user && self.grants_right?(user, :read_roster)
|
1113
|
+
tabs << { :id => TAB_STATISTICS, :label => t('#account.tab_statistics', "Statistics"), :css_class => 'statistics', :href => :statistics_account_path } if user && self.grants_right?(user, :view_statistics)
|
1114
|
+
tabs << { :id => TAB_PERMISSIONS, :label => t('#account.tab_permissions', "Permissions"), :css_class => 'permissions', :href => :account_permissions_path } if user && self.grants_right?(user, :manage_role_overrides)
|
1115
|
+
if user && self.grants_right?(user, :manage_outcomes)
|
1116
|
+
tabs << { :id => TAB_OUTCOMES, :label => t('#account.tab_outcomes', "Outcomes"), :css_class => 'outcomes', :href => :account_outcomes_path }
|
1117
|
+
tabs << { :id => TAB_RUBRICS, :label => t('#account.tab_rubrics', "Rubrics"), :css_class => 'rubrics', :href => :account_rubrics_path }
|
1118
|
+
end
|
1119
|
+
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, :manage_grades)
|
1120
|
+
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, :manage_grades)
|
1121
|
+
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
|
1122
|
+
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, :manage_user_notes)
|
1123
|
+
tabs << { :id => TAB_TERMS, :label => t('#account.tab_terms', "Terms"), :css_class => 'terms', :href => :account_terms_path } if self.root_account? && manage_settings
|
1124
|
+
tabs << { :id => TAB_AUTHENTICATION, :label => t('#account.tab_authentication', "Authentication"), :css_class => 'authentication', :href => :account_account_authorization_configs_path } if self.root_account? && manage_settings
|
1125
|
+
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, :manage_sis)
|
1126
|
+
end
|
1127
|
+
tabs += external_tool_tabs(opts)
|
1128
|
+
tabs << { :id => TAB_ADMIN_TOOLS, :label => t('#account.tab_admin_tools', "Admin Tools"), :css_class => 'admin_tools', :href => :account_admin_tools_path } if can_see_admin_tools_tab?(user)
|
1129
|
+
tabs << { :id => TAB_SETTINGS, :label => t('#account.tab_settings', "Settings"), :css_class => 'settings', :href => :account_settings_path }
|
1130
|
+
tabs
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
def can_see_admin_tools_tab?(user)
|
1134
|
+
return false if !user || site_admin?
|
1135
|
+
admin_tool_permissions = RoleOverride.manageable_permissions(self).find_all{|p| p[1][:admin_tool]}
|
1136
|
+
admin_tool_permissions.any? do |p|
|
1137
|
+
self.grants_right?(user, p.first)
|
1138
|
+
end
|
1139
|
+
end
|
1140
|
+
|
1141
|
+
def is_a_context?
|
1142
|
+
true
|
1143
|
+
end
|
1144
|
+
|
1145
|
+
def help_links
|
1146
|
+
Canvas::Help.default_links + (settings[:custom_help_links] || [])
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
def self.allowable_services
|
1150
|
+
{
|
1151
|
+
:google_docs => {
|
1152
|
+
:name => t("account_settings.google_docs", "Google Docs"),
|
1153
|
+
:description => "",
|
1154
|
+
:expose_to_ui => (GoogleDocs::Connection.config ? :service : false)
|
1155
|
+
},
|
1156
|
+
:google_docs_previews => {
|
1157
|
+
:name => t("account_settings.google_docs_preview", "Google Docs Preview"),
|
1158
|
+
:description => "",
|
1159
|
+
:expose_to_ui => :service
|
1160
|
+
},
|
1161
|
+
:facebook => {
|
1162
|
+
:name => t("account_settings.facebook", "Facebook"),
|
1163
|
+
:description => "",
|
1164
|
+
:expose_to_ui => (Facebook::Connection.config ? :service : false)
|
1165
|
+
},
|
1166
|
+
:skype => {
|
1167
|
+
:name => t("account_settings.skype", "Skype"),
|
1168
|
+
:description => "",
|
1169
|
+
:expose_to_ui => :service
|
1170
|
+
},
|
1171
|
+
:linked_in => {
|
1172
|
+
:name => t("account_settings.linked_in", "LinkedIn"),
|
1173
|
+
:description => "",
|
1174
|
+
:expose_to_ui => (LinkedIn::Connection.config ? :service : false)
|
1175
|
+
},
|
1176
|
+
:twitter => {
|
1177
|
+
:name => t("account_settings.twitter", "Twitter"),
|
1178
|
+
:description => "",
|
1179
|
+
:expose_to_ui => (Twitter::Connection.config ? :service : false)
|
1180
|
+
},
|
1181
|
+
:delicious => {
|
1182
|
+
:name => t("account_settings.delicious", "Delicious"),
|
1183
|
+
:description => "",
|
1184
|
+
:expose_to_ui => :service
|
1185
|
+
},
|
1186
|
+
:diigo => {
|
1187
|
+
:name => t("account_settings.diigo", "Diigo"),
|
1188
|
+
:description => "",
|
1189
|
+
:expose_to_ui => :service
|
1190
|
+
},
|
1191
|
+
# TODO: move avatars to :settings hash, it makes more sense there
|
1192
|
+
# In the meantime, we leave it as a service but expose it in the
|
1193
|
+
# "Features" (settings) portion of the account admin UI
|
1194
|
+
:avatars => {
|
1195
|
+
:name => t("account_settings.avatars", "User Avatars"),
|
1196
|
+
:description => "",
|
1197
|
+
:default => false,
|
1198
|
+
:expose_to_ui => :setting
|
1199
|
+
},
|
1200
|
+
:account_survey_notifications => {
|
1201
|
+
:name => t("account_settings.account_surveys", "Account Surveys"),
|
1202
|
+
:description => "",
|
1203
|
+
:default => false,
|
1204
|
+
:expose_to_ui => :setting,
|
1205
|
+
:expose_to_ui_proc => proc { |user, account| user && account && account.grants_right?(user, :manage_site_settings) },
|
1206
|
+
},
|
1207
|
+
}.merge(@plugin_services || {}).freeze
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
def self.register_service(service_name, info_hash)
|
1211
|
+
@plugin_services ||= {}
|
1212
|
+
@plugin_services[service_name.to_sym] = info_hash.freeze
|
1213
|
+
end
|
1214
|
+
|
1215
|
+
def self.default_allowable_services
|
1216
|
+
self.allowable_services.reject {|s, info| info[:default] == false }
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
def set_service_availability(service, enable)
|
1220
|
+
service = service.to_sym
|
1221
|
+
raise "Invalid Service" unless Account.allowable_services[service]
|
1222
|
+
allowed_service_names = (self.allowed_services || "").split(",").compact
|
1223
|
+
if allowed_service_names.count > 0 and not [ '+', '-' ].member?(allowed_service_names[0][0,1])
|
1224
|
+
# This account has a hard-coded list of services, so handle accordingly
|
1225
|
+
allowed_service_names.reject! { |flag| flag.match("^[+-]?#{service}$") }
|
1226
|
+
allowed_service_names << service if enable
|
1227
|
+
else
|
1228
|
+
allowed_service_names.reject! { |flag| flag.match("^[+-]?#{service}$") }
|
1229
|
+
if enable
|
1230
|
+
# only enable if it is not enabled by default
|
1231
|
+
allowed_service_names << "+#{service}" unless Account.default_allowable_services[service]
|
1232
|
+
else
|
1233
|
+
# only disable if it is not enabled by default
|
1234
|
+
allowed_service_names << "-#{service}" if Account.default_allowable_services[service]
|
1235
|
+
end
|
1236
|
+
end
|
1237
|
+
|
1238
|
+
@allowed_services_hash = nil
|
1239
|
+
self.allowed_services = allowed_service_names.empty? ? nil : allowed_service_names.join(",")
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
def enable_service(service)
|
1243
|
+
set_service_availability(service, true)
|
1244
|
+
end
|
1245
|
+
|
1246
|
+
def disable_service(service)
|
1247
|
+
set_service_availability(service, false)
|
1248
|
+
end
|
1249
|
+
|
1250
|
+
def allowed_services_hash
|
1251
|
+
return @allowed_services_hash if @allowed_services_hash
|
1252
|
+
account_allowed_services = Account.default_allowable_services
|
1253
|
+
if self.allowed_services
|
1254
|
+
allowed_service_names = self.allowed_services.split(",").compact
|
1255
|
+
|
1256
|
+
if allowed_service_names.count > 0
|
1257
|
+
unless [ '+', '-' ].member?(allowed_service_names[0][0,1])
|
1258
|
+
# This account has a hard-coded list of services, so we clear out the defaults
|
1259
|
+
account_allowed_services = { }
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
allowed_service_names.each do |service_switch|
|
1263
|
+
if service_switch =~ /\A([+-]?)(.*)\z/
|
1264
|
+
flag = $1
|
1265
|
+
service_name = $2.to_sym
|
1266
|
+
|
1267
|
+
if flag == '-'
|
1268
|
+
account_allowed_services.delete(service_name)
|
1269
|
+
else
|
1270
|
+
account_allowed_services[service_name] = Account.allowable_services[service_name]
|
1271
|
+
end
|
1272
|
+
end
|
1273
|
+
end
|
1274
|
+
end
|
1275
|
+
end
|
1276
|
+
@allowed_services_hash = account_allowed_services
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
# if expose_as is nil, all services exposed in the ui are returned
|
1280
|
+
# if it's :service or :setting, then only services set to be exposed as that type are returned
|
1281
|
+
def self.services_exposed_to_ui_hash(expose_as = nil, current_user = nil, account = nil)
|
1282
|
+
if expose_as
|
1283
|
+
self.allowable_services.reject { |key, setting| setting[:expose_to_ui] != expose_as }
|
1284
|
+
else
|
1285
|
+
self.allowable_services.reject { |key, setting| !setting[:expose_to_ui] }
|
1286
|
+
end.reject { |key, setting| setting[:expose_to_ui_proc] && !setting[:expose_to_ui_proc].call(current_user, account) }
|
1287
|
+
end
|
1288
|
+
|
1289
|
+
def service_enabled?(service)
|
1290
|
+
service = service.to_sym
|
1291
|
+
case service
|
1292
|
+
when :none
|
1293
|
+
self.allowed_services_hash.empty?
|
1294
|
+
else
|
1295
|
+
self.allowed_services_hash.has_key?(service)
|
1296
|
+
end
|
1297
|
+
end
|
1298
|
+
|
1299
|
+
def self.all_accounts_for(context)
|
1300
|
+
if context.respond_to?(:account)
|
1301
|
+
context.account.account_chain
|
1302
|
+
elsif context.respond_to?(:parent_account)
|
1303
|
+
context.account_chain
|
1304
|
+
else
|
1305
|
+
[]
|
1306
|
+
end
|
1307
|
+
end
|
1308
|
+
|
1309
|
+
def self.serialization_excludes; [:uuid]; end
|
1310
|
+
|
1311
|
+
# This could be much faster if we implement a SQL tree for the account tree
|
1312
|
+
# structure.
|
1313
|
+
def find_child(child_id)
|
1314
|
+
child_id = child_id.to_i
|
1315
|
+
child_ids = self.class.connection.select_values("SELECT id FROM accounts WHERE parent_account_id = #{self.id}").map(&:to_i)
|
1316
|
+
until child_ids.empty?
|
1317
|
+
if child_ids.include?(child_id)
|
1318
|
+
return self.class.find(child_id)
|
1319
|
+
end
|
1320
|
+
child_ids = self.class.connection.select_values("SELECT id FROM accounts WHERE parent_account_id IN (#{child_ids.join(",")})").map(&:to_i)
|
1321
|
+
end
|
1322
|
+
return false
|
1323
|
+
end
|
1324
|
+
|
1325
|
+
def manually_created_courses_account
|
1326
|
+
return self.root_account.manually_created_courses_account unless self.root_account?
|
1327
|
+
display_name = t('#account.manually_created_courses', "Manually-Created Courses")
|
1328
|
+
acct = manually_created_courses_account_from_settings
|
1329
|
+
if acct.blank?
|
1330
|
+
transaction do
|
1331
|
+
lock!
|
1332
|
+
acct = manually_created_courses_account_from_settings
|
1333
|
+
acct ||= self.sub_accounts.find_by_name(display_name) # for backwards compatibility
|
1334
|
+
acct ||= self.sub_accounts.create!(:name => display_name)
|
1335
|
+
if acct.id != self.settings[:manually_created_courses_account_id]
|
1336
|
+
self.settings[:manually_created_courses_account_id] = acct.id
|
1337
|
+
self.save!
|
1338
|
+
end
|
1339
|
+
end
|
1340
|
+
end
|
1341
|
+
acct
|
1342
|
+
end
|
1343
|
+
|
1344
|
+
def manually_created_courses_account_from_settings
|
1345
|
+
acct_id = self.settings[:manually_created_courses_account_id]
|
1346
|
+
acct = self.sub_accounts.find_by_id(acct_id) if acct_id.present?
|
1347
|
+
acct = nil if acct.present? && acct.root_account_id != self.id
|
1348
|
+
acct
|
1349
|
+
end
|
1350
|
+
private :manually_created_courses_account_from_settings
|
1351
|
+
|
1352
|
+
def trusted_account_ids
|
1353
|
+
return [] if !root_account? || self == Account.site_admin
|
1354
|
+
[ Account.site_admin.id ]
|
1355
|
+
end
|
1356
|
+
|
1357
|
+
def trust_exists?
|
1358
|
+
false
|
1359
|
+
end
|
1360
|
+
|
1361
|
+
def user_list_search_mode_for(user)
|
1362
|
+
return :preferred if self.root_account.open_registration?
|
1363
|
+
return :preferred if self.root_account.grants_right?(user, :manage_user_logins)
|
1364
|
+
:closed
|
1365
|
+
end
|
1366
|
+
|
1367
|
+
scope :root_accounts, -> { where(:root_account_id => nil) }
|
1368
|
+
scope :processing_sis_batch, -> { where("accounts.current_sis_batch_id IS NOT NULL").order(:updated_at) }
|
1369
|
+
scope :name_like, lambda { |name| where(wildcard('accounts.name', name)) }
|
1370
|
+
scope :active, -> { where("accounts.workflow_state<>'deleted'") }
|
1371
|
+
|
1372
|
+
def canvas_network_enabled?
|
1373
|
+
false
|
1374
|
+
end
|
1375
|
+
|
1376
|
+
def calendar2_only?
|
1377
|
+
true
|
1378
|
+
end
|
1379
|
+
|
1380
|
+
def enable_scheduler?
|
1381
|
+
true
|
1382
|
+
end
|
1383
|
+
|
1384
|
+
def change_root_account_setting!(setting_name, new_value)
|
1385
|
+
root_account.settings[setting_name] = new_value
|
1386
|
+
root_account.save!
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
Bookmarker = BookmarkedCollection::SimpleBookmarker.new(Account, :name, :id)
|
1390
|
+
end
|