snuffle 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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