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.
@@ -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 = [[("&nbsp;&nbsp;" * 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