2011-02-01 09:57:29 +08:00
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
class Course < ActiveRecord :: Base
2011-07-14 00:24:17 +08:00
2011-02-01 09:57:29 +08:00
include Context
include Workflow
2011-06-08 00:31:14 +08:00
attr_accessible :name ,
:section ,
:account ,
:group_weighting_scheme ,
:start_at ,
:conclude_at ,
:grading_standard_id ,
:is_public ,
:publish_grades_immediately ,
:allow_student_wiki_edits ,
:allow_student_assignment_edits ,
:hashtag ,
:show_public_context_messages ,
:syllabus_body ,
:allow_student_forum_attachments ,
:default_wiki_editing_roles ,
:allow_student_organized_groups ,
:course_code ,
:default_view ,
:show_all_discussion_entries ,
:open_enrollment ,
:allow_wiki_comments ,
:turnitin_comments ,
:self_enrollment ,
:license ,
:indexed ,
:enrollment_term ,
:abstract_course ,
:root_account ,
:storage_quota ,
:storage_quota_mb ,
:restrict_enrollments_to_course_dates ,
:grading_standard ,
2011-07-13 04:31:40 +08:00
:grading_standard_enabled ,
2011-08-11 01:48:34 +08:00
:locale ,
:settings
2011-02-01 09:57:29 +08:00
serialize :tab_configuration
2011-08-11 01:48:34 +08:00
serialize :settings , Hash
2011-02-01 09:57:29 +08:00
belongs_to :root_account , :class_name = > 'Account'
2011-06-08 00:31:14 +08:00
belongs_to :abstract_course
2011-02-01 09:57:29 +08:00
belongs_to :enrollment_term
2011-04-09 06:45:38 +08:00
belongs_to :grading_standard
2011-05-25 03:22:12 +08:00
belongs_to :template_course , :class_name = > 'Course'
has_many :templated_courses , :class_name = > 'Course' , :foreign_key = > 'template_course_id'
2011-02-01 09:57:29 +08:00
has_many :course_sections
has_many :active_course_sections , :class_name = > 'CourseSection' , :conditions = > { :workflow_state = > 'active' }
has_many :enrollments , :include = > [ :user , :course ] , :conditions = > [ 'enrollments.workflow_state != ?' , 'deleted' ] , :dependent = > :destroy
2011-02-24 13:26:39 +08:00
has_many :current_enrollments , :class_name = > 'Enrollment' , :conditions = > [ 'enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ?' , 'rejected' , 'completed' , 'deleted' , 'inactive' ] , :include = > :user
2011-02-01 09:57:29 +08:00
has_many :prior_enrollments , :class_name = > 'Enrollment' , :include = > [ :user , :course ] , :conditions = > " enrollments.workflow_state = 'completed' "
has_many :students , :through = > :student_enrollments , :source = > :user , :order = > :sortable_name
has_many :all_students , :through = > :all_student_enrollments , :source = > :user , :order = > :sortable_name
has_many :participating_students , :through = > :enrollments , :source = > :user , :conditions = > " enrollments.type = 'StudentEnrollment' and enrollments.workflow_state = 'active' "
2011-02-24 13:26:39 +08:00
has_many :student_enrollments , :class_name = > 'StudentEnrollment' , :conditions = > [ 'enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ?' , 'deleted' , 'completed' , 'rejected' , 'inactive' ] , :include = > :user #, :conditions => "type = 'StudentEnrollment'"
2011-02-01 09:57:29 +08:00
has_many :all_student_enrollments , :class_name = > 'StudentEnrollment' , :conditions = > [ 'enrollments.workflow_state != ?' , 'deleted' ] , :include = > :user
has_many :detailed_enrollments , :class_name = > 'Enrollment' , :conditions = > [ 'enrollments.workflow_state != ?' , 'deleted' ] , :include = > { :user = > { :pseudonym = > :communication_channel } }
has_many :teachers , :through = > :teacher_enrollments , :source = > :user
has_many :teacher_enrollments , :class_name = > 'TeacherEnrollment' , :conditions = > [ 'enrollments.workflow_state != ?' , 'deleted' ] , :include = > :user
has_many :tas , :through = > :ta_enrollments , :source = > :user
has_many :ta_enrollments , :class_name = > 'TaEnrollment' , :conditions = > [ 'enrollments.workflow_state != ?' , 'deleted' ] , :include = > :user
2011-02-18 07:59:01 +08:00
has_many :designers , :through = > :designer_enrollments , :source = > :user
has_many :designer_enrollments , :class_name = > 'DesignerEnrollment' , :conditions = > [ 'enrollments.workflow_state != ?' , 'deleted' ] , :include = > :user
2011-02-01 09:57:29 +08:00
has_many :observers , :through = > :observer_enrollments , :source = > :user
has_many :observer_enrollments , :class_name = > 'ObserverEnrollment' , :conditions = > [ 'enrollments.workflow_state != ?' , 'deleted' ] , :include = > :user
has_many :admins , :through = > :enrollments , :source = > :user , :conditions = > " enrollments.type = 'TaEnrollment' or enrollments.type = 'TeacherEnrollment' "
2011-07-26 00:12:55 +08:00
has_many :admin_enrollments , :class_name = > 'Enrollment' , :conditions = > " (enrollments.type = 'TaEnrollment' or enrollments.type = 'TeacherEnrollment') "
2011-02-01 09:57:29 +08:00
has_many :participating_admins , :through = > :enrollments , :source = > :user , :conditions = > " (enrollments.type = 'TaEnrollment' or enrollments.type = 'TeacherEnrollment') and enrollments.workflow_state = 'active' "
2011-07-26 00:12:55 +08:00
2011-02-01 09:57:29 +08:00
has_many :learning_outcomes , :through = > :learning_outcome_tags , :source = > :learning_outcome_content
2011-10-05 02:30:39 +08:00
has_many :learning_outcome_tags , :as = > :context , :class_name = > 'ContentTag' , :conditions = > [ 'content_tags.tag_type = ? AND content_tags.content_type = ? AND content_tags.workflow_state != ?' , 'learning_outcome_association' , 'LearningOutcome' , 'deleted' ]
2011-02-01 09:57:29 +08:00
has_many :created_learning_outcomes , :class_name = > 'LearningOutcome' , :as = > :context
has_many :learning_outcome_groups , :as = > :context
has_many :course_account_associations
2011-05-17 00:27:35 +08:00
has_many :non_unique_associated_accounts , :source = > :account , :through = > :course_account_associations , :order = > 'course_account_associations.depth'
2011-02-01 09:57:29 +08:00
has_many :users , :through = > :enrollments , :source = > :user
2011-09-22 04:31:36 +08:00
has_many :group_categories , :as = > :context , :conditions = > [ 'deleted_at IS NULL' ]
has_many :all_group_categories , :class_name = > 'GroupCategory' , :as = > :context
2011-02-01 09:57:29 +08:00
has_many :groups , :as = > :context
has_many :active_groups , :as = > :context , :class_name = > 'Group' , :conditions = > [ 'groups.workflow_state != ?' , 'deleted' ]
has_many :assignment_groups , :as = > :context , :dependent = > :destroy , :order = > 'assignment_groups.position, assignment_groups.name'
2011-04-09 06:45:38 +08:00
has_many :assignments , :as = > :context , :dependent = > :destroy , :order = > 'assignments.created_at'
2011-02-01 09:57:29 +08:00
has_many :calendar_events , :as = > :context , :conditions = > [ 'calendar_events.workflow_state != ?' , 'cancelled' ] , :dependent = > :destroy
has_many :submissions , :through = > :assignments , :order = > 'submissions.updated_at DESC' , :include = > :quiz_submission , :dependent = > :destroy
has_many :discussion_topics , :as = > :context , :conditions = > [ 'discussion_topics.workflow_state != ?' , 'deleted' ] , :include = > :user , :dependent = > :destroy , :order = > 'discussion_topics.position DESC, discussion_topics.created_at DESC'
has_many :active_discussion_topics , :as = > :context , :class_name = > 'DiscussionTopic' , :conditions = > [ 'discussion_topics.workflow_state != ?' , 'deleted' ] , :include = > :user
has_many :all_discussion_topics , :as = > :context , :class_name = > " DiscussionTopic " , :include = > :user , :dependent = > :destroy
has_many :discussion_entries , :through = > :discussion_topics , :include = > [ :discussion_topic , :user ] , :dependent = > :destroy
has_many :announcements , :as = > :context , :class_name = > 'Announcement' , :dependent = > :destroy
has_many :active_announcements , :as = > :context , :class_name = > 'Announcement' , :conditions = > [ 'discussion_topics.workflow_state != ?' , 'deleted' ] , :order = > 'created_at DESC'
2011-09-29 07:26:18 +08:00
has_many :attachments , :as = > :context , :dependent = > :destroy , :extend = > Attachment :: FindInContextAssociation
2011-07-12 02:25:54 +08:00
has_many :active_images , :as = > :context , :class_name = > 'Attachment' , :conditions = > [ " attachments.file_state != ? AND attachments.content_type LIKE 'image%' " , 'deleted' ] , :order = > 'attachments.display_name' , :include = > :thumbnail
2011-03-26 15:02:04 +08:00
has_many :active_assignments , :as = > :context , :class_name = > 'Assignment' , :conditions = > [ 'assignments.workflow_state != ?' , 'deleted' ] , :order = > 'assignments.title, assignments.position'
2011-02-01 09:57:29 +08:00
has_many :folders , :as = > :context , :dependent = > :destroy , :order = > 'folders.name'
has_many :active_folders , :class_name = > 'Folder' , :as = > :context , :conditions = > [ 'folders.workflow_state != ?' , 'deleted' ] , :order = > 'folders.name'
2011-03-02 03:17:42 +08:00
has_many :active_folders_with_sub_folders , :class_name = > 'Folder' , :as = > :context , :include = > [ :active_sub_folders ] , :conditions = > [ 'folders.workflow_state != ?' , 'deleted' ] , :order = > 'folders.name'
2011-02-01 09:57:29 +08:00
has_many :active_folders_detailed , :class_name = > 'Folder' , :as = > :context , :include = > [ :active_sub_folders , :active_file_attachments ] , :conditions = > [ 'folders.workflow_state != ?' , 'deleted' ] , :order = > 'folders.name'
has_many :messages , :as = > :context , :dependent = > :destroy
2011-03-10 00:11:22 +08:00
has_many :context_external_tools , :as = > :context , :dependent = > :destroy , :order = > 'name'
2011-02-01 09:57:29 +08:00
belongs_to :wiki
has_many :default_wiki_wiki_pages , :class_name = > 'WikiPage' , :through = > :wiki , :source = > :wiki_pages , :conditions = > [ 'wiki_pages.workflow_state != ?' , 'deleted' ] , :order = > 'wiki_pages.view_count DESC'
has_many :wiki_namespaces , :as = > :context , :dependent = > :destroy
has_many :quizzes , :as = > :context , :dependent = > :destroy , :order = > 'lock_at, title'
has_many :active_quizzes , :class_name = > 'Quiz' , :as = > :context , :include = > :assignment , :conditions = > [ 'quizzes.workflow_state != ?' , 'deleted' ] , :order = > 'created_at'
2011-08-27 08:33:01 +08:00
has_many :assessment_questions , :through = > :assessment_question_banks
2011-02-01 09:57:29 +08:00
has_many :assessment_question_banks , :as = > :context , :include = > [ :assessment_questions , :assessment_question_bank_users ]
2011-08-27 08:33:01 +08:00
def inherited_assessment_question_banks ( include_self = false )
self . account . inherited_assessment_question_banks ( true , * ( include_self ? [ self ] : [ ] ) )
end
2011-02-01 09:57:29 +08:00
has_many :external_feeds , :as = > :context , :dependent = > :destroy
belongs_to :default_grading_standard , :class_name = > 'GradingStandard' , :foreign_key = > 'grading_standard_id'
has_many :grading_standards , :as = > :context
has_one :gradebook_upload , :as = > :context , :dependent = > :destroy
has_many :web_conferences , :as = > :context , :order = > 'created_at DESC' , :dependent = > :destroy
has_many :rubrics , :as = > :context
has_many :rubric_associations , :as = > :context , :include = > :rubric , :dependent = > :destroy
has_many :tags , :class_name = > 'ContentTag' , :as = > 'context' , :order = > 'LOWER(title)' , :conditions = > { :tag_type = > 'default' } , :dependent = > :destroy
has_many :collaborations , :as = > :context , :order = > 'title, created_at' , :dependent = > :destroy
has_one :scribd_account , :as = > :scribdable
has_many :short_message_associations , :as = > :context , :include = > :short_message , :dependent = > :destroy
has_many :short_messages , :through = > :short_message_associations , :dependent = > :destroy
has_many :grading_standards , :as = > :context
has_many :context_modules , :as = > :context , :order = > :position , :dependent = > :destroy
2011-04-12 04:24:34 +08:00
has_many :active_context_modules , :as = > :context , :class_name = > 'ContextModule' , :conditions = > { :workflow_state = > 'active' }
2011-02-01 09:57:29 +08:00
has_many :context_module_tags , :class_name = > 'ContentTag' , :as = > 'context' , :order = > :position , :conditions = > [ 'tag_type = ?' , 'context_module' ] , :dependent = > :destroy
has_many :media_objects , :as = > :context
has_many :page_views , :as = > :context
has_many :role_overrides , :as = > :context
2011-04-06 23:13:00 +08:00
has_many :content_exports
2011-09-07 22:02:47 +08:00
has_many :course_imports
2011-07-26 00:12:55 +08:00
has_many :alerts , :as = > :context , :include = > :criteria
2011-02-01 09:57:29 +08:00
attr_accessor :import_source
before_save :assign_uuid
before_save :assert_defaults
before_save :set_update_account_associations_if_changed
before_save :update_enrollments_later
2011-04-13 01:03:21 +08:00
after_save :update_final_scores_on_weighting_scheme_change
2011-02-01 09:57:29 +08:00
after_save :update_account_associations_if_changed
2011-06-01 04:47:28 +08:00
before_validation :verify_unique_sis_source_id
2011-02-01 09:57:29 +08:00
validates_length_of :syllabus_body , :maximum = > maximum_long_text_length , :allow_nil = > true , :allow_blank = > true
2011-07-13 04:31:40 +08:00
validates_locale :allow_nil = > true
2011-02-01 09:57:29 +08:00
sanitize_field :syllabus_body , Instructure :: SanitizeField :: SANITIZE
2011-09-22 01:36:45 +08:00
include StickySisFields
2011-09-28 06:54:48 +08:00
are_sis_sticky :name , :course_code , :start_at , :conclude_at , :restrict_enrollments_to_course_dates , :enrollment_term_id
2011-09-22 01:36:45 +08:00
2011-02-01 09:57:29 +08:00
has_a_broadcast_policy
2011-05-10 00:14:16 +08:00
def self . skip_updating_account_associations ( & block )
2011-05-17 00:27:35 +08:00
if @skip_updating_account_assocations
block . call
else
begin
@skip_updating_account_associations = true
block . call
ensure
@skip_updating_account_associations = false
end
end
2011-05-10 00:14:16 +08:00
end
def self . skip_updating_account_associations?
! ! @skip_updating_account_associations
end
2011-02-01 09:57:29 +08:00
def set_update_account_associations_if_changed
@should_update_account_associations = self . root_account_id_changed? || self . account_id_changed?
2011-08-24 00:39:52 +08:00
@should_delay_account_associations = ! self . new_record?
2011-02-01 09:57:29 +08:00
true
end
def update_account_associations_if_changed
2011-08-24 00:39:52 +08:00
send_now_or_later_if_production ( @should_delay_account_associations ? :later : :now , :update_account_associations ) if @should_update_account_associations && ! self . class . skip_updating_account_associations?
2011-02-01 09:57:29 +08:00
end
def module_based?
Rails . cache . fetch ( [ 'module_based_course' , self ] . cache_key ) do
self . context_modules . active . any? { | m | m . completion_requirements && ! m . completion_requirements . empty? }
end
end
2011-06-01 04:47:28 +08:00
def verify_unique_sis_source_id
return true unless self . sis_source_id
existing_course = self . root_account . all_courses . find_by_sis_source_id ( self . sis_source_id )
2011-06-22 00:23:08 +08:00
return true if ! existing_course || existing_course . id == self . id
2011-06-01 04:47:28 +08:00
2011-06-22 00:23:08 +08:00
self . errors . add ( :sis_source_id , t ( 'errors.sis_in_use' , " SIS ID \" %{sis_id} \" is already in use " , :sis_id = > self . sis_source_id ) )
2011-06-01 04:47:28 +08:00
false
end
2011-02-01 09:57:29 +08:00
def public_license?
license && license != 'private'
end
2011-06-22 00:23:08 +08:00
def self . licenses
ActiveSupport :: OrderedHash [
'private' ,
2011-02-01 09:57:29 +08:00
{
2011-06-22 00:23:08 +08:00
:readable_license = > t ( '#cc.private' , 'Private (Copyrighted)' ) ,
:license_url = > " http://en.wikipedia.org/wiki/Copyright "
} ,
'cc_by_nc_nd' ,
2011-02-01 09:57:29 +08:00
{
2011-06-22 00:23:08 +08:00
:readable_license = > t ( '#cc.by_nc_nd' , 'CC Attribution Non-Commercial No Derivatives' ) ,
2011-02-01 09:57:29 +08:00
:license_url = > " http://creativecommons.org/licenses/by-nc-nd/3.0/ "
2011-06-22 00:23:08 +08:00
} ,
'cc_by_nc_sa' ,
2011-02-01 09:57:29 +08:00
{
2011-06-22 00:23:08 +08:00
:readable_license = > t ( '#cc.by_nc_sa' , 'CC Attribution Non-Commercial Share Alike' ) ,
2011-02-01 09:57:29 +08:00
:license_url = > " http://creativecommons.org/licenses/by-nc-sa/3.0 "
2011-06-22 00:23:08 +08:00
} ,
'cc_by_nc' ,
2011-02-01 09:57:29 +08:00
{
2011-06-22 00:23:08 +08:00
:readable_license = > t ( '#cc.by_nc' , 'CC Attribution Non-Commercial' ) ,
2011-02-01 09:57:29 +08:00
:license_url = > " http://creativecommons.org/licenses/by-nc/3.0 "
2011-06-22 00:23:08 +08:00
} ,
'cc_by_nd' ,
2011-02-01 09:57:29 +08:00
{
2011-06-22 00:23:08 +08:00
:readable_license = > t ( '#cc.by_nd' , 'CC Attribution No Derivatives' ) ,
2011-02-01 09:57:29 +08:00
:license_url = > " http://creativecommons.org/licenses/by-nd/3.0 "
2011-06-22 00:23:08 +08:00
} ,
'cc_by_sa' ,
2011-02-01 09:57:29 +08:00
{
2011-06-22 00:23:08 +08:00
:readable_license = > t ( '#cc.by_sa' , 'CC Attribution Share Alike' ) ,
2011-02-01 09:57:29 +08:00
:license_url = > " http://creativecommons.org/licenses/by-sa/3.0 "
2011-06-22 00:23:08 +08:00
} ,
'cc_by' ,
2011-02-01 09:57:29 +08:00
{
2011-06-22 00:23:08 +08:00
:readable_license = > t ( '#cc.by' , 'CC Attribution' ) ,
2011-02-01 09:57:29 +08:00
:license_url = > " http://creativecommons.org/licenses/by/3.0 "
2011-06-22 00:23:08 +08:00
} ,
'public_domain' ,
2011-02-01 09:57:29 +08:00
{
2011-06-22 00:23:08 +08:00
:readable_license = > t ( '#cc.public_domain' , 'Public Domain' ) ,
:license_url = > " http://en.wikipedia.org/wiki/Public_domain "
} ,
]
2011-02-01 09:57:29 +08:00
end
2011-06-22 00:23:08 +08:00
def license_data
licenses = self . class . licenses
licenses [ license ] || licenses [ 'private' ]
end
2011-02-01 09:57:29 +08:00
def license_url
license_data [ :license_url ]
end
2011-06-22 00:23:08 +08:00
2011-02-01 09:57:29 +08:00
def readable_license
license_data [ :readable_license ]
end
2011-06-22 00:23:08 +08:00
2011-08-18 03:33:10 +08:00
def self . update_account_associations ( courses_or_course_ids , opts = { } )
return [ ] if courses_or_course_ids . empty?
opts . reverse_merge! :account_chain_cache = > { }
account_chain_cache = opts [ :account_chain_cache ]
# Split it up into manageable chunks
user_ids_to_update_account_associations = [ ]
if courses_or_course_ids . length > 500
opts = opts . dup
opts . reverse_merge! :skip_user_account_associations = > true
courses_or_course_ids . uniq . compact . each_slice ( 500 ) do | courses_or_course_ids_slice |
user_ids_to_update_account_associations += update_account_associations ( courses_or_course_ids_slice , opts )
end
else
if courses_or_course_ids . first . is_a? Course
courses = courses_or_course_ids
Course . send ( :preload_associations , courses , :course_sections = > :nonxlist_course )
course_ids = courses . map ( & :id )
else
course_ids = courses_or_course_ids
courses = Course . find ( :all , :conditions = > { :id = > course_ids } , :include = > { :course_sections = > :nonxlist_course } )
end
course_ids_to_update_user_account_associations = [ ]
CourseAccountAssociation . transaction do
current_associations = { }
to_delete = [ ]
CourseAccountAssociation . find ( :all , :conditions = > { :course_id = > course_ids } ) . each do | aa |
key = [ aa . course_section_id , aa . account_id ]
# duplicates
current_course_associations = current_associations [ aa . course_id ] || = { }
if current_course_associations . has_key? ( key )
to_delete << aa . id
next
end
current_course_associations [ key ] = [ aa . id , aa . depth ]
end
courses . each do | course |
did_an_update = false
current_course_associations = current_associations [ course . id ] || { }
# Courses are tied to accounts directly and through sections and crosslisted courses
( course . course_sections + [ nil ] ) . each do | section |
next if section && ! section . active?
section . course = course if section
starting_account_ids = [ course . account_id , section . try ( :account_id ) , section . try ( :nonxlist_course ) . try ( :account_id ) ] . compact . uniq
account_ids_with_depth = User . calculate_account_associations_from_accounts ( starting_account_ids , account_chain_cache ) . map
account_ids_with_depth . each do | account_id_with_depth |
account_id = account_id_with_depth [ 0 ]
depth = account_id_with_depth [ 1 ]
key = [ section . try ( :id ) , account_id ]
association = current_course_associations [ key ]
if association . nil?
# new association, create it
CourseAccountAssociation . create! do | aa |
aa . course_id = course . id
aa . course_section_id = section . try ( :id )
aa . account_id = account_id
aa . depth = depth
end
did_an_update = true
else
if association [ 1 ] != depth
CourseAccountAssociation . update_all ( " depth= #{ depth } " , :id = > association [ 0 ] )
did_an_update = true
end
# remove from list of existing
current_course_associations . delete ( key )
end
end
end
did_an_update || = ! current_course_associations . empty?
if did_an_update
course . course_account_associations . reset
course . non_unique_associated_accounts . reset
course_ids_to_update_user_account_associations << course . id
end
end
to_delete += current_associations . map { | k , v | v . map { | k2 , v2 | v2 [ 0 ] } } . flatten
unless to_delete . empty?
CourseAccountAssociation . delete_all ( :id = > to_delete )
end
end
user_ids_to_update_account_associations = Enrollment . find ( :all , :select = > 'user_id' , :group = > :user_id ,
:conditions = > [ 'course_id IN(?) AND workflow_state <> ?' , course_ids_to_update_user_account_associations , 'deleted' ] ) . map ( & :user_id ) unless
course_ids_to_update_user_account_associations . empty?
2011-02-01 09:57:29 +08:00
end
2011-08-18 03:33:10 +08:00
User . update_account_associations ( user_ids_to_update_account_associations , :account_chain_cache = > account_chain_cache ) unless user_ids_to_update_account_associations . empty? || opts [ :skip_user_account_associations ]
user_ids_to_update_account_associations
2011-02-01 09:57:29 +08:00
end
def has_outcomes
Rails . cache . fetch ( [ 'has_outcomes' , self ] . cache_key ) do
self . learning_outcome_tags . count > 0
end
end
2011-08-18 03:33:10 +08:00
def update_account_associations
Course . update_account_associations ( [ self ] )
2011-02-01 09:57:29 +08:00
end
2011-05-17 00:27:35 +08:00
def associated_accounts
self . non_unique_associated_accounts . uniq
end
2011-02-01 09:57:29 +08:00
# objects returned from this query will give you an additional attribute "page_views_count" that you can use, so:
# Account.first.courses.most_active(10).first.page_views_count #=> "466"
named_scope :most_active , lambda { | limit |
{
2011-03-01 08:37:39 +08:00
:select = > " courses.*, (SELECT COUNT(*) FROM page_views WHERE context_id = courses.id AND context_type = 'Course') AS page_views_count " ,
2011-02-01 09:57:29 +08:00
:order = > " page_views_count DESC " ,
:limit = > limit
}
}
named_scope :recently_started , lambda {
2011-07-28 00:33:04 +08:00
{ :conditions = > [ 'start_at < ? and start_at > ?' , Time . now . utc , 1 . month . ago ] , :order = > 'start_at DESC' , :limit = > 10 }
2011-02-01 09:57:29 +08:00
}
named_scope :recently_ended , lambda {
2011-07-28 00:33:04 +08:00
{ :conditions = > [ 'conclude_at < ? and conclude_at > ?' , Time . now . utc , 1 . month . ago ] , :order = > 'start_at DESC' , :limit = > 10 }
2011-02-01 09:57:29 +08:00
}
named_scope :recently_created , lambda {
{ :conditions = > [ 'created_at > ?' , 1 . month . ago ] , :order = > 'created_at DESC' , :limit = > 50 , :include = > :teachers }
}
named_scope :for_term , lambda { | term |
term ? { :conditions = > [ 'courses.enrollment_term_id = ?' , term . id ] } : { }
}
named_scope :active_first , lambda {
2011-05-27 06:23:06 +08:00
{ :order = > " CASE WHEN courses.workflow_state='available' THEN 0 ELSE 1 END, name " }
2011-02-01 09:57:29 +08:00
}
named_scope :limit , lambda { | limit |
{ :limit = > limit }
}
named_scope :name_like , lambda { | name |
2011-06-04 08:00:32 +08:00
{ :conditions = > wildcard ( 'courses.name' , 'courses.sis_source_id' , 'courses.course_code' , name ) }
2011-02-01 09:57:29 +08:00
}
named_scope :needs_account , lambda { | account , limit |
{ :conditions = > { :account_id = > nil , :root_account_id = > account . id } , :limit = > limit }
}
named_scope :active , lambda {
{ :conditions = > [ 'courses.workflow_state != ?' , 'deleted' ] }
}
named_scope :least_recently_updated , lambda { | limit |
{ :order = > 'updated_at' , :limit = > limit }
}
named_scope :manageable_by_user , lambda { | user_id |
2011-06-09 03:56:27 +08:00
{ :select = > 'DISTINCT courses.*' ,
:joins = > " INNER JOIN (
SELECT caa . course_id , au . user_id FROM course_account_associations AS caa
INNER JOIN accounts AS a ON a . id = caa . account_id AND a . workflow_state = 'active'
INNER JOIN account_users AS au ON au . account_id = a . id AND au . user_id = #{user_id.to_i}
UNION SELECT courses . id AS course_id , e . user_id FROM courses
INNER JOIN enrollments AS e ON e . course_id = courses . id AND e . user_id = #{user_id.to_i}
AND e . workflow_state = 'active' AND e . type IN ( 'TeacherEnrollment' , 'TaEnrollment' )
WHERE courses . workflow_state NOT IN ( 'aborted' , 'deleted' ) ) as course_users
ON course_users . course_id = courses . id "
2011-02-01 09:57:29 +08:00
}
}
named_scope :not_deleted , { :conditions = > [ 'workflow_state != ?' , 'deleted' ] }
2011-08-17 05:49:53 +08:00
named_scope :with_enrollments , lambda {
{ :conditions = > [ " exists ( #{ Enrollment . active . send ( :construct_finder_sql , { :select = > " 1 " , :conditions = > [ " enrollments.course_id = courses.id " ] } ) } ) " ] }
}
2011-02-01 09:57:29 +08:00
set_broadcast_policy do | p |
p . dispatch :grade_weight_changed
p . to { participating_students }
p . whenever { | record |
( record . available? && @grade_weight_changed ) ||
record . changed_in_state ( :available , :fields = > :group_weighting_scheme )
}
p . dispatch :new_course
p . to { self . root_account . account_users }
p . whenever { | record |
record . root_account &&
2011-06-22 00:23:08 +08:00
( ( record . just_created && record . name != Course . default_name ) ||
( record . prior_version . name == Course . default_name && record . name != Course . default_name ) )
2011-02-01 09:57:29 +08:00
}
end
2011-06-22 00:23:08 +08:00
def self . default_name
2011-07-01 04:36:59 +08:00
# TODO i18n
2011-06-22 00:23:08 +08:00
t ( 'default_name' , " My Course " )
end
2011-02-01 09:57:29 +08:00
def paginate_users_not_in_groups ( groups , page , per_page = 15 )
2011-10-20 02:26:39 +08:00
User . paginate_by_sql ( [ " SELECT u.id, u.name, u.title, u.given_name, u.surname, u.suffix
2011-02-01 09:57:29 +08:00
FROM users u
INNER JOIN enrollments e ON e . user_id = u . id
WHERE e . course_id = ? AND e . workflow_state NOT IN ( 'rejected' , 'completed' , 'deleted' ) AND e . type = 'StudentEnrollment'
#{"AND NOT EXISTS (SELECT *
FROM group_memberships gm
WHERE gm . user_id = u . id AND
gm . group_id IN ( #{groups.map(&:id).join ','}))" unless groups.empty?}
ORDER BY u . sortable_name ASC " , self.id], :page => page, :per_page => per_page)
end
def admins_in_charge_of ( user_id )
section_ids = current_enrollments . find ( :all , :select = > 'course_section_id, course_id, user_id, limit_priveleges_to_course_section' , :conditions = > { :course_id = > self . id , :user_id = > user_id } ) . map ( & :course_section_id ) . compact . uniq
if section_ids . empty?
participating_admins
else
participating_admins . for_course_section ( section_ids )
end
end
def user_is_teacher? ( user )
return unless user
cache_key = [ self , user , " course_user_is_teacher " ] . cache_key
res = Rails . cache . read ( cache_key )
if res . nil?
res = user . cached_current_enrollments . any? { | e | e . course_id == self . id && e . participating_admin? }
Rails . cache . write ( cache_key , res )
end
res
end
memoize :user_is_teacher?
def user_is_student? ( user )
return unless user
cache_key = [ self , user , " course_user_is_student " ] . cache_key
res = Rails . cache . read ( cache_key )
if res . nil?
res = ! self . student_enrollments . find_by_user_id ( user . id ) . nil?
Rails . cache . write ( cache_key , res )
end
res
end
memoize :user_is_student?
def grade_weight_changed!
@grade_weight_changed = true
self . save!
@grade_weight_changed = false
end
def membership_for_user ( user )
self . enrollments . find_by_user_id ( user && user . id )
end
def assert_defaults
Hashtag . find_or_create_by_hashtag ( self . hashtag ) if self . hashtag && self . hashtag != " "
self . tab_configuration || = [ ] unless self . tab_configuration == [ ]
self . name = nil if self . name && self . name . strip . empty?
2011-06-22 00:23:08 +08:00
self . name || = t ( 'missing_name' , " Unnamed Course " )
2011-04-23 14:16:02 +08:00
self . course_code = nil if self . course_code == " " || ( self . name_changed? && self . course_code && self . name_was && self . name_was . start_with? ( self . course_code ) )
2011-02-01 09:57:29 +08:00
if ! self . course_code && self . name
res = [ ]
split = self . name . split ( / \ s / )
res << split [ 0 ]
res << split [ 1 .. - 1 ] . find { | txt | txt . match ( / \ d / ) } rescue nil
self . course_code = res . compact . join ( " " )
end
@group_weighting_scheme_changed = self . group_weighting_scheme_changed?
self . indexed = nil unless self . is_public
if self . account_id && self . account_id_changed?
2011-05-07 00:42:55 +08:00
# This is a bit tricky. Basically, it ensures a is the current account;
# if account is not loaded, it will not double load (because it's
# already cached). If it's already loaded, and correct, it again will
# only use the cache. If it's already loaded and the wrong one, it will
# force reload
a = self . account ( self . account && self . account . id != self . account_id )
2011-02-01 09:57:29 +08:00
self . root_account_id = a . root_account_id if a
self . root_account_id || = a . id if a
2011-05-07 00:42:55 +08:00
# Ditto
self . root_account ( self . root_account && self . root_account . id != self . root_account_id )
2011-02-01 09:57:29 +08:00
end
if self . root_account_id && self . root_account_id_changed?
2011-05-07 00:42:55 +08:00
a = self . account ( self . account && self . account . id != self . account_id )
2011-02-01 09:57:29 +08:00
self . account_id = nil if self . account_id && self . account_id != self . root_account_id && a && a . root_account_id != self . root_account_id
self . account_id || = self . root_account_id
end
self . root_account_id || = Account . default . id
self . account_id || = self . root_account_id
2011-08-20 06:09:57 +08:00
self . enrollment_term = nil if self . enrollment_term . try ( :root_account_id ) != self . root_account_id
2011-02-01 09:57:29 +08:00
self . enrollment_term || = self . root_account . default_enrollment_term
self . publish_grades_immediately = true if self . publish_grades_immediately == nil
self . allow_student_wiki_edits = ( self . default_wiki_editing_roles || " " ) . split ( ',' ) . include? ( 'students' )
true
end
def update_course_section_names
return if @course_name_was == self . name || ! @course_name_was
sections = self . course_sections
fields_to_possibly_rename = [ :name , :section_code , :long_section_code ]
sections . each do | section |
something_changed = false
fields_to_possibly_rename . each do | field |
section . send ( " #{ field } = " , section . default_section ?
self . name :
( section . send ( field ) || self . name ) . sub ( @course_name_was , self . name ) )
something_changed = true if section . send ( field ) != section . send ( " #{ field } _was " )
end
if something_changed
2011-08-31 06:13:41 +08:00
attr_hash = { :updated_at = > Time . now . utc }
2011-02-01 09:57:29 +08:00
fields_to_possibly_rename . each { | key | attr_hash [ key ] = section . send ( key ) }
CourseSection . update_all ( attr_hash , { :id = > section . id } )
end
end
end
def update_enrollments_later
2011-08-20 06:09:57 +08:00
self . update_enrolled_users if ! self . new_record? && ! ( self . changes . keys & [ 'workflow_state' , 'name' , 'course_code' , 'start_at' , 'conclude_at' , 'enrollment_term_id' ] ) . empty?
2011-02-01 09:57:29 +08:00
true
end
def update_enrolled_users
if self . completed?
2011-08-20 06:09:57 +08:00
Enrollment . update_all ( { :workflow_state = > 'completed' } , " course_id= #{ self . id } AND workflow_state IN('active', 'invited') " )
2011-02-16 07:08:51 +08:00
elsif self . deleted?
2011-08-20 06:09:57 +08:00
Enrollment . update_all ( { :workflow_state = > 'deleted' } , " course_id= #{ self . id } AND workflow_state!='deleted' " )
end
case Enrollment . connection . adapter_name
when 'MySQL'
Enrollment . connection . execute ( " UPDATE users, enrollments SET users.updated_at=NOW(), enrollments.updated_at=NOW() WHERE users.id=enrollments.user_id AND enrollments.course_id= #{ self . id } " )
else
2011-08-31 06:13:41 +08:00
Enrollment . update_all ( { :updated_at = > Time . now . utc } , :course_id = > self . id )
User . update_all ( { :updated_at = > Time . now . utc } , " id IN (SELECT user_id FROM enrollments WHERE course_id= #{ self . id } ) " )
2011-02-01 09:57:29 +08:00
end
end
def self_enrollment_allowed?
2011-03-27 11:38:54 +08:00
! ! ( self . account && self . account . self_enrollment_allowed? ( self ) )
2011-02-01 09:57:29 +08:00
end
def self_enrollment_code
Digest :: MD5 . hexdigest ( " #{ uuid } _for_ #{ id } " )
end
memoize :self_enrollment_code
2011-04-13 01:03:21 +08:00
def update_final_scores_on_weighting_scheme_change
2011-02-01 09:57:29 +08:00
if @group_weighting_scheme_changed
2011-04-13 01:03:21 +08:00
Enrollment . send_later_if_production ( :recompute_final_score , self . students . map ( & :id ) , self . id )
2011-02-01 09:57:29 +08:00
end
end
2011-04-13 01:03:21 +08:00
def recompute_student_scores
Enrollment . send_later_if_production ( :recompute_final_score , self . students . map ( & :id ) , self . id )
2011-02-01 09:57:29 +08:00
end
def home_page
WikiNamespace . default_for_context ( self ) . wiki . wiki_page
end
def context_code
raise " DONT USE THIS, use .short_name instead " unless ENV [ 'RAILS_ENV' ] == " production "
end
def allow_media_comments?
true || [ ] . include? ( self . id )
end
def short_name
course_code
end
def short_name = ( val )
write_attribute ( :course_code , val )
end
# Allows the account to be set directly
belongs_to :account
def wiki
2011-05-03 13:56:39 +08:00
res = self . wiki_id && Wiki . find_by_id ( self . wiki_id )
2011-02-01 09:57:29 +08:00
unless res
res = WikiNamespace . default_for_context ( self ) . wiki
self . wiki_id = res . id if res
self . save
end
res
end
# A universal lookup for all messages.
def messages
Message . for ( self )
end
def do_complete
self . conclude_at || = Time . now
end
2011-05-24 07:24:31 +08:00
def do_unconclude
self . conclude_at = nil
end
2011-02-01 09:57:29 +08:00
def do_offer
self . start_at || = Time . now
send_later_if_production ( :invite_uninvited_students )
end
def invite_uninvited_students
self . enrollments . find_all_by_workflow_state ( " creation_pending " ) . each do | e |
e . invite!
end
end
def hashtag_model
Hashtag . find_by_hashtag ( self . hashtag ) if self . hashtag
end
workflow do
state :created do
event :claim , :transitions_to = > :claimed
event :offer , :transitions_to = > :available
event :abort_it , :transitions_to = > :aborted
event :complete , :transitions_to = > :completed
end
state :claimed do
event :offer , :transitions_to = > :available
event :abort_it , :transitions_to = > :aborted
event :complete , :transitions_to = > :completed
end
state :available do
event :abort_it , :transitions_to = > :aborted
event :complete , :transitions_to = > :completed
end
state :aborted
2011-05-24 07:24:31 +08:00
state :completed do
event :unconclude , :transitions_to = > :available
end
2011-02-01 09:57:29 +08:00
state :deleted
end
alias_method :destroy! , :destroy
def destroy
self . workflow_state = 'deleted'
save!
end
def call_event ( event )
self . send ( event ) if self . current_state . events . include? event . to_sym
end
def claim_with_teacher ( user )
raise " Must provide a valid teacher " unless user
return unless self . state == :created
e = enroll_user ( user , 'TeacherEnrollment' , :enrollment_state = > 'active' ) #teacher(user)
claim
e
end
def self . require_assignment_groups ( contexts )
courses = contexts . select { | c | c . is_a? ( Course ) }
hash = { }
courses . each { | c | hash [ c . id ] = { :found = > false , :course = > c } }
groups = AssignmentGroup . find ( :all , { :select = > " id, context_id, context_type " , :conditions = > { :context_type = > " Course " , :context_id = > courses . map ( & :id ) } } )
groups . each { | c | hash [ c . context_id ] [ :found ] = true }
hash . select { | id , obj | ! obj [ :found ] } . each { | id , obj | obj [ :course ] . require_assignment_group rescue nil }
end
def require_assignment_group
has_group = Rails . cache . read ( [ 'has_assignment_group' , self ] . cache_key )
return if has_group && ENV [ 'RAILS_ENV' ] == 'production'
if self . assignment_groups . active . empty?
2011-06-22 00:23:08 +08:00
self . assignment_groups . create ( :name = > t ( '#assignment_group.default_name' , " Assignments " ) )
2011-02-01 09:57:29 +08:00
end
Rails . cache . write ( [ 'has_assignment_group' , self ] . cache_key , true )
end
def self . create_unique ( uuid = nil , account_id = nil , root_account_id = nil )
2011-04-15 06:09:37 +08:00
uuid || = AutoHandle . generate_securish_uuid
2011-02-01 09:57:29 +08:00
course = find_or_initialize_by_uuid ( uuid )
course = Course . new if course . deleted?
2011-06-22 00:23:08 +08:00
course . name = self . default_name if course . new_record?
course . short_name = t ( 'default_short_name' , " Course-101 " ) if course . new_record?
2011-02-01 09:57:29 +08:00
course . account_id = account_id || root_account_id
course . root_account_id = root_account_id
course . save!
course
end
def <=> ( other )
self . id < = > other . id
end
def quota
Rails . cache . fetch ( [ 'default_quota' , self ] . cache_key ) do
2011-07-12 05:36:55 +08:00
storage_quota
2011-02-01 09:57:29 +08:00
end
end
2011-06-18 00:46:48 +08:00
def storage_quota_mb
2011-06-18 04:19:46 +08:00
storage_quota / 1 . megabyte
2011-06-18 00:46:48 +08:00
end
def storage_quota_mb = ( val )
2011-07-09 21:22:33 +08:00
self . storage_quota = val . try ( :to_i ) . try ( :megabytes )
2011-06-18 00:46:48 +08:00
end
2011-07-12 05:36:55 +08:00
def storage_quota
return read_attribute ( :storage_quota ) ||
( self . account . default_storage_quota rescue nil ) ||
Setting . get_cached ( 'course_default_quota' , 500 . megabytes . to_s ) . to_i
end
2011-02-01 09:57:29 +08:00
def storage_quota = ( val )
val = val . to_f
val = nil if val < = 0
if account && account . default_storage_quota == val
val = nil
end
write_attribute ( :storage_quota , val )
end
def assign_uuid
2011-04-15 06:09:37 +08:00
self . uuid || = AutoHandle . generate_securish_uuid
2011-02-01 09:57:29 +08:00
end
protected :assign_uuid
def full_name
name
end
def to_atom
Atom :: Entry . new do | entry |
entry . title = self . name
entry . updated = self . updated_at
entry . published = self . created_at
entry . links << Atom :: Link . new ( :rel = > 'alternate' ,
:href = > " / #{ context_url_prefix } /courses/ #{ self . id } " )
end
end
set_policy do
given { | user | self . available? && self . is_public }
2011-07-14 00:24:17 +08:00
can :read
2011-02-01 09:57:29 +08:00
2011-08-13 00:12:13 +08:00
RoleOverride . permissions . each_key do | permission |
2011-02-01 09:57:29 +08:00
given { | user , session | self . enrollment_allows ( user , session , permission ) || self . account_membership_allows ( user , session , permission ) }
2011-07-14 00:24:17 +08:00
can permission
2011-02-01 09:57:29 +08:00
end
refactor user creation/invitations closes #5833
fixes #5573, #5572, #5753
* communication channels are now only unique within a single user
* UserList changes
* Always resolve pseudonym#unique_ids
* Support looking up by SMS CCs
* Option to either require e-mails match an existing CC,
or e-mails that don't match a Pseudonym will always be
returned unattached (relying on better merging behavior
to not have a gazillion accounts created)
* Method to return users, creating new ones (*without* a
Pseudonym) if necessary. (can't create with a pseudonym,
since Pseudonym#unique_id is still unique, I can't have
multiple outstanding users with the same unique_id)
* EnrollmentsFromUserList is mostly gutted, now using UserList's
functionality directy.
* Use UserList for adding account admins, removing the now
unused Account#add_admin => User#find_by_email/User#assert_by_email
codepath
* Update UsersController#create to not worry about duplicate
communication channels
* Remove AccountsController#add_user, and just use
UsersController#create
* Change SIS::UserImporter to send out a merge opportunity
e-mail if a conflicting CC is found (but still create the CC)
* In /profile, don't worry about conflicting CCs (the CC confirmation
process will now allow merging)
* Remove CommunicationChannelsController#try_merge and #merge
* For the non-simple case of CoursesController#enrollment_invitation
redirect to /register (CommunicationsChannelController#confirm)
* Remove CoursesController#transfer_enrollment
* Move PseudonymsController#registration_confirmation to
CommunicationChannelsController#confirm (have to be able to
register an account without a Pseudonym yet)
* Fold the old direct confirm functionality in, if there are
no available merge opportunities
* Allow merging the new account with the currently logged in user
* Allow changing the Pseudonym#unique_id when registering a new
account (since there might be conflicts)
* Display a list of merge opportunities based on conflicting
communication channels
* Provide link(s) to log in as the other user,
redirecting back to the registration page after login is
complete (to complete the merge as the current user)
* Remove several assert_* methods that are no longer needed
* Update PseudonymSessionsController a bit to deal with the new
way of dealing with conflicting CCs (especially CCs from LDAP),
and to redirect back to the registration/confirmation page when
attempting to do a merge
* Expose the open_registration setting; use it to control if
inviting users to a course is able to create new users
Change-Id: If2f38818a71af656854d3bf8431ddbf5dcb84691
Reviewed-on: https://gerrit.instructure.com/6149
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
2011-10-13 04:30:48 +08:00
given { | user , session | session && session [ :enrollment_uuid ] && ( hash = Enrollment . course_user_state ( self , session [ :enrollment_uuid ] ) || { } ) && ( hash [ :enrollment_state ] == " invited " || hash [ :enrollment_state ] == " active " && hash [ :user_state ] == " pre_registered " ) }
2011-07-14 00:24:17 +08:00
can :read
refactor user creation/invitations closes #5833
fixes #5573, #5572, #5753
* communication channels are now only unique within a single user
* UserList changes
* Always resolve pseudonym#unique_ids
* Support looking up by SMS CCs
* Option to either require e-mails match an existing CC,
or e-mails that don't match a Pseudonym will always be
returned unattached (relying on better merging behavior
to not have a gazillion accounts created)
* Method to return users, creating new ones (*without* a
Pseudonym) if necessary. (can't create with a pseudonym,
since Pseudonym#unique_id is still unique, I can't have
multiple outstanding users with the same unique_id)
* EnrollmentsFromUserList is mostly gutted, now using UserList's
functionality directy.
* Use UserList for adding account admins, removing the now
unused Account#add_admin => User#find_by_email/User#assert_by_email
codepath
* Update UsersController#create to not worry about duplicate
communication channels
* Remove AccountsController#add_user, and just use
UsersController#create
* Change SIS::UserImporter to send out a merge opportunity
e-mail if a conflicting CC is found (but still create the CC)
* In /profile, don't worry about conflicting CCs (the CC confirmation
process will now allow merging)
* Remove CommunicationChannelsController#try_merge and #merge
* For the non-simple case of CoursesController#enrollment_invitation
redirect to /register (CommunicationsChannelController#confirm)
* Remove CoursesController#transfer_enrollment
* Move PseudonymsController#registration_confirmation to
CommunicationChannelsController#confirm (have to be able to
register an account without a Pseudonym yet)
* Fold the old direct confirm functionality in, if there are
no available merge opportunities
* Allow merging the new account with the currently logged in user
* Allow changing the Pseudonym#unique_id when registering a new
account (since there might be conflicts)
* Display a list of merge opportunities based on conflicting
communication channels
* Provide link(s) to log in as the other user,
redirecting back to the registration page after login is
complete (to complete the merge as the current user)
* Remove several assert_* methods that are no longer needed
* Update PseudonymSessionsController a bit to deal with the new
way of dealing with conflicting CCs (especially CCs from LDAP),
and to redirect back to the registration/confirmation page when
attempting to do a merge
* Expose the open_registration setting; use it to control if
inviting users to a course is able to create new users
Change-Id: If2f38818a71af656854d3bf8431ddbf5dcb84691
Reviewed-on: https://gerrit.instructure.com/6149
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
2011-10-13 04:30:48 +08:00
2011-08-20 06:09:57 +08:00
given { | user | self . available? && user && user . cached_current_enrollments . any? { | e | e . course_id == self . id && [ :active , :invited , :completed ] . include? ( e . state_based_on_date ) } }
2011-07-14 00:24:17 +08:00
can :read
2011-02-01 09:57:29 +08:00
given { | user | self . available? && user && user . cached_current_enrollments . any? { | e | e . course_id == self . id && e . participating_student? } }
2011-08-16 06:34:57 +08:00
can :read and can :participate_as_student and can :read_grades
2011-02-11 14:00:39 +08:00
2011-02-01 09:57:29 +08:00
given { | user | self . completed? && user && user . cached_current_enrollments . any? { | e | e . course_id == self . id && e . participating_student? } }
2011-08-16 06:34:57 +08:00
can :read
2011-02-01 09:57:29 +08:00
given { | user | ( self . available? || self . completed? ) && user && user . cached_not_ended_enrollments . any? { | e | e . course_id == self . id && e . participating_observer? } }
2011-07-14 00:24:17 +08:00
can :read
2011-02-01 09:57:29 +08:00
2011-02-11 14:00:39 +08:00
given { | user | ( self . available? || self . completed? ) && user && user . cached_not_ended_enrollments . any? { | e | e . course_id == self . id && e . participating_observer? && e . associated_user_id } }
2011-07-14 00:24:17 +08:00
can :read_grades
2011-02-11 14:00:39 +08:00
2011-02-01 09:57:29 +08:00
given { | user , session | self . available? && self . teacherless? && user && user . cached_not_ended_enrollments . any? { | e | e . course_id == self . id && e . participating_student? } && ( ! session || ! session [ " role_course_ #{ self . id } " ] ) }
2011-07-14 00:24:17 +08:00
can :update and can :delete and RoleOverride . teacherless_permissions . each { | p | can p }
2011-08-16 06:34:57 +08:00
# Active teachers
2011-09-01 05:23:47 +08:00
given { | user , session | ( self . available? || self . created? || self . claimed? ) && user && user . cached_not_ended_enrollments . any? { | e | e . course_id == self . id && e . participating_admin? } && ( ! session || ! session [ " role_course_ #{ self . id } " ] ) }
can :read_as_admin and can :read and can :manage and can :update
2011-08-15 23:53:55 +08:00
2011-09-01 05:23:47 +08:00
given { | user , session | ! self . deleted? && ! self . sis_source_id && user && user . cached_not_ended_enrollments . any? { | e | e . course_id == self . id && e . participating_admin? } && ( ! session || ! session [ " role_course_ #{ self . id } " ] ) }
can :delete
# Prior users
given { | user | ! self . deleted? && user && self . prior_enrollments . map ( & :user_id ) . include? ( user . id ) }
2011-07-14 00:24:17 +08:00
can :read
2011-08-16 06:34:57 +08:00
# Teacher of a concluded course
2011-09-01 05:23:47 +08:00
given { | user | ! self . deleted? && user && self . prior_enrollments . select { | e | e . admin? } . map ( & :user_id ) . include? ( user . id ) }
2011-08-16 06:34:57 +08:00
can :read_as_admin and can :read_user_notes and can :read_roster and can :view_all_grades and can :read_prior_users
2011-09-01 05:23:47 +08:00
given { | user | ! self . deleted? && ! self . sis_source_id && user && self . prior_enrollments . select { | e | e . admin? } . map ( & :user_id ) . include? ( user . id ) }
can :delete
2011-08-16 06:34:57 +08:00
# Student of a concluded course
2011-09-01 05:23:47 +08:00
given { | user | ! self . deleted? && user && self . prior_enrollments . select { | e | e . student? || e . assigned_observer? } . map ( & :user_id ) . include? ( user . id ) }
2011-07-14 00:24:17 +08:00
can :read and can :read_grades
2011-08-16 06:34:57 +08:00
# Viewing as different role type
2011-02-01 09:57:29 +08:00
given { | user , session | session && session [ " role_course_ #{ self . id } " ] }
2011-07-14 00:24:17 +08:00
can :read
2011-08-15 23:53:55 +08:00
2011-08-16 06:34:57 +08:00
# Admin
given { | user , session | self . account_membership_allows ( user , session ) }
can :read_as_admin
given { | user , session | self . account_membership_allows ( user , session , :manage_courses ) }
can :read_as_admin and can :manage and can :update and can :delete
given { | user , session | self . account_membership_allows ( user , session , :read_course_content ) }
can :read
2011-09-01 05:23:47 +08:00
given { | user , session | ! self . deleted? && self . sis_source_id && self . account_membership_allows ( user , session , :manage_sis ) }
can :delete
2011-08-16 06:34:57 +08:00
# Admins with read_roster can see prior enrollments (can't just check read_roster directly,
# because students can't see prior enrollments)
given { | user , session | self . account_membership_allows ( user , session , :read_roster ) }
can :read_prior_roster
2011-02-01 09:57:29 +08:00
end
def enrollment_allows ( user , session , permission )
return false unless user && permission
2011-03-29 00:07:52 +08:00
2011-02-01 09:57:29 +08:00
@enrollment_lookup || = { }
2011-03-29 00:07:52 +08:00
@enrollment_lookup [ user . id ] || =
if session && temp_type = session [ " role_course_ #{ self . id } " ]
2011-08-12 05:32:03 +08:00
[ Enrollment . typed_enrollment ( temp_type ) . new ( :course = > self , :user = > user , :workflow_state = > 'active' ) ]
2011-03-29 00:07:52 +08:00
else
2011-08-24 05:51:31 +08:00
self . enrollments . active_or_pending . for_user ( user ) . reject { | e | [ :inactive , :completed ] . include? ( e . state_based_on_date ) }
2011-02-01 09:57:29 +08:00
end
2011-03-29 00:07:52 +08:00
@enrollment_lookup [ user . id ] . any? { | e | e . has_permission_to? ( permission ) }
2011-02-01 09:57:29 +08:00
end
def self . find_all_by_context_code ( codes )
ids = codes . map { | c | c . match ( / \ Acourse_( \ d+) \ z / ) [ 1 ] rescue nil } . compact
Course . find ( :all , :conditions = > { :id = > ids } , :include = > :current_enrollments )
end
2011-02-24 13:26:39 +08:00
def end_at
conclude_at
end
def end_at_changed?
conclude_at_changed?
end
2011-07-28 00:33:04 +08:00
def recently_ended?
conclude_at && conclude_at < Time . now . utc && conclude_at > 1 . month . ago
end
2011-02-01 09:57:29 +08:00
def state_sortable
case state
when :invited
1
when :creation_pending
1
when :active
0
when :deleted
5
when :course_inactivated
3
when :rejected
4
when :completed
2
else
6
end
end
def has_outcomes?
self . learning_outcomes . count > 0
end
def account_chain
self . account . account_chain
end
2011-09-08 13:20:55 +08:00
def account_chain_ids
account_chain . map ( & :id )
end
memoize :account_chain_ids
2011-02-01 09:57:29 +08:00
def institution_name
return self . root_account . name if self . root_account_id != Account . default . id
return ( self . account || self . root_account ) . name
end
memoize :institution_name
2011-08-13 00:12:13 +08:00
def account_users_for ( user )
@associated_account_ids || = ( self . associated_accounts + [ Account . site_admin ] ) . map { | a | a . active? ? a . id : nil } . compact
@account_users || = { }
@account_users [ user ] || = AccountUser . find ( :all , :conditions = > { :account_id = > @associated_account_ids , :user_id = > user . id } ) if user
@account_users [ user ] || = nil
@account_users [ user ]
end
2011-08-16 06:34:57 +08:00
def account_membership_allows ( user , session , permission = nil )
return false unless user
2011-02-01 09:57:29 +08:00
return false if session && session [ " role_course_ #{ self . id } " ]
2011-08-13 00:12:13 +08:00
2011-02-01 09:57:29 +08:00
@membership_allows || = { }
2011-08-16 06:34:57 +08:00
@membership_allows [ [ user . id , permission ] ] || = self . account_users_for ( user ) . any? { | au | permission . nil? || au . has_permission_to? ( permission ) }
2011-02-01 09:57:29 +08:00
end
def teacherless?
# TODO: I need a better test for teacherless courses... in the mean time we'll just do this
return false
@teacherless_course || = Rails . cache . fetch ( [ 'teacherless_course' , self ] . cache_key ) do
! self . sis_source_id && self . teacher_enrollments . empty?
end
end
def wiki_namespace
WikiNamespace . default_for_context ( self )
end
2011-04-15 07:49:25 +08:00
2011-10-01 02:40:25 +08:00
def grade_publishing_messages
2011-10-04 02:18:07 +08:00
messages = { }
student_enrollments . count ( :all , :group = > [ :grade_publishing_message , :grade_publishing_status ] ) . each do | key , count |
status = key . last
status = " unpublished " if status . blank?
message = key . first
case status
when 'error'
if message . present?
message = t ( 'grade_publishing_status.error_with_message' , " Error: %{message} " , :message = > message )
else
message = t ( 'grade_publishing_status.error' , " Error " )
end
when 'unpublished'
if message . present?
message = t ( 'grade_publishing_status.unpublished_with_message' , " Unpublished: %{message} " , :message = > message )
else
message = t ( 'grade_publishing_status.unpublished' , " Unpublished " )
end
when 'pending'
if message . present?
message = t ( 'grade_publishing_status.pending_with_message' , " Pending: %{message} " , :message = > message )
else
message = t ( 'grade_publishing_status.pending' , " Pending " )
end
when 'publishing'
if message . present?
message = t ( 'grade_publishing_status.publishing_with_message' , " Publishing: %{message} " , :message = > message )
else
message = t ( 'grade_publishing_status.publishing' , " Publishing " )
end
when 'published'
if message . present?
message = t ( 'grade_publishing_status.published_with_message' , " Published: %{message} " , :message = > message )
else
message = t ( 'grade_publishing_status.published' , " Published " )
end
when 'unpublishable'
if message . present?
message = t ( 'grade_publishing_status.unpublishable_with_message' , " Unpublishable: %{message} " , :message = > message )
else
message = t ( 'grade_publishing_status.unpublishable' , " Unpublishable " )
end
else
if message . present?
message = t ( 'grade_publishing_status.unknown_with_message' , " Unknown status, %{status}: %{message} " , :message = > message , :status = > status )
else
message = t ( 'grade_publishing_status.unknown' , " Unknown status, %{status} " , :status = > status )
end
end
messages [ message ] || = 0
messages [ message ] += count
end
messages
2011-10-01 02:40:25 +08:00
end
2011-04-15 07:49:25 +08:00
def grade_publishing_status
2011-10-04 02:18:07 +08:00
# this will return the overall course grade publishing status
2011-04-15 07:49:25 +08:00
statuses = { }
2011-10-04 02:18:07 +08:00
student_enrollments . count ( :all , :group = > [ :grade_publishing_status ] ) . each do | key , count |
statuses [ key || " unpublished " ] = true
2011-04-15 07:49:25 +08:00
end
return " unpublished " unless statuses . size > 0
2011-04-28 08:16:51 +08:00
[ " error " , " unpublished " , " pending " , " publishing " , " published " , " unpublishable " ] . each do | status |
2011-04-15 07:49:25 +08:00
return status if statuses . has_key? ( status )
end
return " error "
end
def publish_final_grades ( publishing_user )
# we want to set all the publishing statuses to 'pending' immediately,
# and then as a delayed job, actually go publish them.
settings = PluginSetting . settings_for_plugin ( 'grade_export' )
raise " final grade publishing disabled " unless settings [ :enabled ] == " true "
raise " endpoint undefined " if settings [ :publish_endpoint ] . nil? || settings [ :publish_endpoint ] . empty?
last_publish_attempt_at = Time . now . utc
self . student_enrollments . update_all :grade_publishing_status = > " pending " ,
:last_publish_attempt_at = > last_publish_attempt_at
send_later_if_production ( :send_final_grades_to_endpoint , publishing_user )
2011-04-28 08:16:51 +08:00
send_at ( last_publish_attempt_at + settings [ :success_timeout ] . to_i . seconds , :expire_pending_grade_publishing_statuses , last_publish_attempt_at ) if settings [ :success_timeout ] . present? && settings [ :wait_for_success ] && Rails . env . production?
end
def self . valid_grade_export_types
@valid_grade_export_types || = {
" instructure_csv " = > {
2011-06-22 00:23:08 +08:00
:name = > t ( 'grade_export_types.instructure_csv' , " Instructure formatted CSV " ) ,
2011-04-28 08:16:51 +08:00
:callback = > lambda { | course , enrollments , publishing_pseudonym |
course . generate_grade_publishing_csv_output ( enrollments , publishing_pseudonym )
}
}
}
2011-04-15 07:49:25 +08:00
end
def send_final_grades_to_endpoint ( publishing_user )
# actual grade publishing logic is here, but you probably want
# 'publish_final_grades'
enrollments = self . student_enrollments . scoped ( { :include = > [ :user , :course_section ] } ) . find ( :all , :order = > " users.sortable_name " )
2011-06-22 02:58:04 +08:00
begin
2011-04-15 07:49:25 +08:00
2011-06-22 02:58:04 +08:00
settings = PluginSetting . settings_for_plugin ( 'grade_export' )
raise " final grade publishing disabled " unless settings [ :enabled ] == " true "
raise " endpoint undefined " if settings [ :publish_endpoint ] . blank?
publishing_pseudonym = publishing_user . pseudonyms . active . find_by_account_id ( self . root_account_id , :order = > " sis_user_id DESC " )
errors = [ ]
posts_to_make = [ ]
ignored_enrollment_ids = [ ]
if Course . valid_grade_export_types . has_key? ( settings [ :format_type ] )
callback = Course . valid_grade_export_types [ settings [ :format_type ] ] [ :callback ]
2011-05-20 08:20:01 +08:00
posts_to_make , ignored_enrollment_ids = callback . call ( self , enrollments ,
publishing_pseudonym )
end
2011-06-22 02:58:04 +08:00
rescue
Enrollment . update_all ( { :grade_publishing_status = > " error " } , { :id = > enrollments . map ( & :id ) } )
raise
2011-04-28 08:16:51 +08:00
end
2011-04-15 07:49:25 +08:00
2011-06-01 04:24:52 +08:00
Enrollment . update_all ( { :grade_publishing_status = > " unpublishable " } , { :id = > ignored_enrollment_ids } )
2011-04-15 07:49:25 +08:00
2011-04-28 08:16:51 +08:00
posts_to_make . each do | enrollment_ids , res , mime_type |
begin
SSLCommon . post_data ( settings [ :publish_endpoint ] , res , mime_type )
2011-06-01 04:24:52 +08:00
Enrollment . update_all ( { :grade_publishing_status = > ( settings [ :wait_for_success ] == " yes " ? " publishing " : " published " ) } , { :id = > enrollment_ids } )
2011-04-28 08:16:51 +08:00
rescue = > e
errors << e
2011-06-01 04:24:52 +08:00
Enrollment . update_all ( { :grade_publishing_status = > " error " } , { :id = > enrollment_ids } )
2011-04-28 08:16:51 +08:00
end
end
2011-06-22 02:58:04 +08:00
2011-04-28 08:16:51 +08:00
raise errors [ 0 ] if errors . size > 0
end
def generate_grade_publishing_csv_output ( enrollments , publishing_pseudonym )
enrollment_ids = [ ]
res = FasterCSV . generate do | csv |
2011-06-23 04:35:05 +08:00
csv << [ " publisher_id " , " publisher_sis_id " , " section_id " , " section_sis_id " , " student_id " , " student_sis_id " , " enrollment_id " , " enrollment_status " , " grade " , " score " ]
2011-04-15 07:49:25 +08:00
enrollments . each do | enrollment |
enrollment_ids << enrollment . id
2011-06-23 04:35:05 +08:00
next unless enrollment . computed_final_score
2011-04-15 07:49:25 +08:00
enrollment . user . pseudonyms . active . find_all_by_account_id ( self . root_account_id ) . each do | user_pseudonym |
2011-06-23 04:35:05 +08:00
csv << [ publishing_pseudonym . try ( :id ) , publishing_pseudonym . try ( :sis_user_id ) , enrollment . course_section . id , enrollment . course_section . sis_source_id , user_pseudonym . id , user_pseudonym . sis_user_id , enrollment . id , enrollment . workflow_state , enrollment . computed_final_grade , enrollment . computed_final_score ]
2011-04-15 07:49:25 +08:00
end
end
end
2011-04-28 08:16:51 +08:00
return [ [ enrollment_ids , res , " text/csv " ] ] , [ ]
2011-04-15 07:49:25 +08:00
end
def expire_pending_grade_publishing_statuses ( last_publish_attempt_at )
self . student_enrollments . scoped ( :conditions = > [ " grade_publishing_status IN ('pending', 'publishing') AND last_publish_attempt_at = ? " ,
last_publish_attempt_at ] ) . update_all :grade_publishing_status = > 'error'
end
2011-02-01 09:57:29 +08:00
def gradebook_to_csv ( options = { } )
assignments = self . assignments . active . gradeable
2011-10-04 07:13:26 +08:00
if options [ :assignment_id ]
assignments = [ assignments . find ( options [ :assignment_id ] ) ]
else
assignments = assignments . find ( :all , :order = > 'due_at, title' )
end
2011-02-01 09:57:29 +08:00
single = assignments . length == 1
student_enrollments = self . student_enrollments . scoped ( { :include = > [ :user , :course_section ] } ) . find ( :all , :order = > " users.sortable_name " )
submissions = self . submissions . inject ( { } ) { | h , sub |
h [ [ sub . user_id , sub . assignment_id ] ] = sub ; h
}
2011-06-22 00:23:08 +08:00
read_only = t ( 'csv.read_only_field' , '(read only)' )
2011-07-01 04:36:59 +08:00
t 'csv.student' , 'Student'
t 'csv.id' , 'ID'
2011-08-19 23:39:44 +08:00
t 'csv.sis_user_id' , 'SIS User ID'
t 'csv.sis_login_id' , 'SIS Login ID'
2011-07-01 04:36:59 +08:00
t 'csv.section' , 'Section'
t 'csv.comments' , 'Comments'
t 'csv.current_score' , 'Current Score'
t 'csv.final_score' , 'Final Score'
t 'csv.final_grade' , 'Final Grade'
t 'csv.points_possible' , 'Points Possible'
2011-02-01 09:57:29 +08:00
res = FasterCSV . generate do | csv |
#First row
2011-08-19 23:39:44 +08:00
row = [ " Student " , " ID " ]
row . concat ( [ " SIS User ID " , " SIS Login ID " ] ) if options [ :include_sis_id ]
row << " Section "
2011-02-01 09:57:29 +08:00
row . concat ( assignments . map { | a | single ? [ a . title_with_id , 'Comments' ] : a . title_with_id } )
row . concat ( [ " Current Score " , " Final Score " ] )
2011-04-09 06:45:38 +08:00
row . concat ( [ " Final Grade " ] ) if self . grading_standard_id
2011-02-01 09:57:29 +08:00
csv << row . flatten
#Second Row
row = [ " Points Possible " , " " , " " ]
2011-09-13 04:52:16 +08:00
row . concat ( [ " " , " " ] ) if options [ :include_sis_id ]
2011-02-01 09:57:29 +08:00
row . concat ( assignments . map { | a | single ? [ a . points_possible , '' ] : a . points_possible } )
2011-06-22 00:23:08 +08:00
row . concat ( [ read_only , read_only ] )
row . concat ( [ read_only ] ) if self . grading_standard_id
2011-02-01 09:57:29 +08:00
csv << row . flatten
student_enrollments . each do | student_enrollment |
student = student_enrollment . user
student_section = ( student_enrollment . course_section . display_name rescue nil ) || " "
student_submissions = assignments . map do | a |
submission = submissions [ [ student . id , a . id ] ]
score = submission && submission . score ? submission . score : " "
data = [ score , '' ] rescue [ " " , '' ]
single ? data : data [ 0 ]
end
#Last Row
2011-08-19 23:39:44 +08:00
row = [ student . last_name_first , student . id ]
2011-10-11 05:42:12 +08:00
row . concat ( [ student . pseudonym . try ( :sis_user_id ) , student . pseudonym . try ( :unique_id ) ] ) if options [ :include_sis_id ]
2011-08-19 23:39:44 +08:00
row << student_section
2011-02-01 09:57:29 +08:00
row . concat ( student_submissions )
row . concat ( [ student_enrollment . computed_current_score , student_enrollment . computed_final_score ] )
2011-04-09 06:45:38 +08:00
if self . grading_standard_id
row . concat ( [ score_to_grade ( student_enrollment . computed_final_score ) ] )
end
2011-02-01 09:57:29 +08:00
csv << row . flatten
end
end
end
2011-04-09 06:45:38 +08:00
def grading_standard_title
if self . grading_standard_id
2011-06-22 00:23:08 +08:00
self . grading_standard . try ( :title ) || t ( 'default_grading_scheme_name' , " Default Grading Scheme " )
2011-04-09 06:45:38 +08:00
else
nil
end
end
def score_to_grade ( score )
return " " unless self . grading_standard_id && score
scheme = self . grading_standard . try ( :data ) || GradingStandard . default_grading_standard
2011-08-27 04:37:42 +08:00
GradingStandard . score_to_grade ( scheme , score )
2011-04-09 06:45:38 +08:00
end
2011-02-01 09:57:29 +08:00
def participants
( participating_admins + participating_students ) . uniq
end
def enroll_user ( user , type = 'StudentEnrollment' , opts = { } )
enrollment_state = opts [ :enrollment_state ]
section = opts [ :section ]
limit_priveleges_to_course_section = opts [ :limit_priveleges_to_course_section ]
section || = self . default_section
enrollment_state || = self . available? ? " invited " : " creation_pending "
enrollment_state = 'invited' if enrollment_state == 'creation_pending' && ( type == 'TeacherEnrollment' || type == 'TaEnrollment' )
e = self . enrollments . find_by_user_id_and_type ( user . id , type ) if user
e . update_attributes ( :workflow_state = > 'invited' , :course_section = > section , :limit_priveleges_to_course_section = > limit_priveleges_to_course_section ) if e && ( e . completed? || e . rejected? )
2011-10-19 06:17:06 +08:00
e || = self . send ( type . underscore . pluralize ) . create ( :user = > user , :workflow_state = > enrollment_state , :course_section = > section , :limit_priveleges_to_course_section = > limit_priveleges_to_course_section )
2011-09-02 11:40:25 +08:00
e . user = user
2011-02-01 09:57:29 +08:00
self . claim if self . created? && e && e . admin?
2011-09-02 03:07:06 +08:00
user . try ( :touch ) unless opts [ :skip_touch_user ]
2011-02-01 09:57:29 +08:00
e
end
def enroll_student ( user )
enroll_user ( user , 'StudentEnrollment' )
end
def enroll_ta ( user )
enroll_user ( user , 'TaEnrollment' )
end
def enroll_teacher ( user )
enroll_user ( user , 'TeacherEnrollment' )
end
def resubmission_for ( asset_string )
admins . each { | u | u . ignored_item_changed! ( asset_string , 'grading' ) }
end
2011-04-09 06:45:38 +08:00
def grading_standard_enabled
! ! self . grading_standard_id
end
def grading_standard_enabled = ( val )
if val == false || val == '0' || val == 'false' || val == 'off'
self . grading_standard = nil
else
self . grading_standard_id || = 0
end
end
2011-02-01 09:57:29 +08:00
def gradebook_json
2011-04-02 06:03:50 +08:00
hash = self . as_json ( :include_root = > false )
submissions = self . submissions
hash [ 'active_assignments' ] = self . active_assignments . map { | a | a . as_json ( :include_root = > false ) }
hash [ 'students' ] = self . students . map do | user |
res = user . as_json ( :include_root = > false )
res [ 'submissions' ] = submissions . select { | s | s . user_id == user . id } . map { | s | s . as_json ( :include_root = > false ) }
res
end
hash . to_json
end
2011-02-01 09:57:29 +08:00
def add_aggregate_entries ( entries , feed )
if feed . feed_purpose == 'announcements'
entries . each do | entry |
user = entry . user || feed . user
# If already existed and has been updated
if entry . entry_changed? && entry . asset
entry . asset . update_attributes (
:title = > entry . title ,
:message = > entry . message
)
elsif ! entry . asset
announcement = self . announcements . build (
:title = > entry . title ,
:message = > entry . message
)
announcement . user = user
announcement . save
entry . update_attributes ( :asset = > announcement )
end
end
elsif feed . feed_purpose == 'calendar'
entries . each do | entry |
user = entry . user || feed . user
# If already existed and has been updated
if entry . entry_changed? && entry . asset
event = entry . asset
event . attributes = {
:title = > entry . title ,
:description = > entry . message ,
:start_at = > entry . start_at ,
:end_at = > entry . end_at
}
event . workflow_state = 'cancelled' if entry . cancelled?
event . save
elsif entry . active? && ! entry . asset
event = self . calendar_events . build (
:title = > entry . title ,
:description = > entry . message ,
:start_at = > entry . start_at ,
:end_at = > entry . end_at
)
event . workflow_state = 'read_only'
event . workflow_state = 'cancelled' if entry . cancelled?
event . save
entry . update_attributes ( :asset = > event )
end
end
end
end
def readable_default_wiki_editing_roles
roles = self . default_wiki_editing_roles || " teachers "
case roles
when 'teachers'
2011-06-22 00:23:08 +08:00
t ( 'wiki_permissions.only_teachers' , 'Only Teachers' )
2011-02-01 09:57:29 +08:00
when 'teachers,students'
2011-06-22 00:23:08 +08:00
t ( 'wiki_permissions.teachers_students' , 'Teacher and Students' )
2011-02-01 09:57:29 +08:00
when 'teachers,students,public'
2011-06-22 00:23:08 +08:00
t ( 'wiki_permissions.all' , 'Anyone' )
2011-02-01 09:57:29 +08:00
else
2011-06-22 00:23:08 +08:00
t ( 'wiki_permissions.only_teachers' , 'Only Teachers' )
2011-02-01 09:57:29 +08:00
end
end
def default_section
2011-05-07 02:22:03 +08:00
self . course_sections . active . find_or_create_by_default_section ( true ) do | section |
section . course = self
section . root_account = self . root_account
end
2011-02-01 09:57:29 +08:00
end
def assert_section
if self . course_sections . active . empty?
default = self . default_section
default . workflow_state = 'active'
default . save
end
end
def file_structure_for ( user )
User . file_structure_for ( self , user )
end
2011-09-07 22:02:47 +08:00
def merge_in ( course , options = { } , import = nil )
2011-02-01 09:57:29 +08:00
return [ ] if course == self
2011-09-07 22:02:47 +08:00
res = merge_into_course ( course , options , import )
2011-02-01 09:57:29 +08:00
course . course_sections . active . each do | section |
if options [ :all_sections ] || options [ section . asset_string . to_sym ]
section . move_to_course ( self )
end
end
res
end
def self . copy_authorized_content ( html , to_context , user )
return html unless to_context
pairs = [ ]
content_types_to_copy = [ 'files' ]
matches = html . scan ( / \/ (courses|groups|users) \/ ( \ d+) \/ ( \ w+) / ) do | match |
pairs << [ match [ 0 ] . singularize , match [ 1 ] . to_i ] if content_types_to_copy . include? ( match [ 2 ] )
end
pairs = pairs . select { | p | p [ 0 ] != to_context . class . to_s || p [ 1 ] != to_context . id }
pairs . uniq . each do | context_type , id |
context = Context . find_by_asset_string ( " #{ context_type } _ #{ id } " ) rescue nil
if context
if context . grants_right? ( user , nil , :manage_content )
html = self . migrate_content_links ( html , context , to_context , content_types_to_copy )
else
html = self . migrate_content_links ( html , context , to_context , content_types_to_copy , user )
end
end
end
html
end
def turnitin_settings
# check if somewhere up the account chain turnitin is enabled and
# has valid settings
self . account . turnitin_settings
end
def turnitin_pledge
self . account . closest_turnitin_pledge
end
def all_turnitin_comments
comments = self . account . closest_turnitin_comments || " "
if self . turnitin_comments && ! self . turnitin_comments . empty?
comments += " \n \n " if comments && ! comments . empty?
comments += self . turnitin_comments
end
self . extend TextHelper
format_message ( comments ) . first
end
def turnitin_enabled?
! ! self . turnitin_settings
end
2011-04-28 02:34:01 +08:00
def self . find_or_create_for_new_context ( obj_class , new_context , old_context , old_id )
association_name = obj_class . table_name
old_item = old_context . send ( association_name ) . find_by_id ( old_id )
2011-06-24 02:17:25 +08:00
res = new_context . send ( association_name ) . first ( :conditions = > { :cloned_item_id = > old_item . cloned_item_id } , :order = > 'id desc' ) if old_item
2011-09-29 07:26:18 +08:00
if ! res && old_item
# make sure it's active by re-finding it with the active scope ... active
old_item = old_context . send ( association_name ) . active . find_by_id ( old_item . id )
2011-04-28 02:34:01 +08:00
res = old_item . clone_for ( new_context ) if old_item
res . save if res
end
res
end
2011-02-01 09:57:29 +08:00
def self . migrate_content_links ( html , from_context , to_context , supported_types = nil , user_to_check_for_permission = nil )
2011-10-04 23:03:19 +08:00
return html unless html . present? && to_context
from_name = from_context . class . name . tableize
to_name = to_context . class . name . tableize
2011-02-01 09:57:29 +08:00
@merge_mappings || = { }
2011-10-04 23:03:19 +08:00
rewriter = UserContent :: HtmlRewriter . new ( from_context , user_to_check_for_permission )
2011-02-01 09:57:29 +08:00
limit_migrations_to_listed_types = ! ! supported_types
2011-10-04 23:03:19 +08:00
rewriter . allowed_types = %w( assignments calendar_events discussion_topics collaborations files conferences quizzes groups modules )
rewriter . set_default_handler do | match |
new_url = match . url
2011-10-16 06:43:54 +08:00
next ( new_url ) if supported_types && ! supported_types . include? ( match . type )
2011-10-04 23:03:19 +08:00
new_id = @merge_mappings [ " #{ match . obj_class . name . underscore } _ #{ match . obj_id } " ]
2011-10-16 06:43:54 +08:00
next ( new_url ) unless rewriter . user_can_view_content? { match . obj_class . find_by_id ( match . obj_id ) }
2011-10-04 23:03:19 +08:00
if ! new_id && to_context != from_context
new_obj = self . find_or_create_for_new_context ( match . obj_class , to_context , from_context , match . obj_id )
new_id = new_obj . id if new_obj
2011-02-01 09:57:29 +08:00
end
2011-10-04 23:03:19 +08:00
if ! limit_migrations_to_listed_types || new_id
new_url = new_url . gsub ( " #{ match . type } / #{ match . obj_id } " , new_id ? " #{ match . type } / #{ new_id } " : " #{ match . type } " )
2011-02-01 09:57:29 +08:00
end
2011-10-04 23:03:19 +08:00
new_url . gsub ( " / #{ from_name } / #{ from_context . id } " , " / #{ to_name } / #{ to_context . id } " )
end
rewriter . set_unknown_handler do | match |
match . url . gsub ( " / #{ from_name } / #{ from_context . id } " , " / #{ to_name } / #{ to_context . id } " )
2011-02-01 09:57:29 +08:00
end
2011-10-04 23:03:19 +08:00
html = rewriter . translate_content ( html )
2011-02-01 09:57:29 +08:00
if ! limit_migrations_to_listed_types
2011-10-04 23:03:19 +08:00
# for things like calendar urls, swap out the old context id with the new one
2011-02-01 09:57:29 +08:00
regex = Regexp . new ( " include_contexts=[^ \\ s&]* #{ from_context . asset_string } " )
html = html . gsub ( regex ) do | match |
match . gsub ( " #{ from_context . asset_string } " , " #{ to_context . asset_string } " )
end
2011-10-04 23:03:19 +08:00
# swap out the old host with the new host
2011-02-01 09:57:29 +08:00
html = html . gsub ( HostUrl . context_host ( from_context ) , HostUrl . context_host ( to_context ) )
end
2011-10-04 23:03:19 +08:00
2011-02-01 09:57:29 +08:00
html
end
def migrate_content_links ( html , from_course )
Course . migrate_content_links ( html , from_course , self )
end
attr_accessor :merge_results
def log_merge_result ( text )
@merge_results || = [ ]
logger . debug text
@merge_results << text
end
def warn_merge_result ( text )
log_merge_result ( text )
end
def bool_res ( val )
val == '1' || val == true || val == 'yes'
end
def boolean_hash ( hash )
res = { }
hash . each do | key , val |
res [ key ] = true if bool_res ( hash [ key ] )
end
end
def process_migration_files ( data , migration )
return unless data [ 'all_files_export' ] && data [ 'all_files_export' ] [ 'file_path' ]
return unless File . exist? ( data [ 'all_files_export' ] [ 'file_path' ] )
2011-04-19 11:16:36 +08:00
self . attachment_path_id_lookup || = { }
2011-02-01 09:57:29 +08:00
params = migration . migration_settings [ :migration_ids_to_import ]
valid_paths = [ ]
( data [ 'file_map' ] || { } ) . each do | id , file |
2011-04-29 23:56:52 +08:00
if ! migration . context . attachments . detect { | f | f . migration_id == file [ 'migration_id' ] } || migration . migration_settings [ :files_import_allow_rename ]
2011-02-01 09:57:29 +08:00
path = file [ 'path_name' ] . starts_with? ( '/' ) ? file [ 'path_name' ] [ 1 .. - 1 ] : file [ 'path_name' ]
2011-04-19 11:16:36 +08:00
self . attachment_path_id_lookup [ path ] = file [ 'migration_id' ]
2011-02-01 09:57:29 +08:00
if params [ :copy ] [ :files ]
valid_paths << path if ( bool_res ( params [ :copy ] [ :files ] [ file [ 'migration_id' ] . to_sym ] ) rescue false )
else
valid_paths << path
end
end
end
valid_paths = [ 0 ] if valid_paths . empty? && params [ :copy ] && params [ :copy ] [ :files ]
logger . debug " adding #{ valid_paths . length } files "
total = valid_paths . length
if valid_paths != [ 0 ]
current = 0
last = current
callback = Proc . new do
current += 1
if ( current - last ) > 10
last = current
2011-05-14 06:36:20 +08:00
migration . fast_update_progress ( ( current . to_f / total ) * 18 . 0 )
2011-02-01 09:57:29 +08:00
end
end
2011-04-29 23:56:52 +08:00
unzip_opts = {
:course = > migration . context ,
:filename = > data [ 'all_files_export' ] [ 'file_path' ] ,
:valid_paths = > valid_paths ,
:callback = > callback ,
:logger = > logger ,
:rename_files = > migration . migration_settings [ :files_import_allow_rename ] ,
:migration_id_map = > self . attachment_path_id_lookup ,
}
if root_path = migration . migration_settings [ :files_import_root_path ]
unzip_opts [ :root_directory ] = Folder . assert_path (
root_path , migration . context )
end
unzipper = UnzipAttachment . new ( unzip_opts )
2011-02-01 09:57:29 +08:00
migration . fast_update_progress ( 1 . 0 )
unzipper . process
end
end
private :process_migration_files
def import_from_migration ( data , params , migration )
params || = { :copy = > { } }
logger . debug " starting import "
@full_migration_hash = data
@external_url_hash = { }
@migration_results = [ ]
( data [ 'web_link_categories' ] || [ ] ) . map { | c | c [ 'links' ] } . flatten . each do | link |
@external_url_hash [ link [ 'link_id' ] ] = link
end
ActiveRecord :: Base . skip_touch_context
@imported_migration_items = [ ]
# These only need to be processed once
2011-04-24 08:51:53 +08:00
Attachment . skip_media_object_creation do
2011-05-14 06:36:20 +08:00
process_migration_files ( data , migration ) ; migration . fast_update_progress ( 18 )
2011-04-24 08:51:53 +08:00
Attachment . process_migration ( data , migration ) ; migration . fast_update_progress ( 20 )
2011-04-28 00:25:47 +08:00
mo_attachments = self . imported_migration_items . find_all { | i | i . is_a? ( Attachment ) && i . media_entry_id . present? }
2011-04-24 08:51:53 +08:00
# we'll wait synchronously for the media objects to be uploaded, so that
# we have the media_ids that we need later.
2011-04-28 00:25:47 +08:00
unless mo_attachments . blank?
2011-04-28 23:23:03 +08:00
import_media_objects_and_attachments ( mo_attachments , migration )
2011-04-28 00:25:47 +08:00
end
2011-04-24 08:51:53 +08:00
end
2011-04-28 00:25:47 +08:00
2011-04-26 15:11:56 +08:00
# needs to happen after the files are processed, so that they are available in the syllabus
import_settings_from_migration ( data ) ; migration . fast_update_progress ( 21 )
2011-04-28 00:25:47 +08:00
2011-04-24 08:51:53 +08:00
migration . fast_update_progress ( 30 )
2011-04-16 10:48:44 +08:00
question_data = AssessmentQuestion . process_migration ( data , migration ) ; migration . fast_update_progress ( 35 )
Group . process_migration ( data , migration ) ; migration . fast_update_progress ( 36 )
LearningOutcome . process_migration ( data , migration ) ; migration . fast_update_progress ( 37 )
Rubric . process_migration ( data , migration ) ; migration . fast_update_progress ( 38 )
2011-04-12 21:41:47 +08:00
@assignment_group_no_drop_assignments = { }
2011-04-16 10:48:44 +08:00
AssignmentGroup . process_migration ( data , migration ) ; migration . fast_update_progress ( 39 )
ExternalFeed . process_migration ( data , migration ) ; migration . fast_update_progress ( 39 . 5 )
GradingStandard . process_migration ( data , migration ) ; migration . fast_update_progress ( 40 )
Quiz . process_migration ( data , migration , question_data ) ; migration . fast_update_progress ( 50 )
2011-09-17 07:09:59 +08:00
ContextExternalTool . process_migration ( data , migration ) ; migration . fast_update_progress ( 54 )
2011-02-01 09:57:29 +08:00
2011-09-16 07:54:34 +08:00
#These need to be ran twice because they can reference each other
DiscussionTopic . process_migration ( data , migration ) ; migration . fast_update_progress ( 55 )
WikiPage . process_migration ( data , migration ) ; migration . fast_update_progress ( 60 )
Assignment . process_migration ( data , migration ) ; migration . fast_update_progress ( 65 )
ContextModule . process_migration ( data , migration ) ; migration . fast_update_progress ( 70 )
# and second time...
DiscussionTopic . process_migration ( data , migration ) ; migration . fast_update_progress ( 75 )
WikiPage . process_migration ( data , migration ) ; migration . fast_update_progress ( 80 )
Assignment . process_migration ( data , migration ) ; migration . fast_update_progress ( 85 )
2011-02-01 09:57:29 +08:00
#These aren't referenced by anything, but reference other things
2011-09-16 07:54:34 +08:00
CalendarEvent . process_migration ( data , migration ) ; migration . fast_update_progress ( 90 )
WikiPage . process_migration_course_outline ( data , migration ) ; migration . fast_update_progress ( 95 )
2011-02-01 09:57:29 +08:00
2011-06-18 00:58:18 +08:00
begin
#Adjust dates
if bool_res ( params [ :copy ] [ :shift_dates ] )
shift_options = ( bool_res ( params [ :copy ] [ :shift_dates ] ) rescue false ) ? params [ :copy ] : { }
shift_options = shift_date_options ( self , shift_options )
@imported_migration_items . each do | event |
if event . is_a? ( Assignment )
event . due_at = shift_date ( event . due_at , shift_options )
event . lock_at = shift_date ( event . lock_at , shift_options )
event . unlock_at = shift_date ( event . unlock_at , shift_options )
event . peer_reviews_due_at = shift_date ( event . peer_reviews_due_at , shift_options )
event . save_without_broadcasting!
elsif event . is_a? ( DiscussionTopic )
event . delayed_post_at = shift_date ( event . delayed_post_at , shift_options )
event . save_without_broadcasting!
elsif event . is_a? ( CalendarEvent )
event . start_at = shift_date ( event . start_at , shift_options )
event . end_at = shift_date ( event . end_at , shift_options )
event . save_without_broadcasting!
elsif event . is_a? ( Quiz )
event . due_at = shift_date ( event . due_at , shift_options )
event . lock_at = shift_date ( event . lock_at , shift_options )
event . unlock_at = shift_date ( event . unlock_at , shift_options )
event . save!
end
2011-02-01 09:57:29 +08:00
end
end
2011-06-18 00:58:18 +08:00
rescue
migration . add_warning ( " Couldn't adjust the due dates. " , $! )
2011-02-01 09:57:29 +08:00
end
migration . progress = 100
migration . migration_settings || = { }
migration . migration_settings [ :imported_assets ] = @imported_migration_items . map ( & :asset_string )
migration . workflow_state = :imported
migration . save
ActiveRecord :: Base . skip_touch_context ( false )
self . touch
@imported_migration_items
end
2011-06-18 00:58:18 +08:00
attr_accessor :imported_migration_items , :full_migration_hash , :external_url_hash
attr_accessor :folder_name_lookups , :attachment_path_id_lookup , :assignment_group_no_drop_assignments
2011-04-12 21:41:47 +08:00
def import_settings_from_migration ( data )
2011-04-15 00:40:32 +08:00
return unless data [ :course ]
settings = data [ :course ]
2011-09-14 01:51:33 +08:00
self . conclude_at = Canvas :: Migration :: MigratorHelper . get_utc_time_from_timestamp ( settings [ :conclude_at ] ) if settings [ :conclude_at ]
self . start_at = Canvas :: Migration :: MigratorHelper . get_utc_time_from_timestamp ( settings [ :start_at ] ) if settings [ :start_at ]
2011-04-15 00:40:32 +08:00
self . syllabus_body = ImportedHtmlConverter . convert ( settings [ :syllabus_body ] , self ) if settings [ :syllabus_body ]
atts = Course . clonable_attributes
2011-09-14 01:51:33 +08:00
atts -= Canvas :: Migration :: MigratorHelper :: COURSE_NO_COPY_ATTS
2011-04-15 00:40:32 +08:00
settings . slice ( * atts . map ( & :to_s ) ) . each do | key , val |
self . send ( " #{ key } = " , val )
2011-04-12 21:41:47 +08:00
end
end
2011-04-28 00:25:47 +08:00
2011-04-28 23:23:03 +08:00
def import_media_objects_and_attachments ( mo_attachments , migration )
MediaObject . add_media_files ( mo_attachments , true )
2011-04-28 00:25:47 +08:00
# attachments in /media_objects were created on export, soley to
# download and include a media object in the export. now that they've
# been sent to kaltura, we can remove them.
2011-04-29 01:08:25 +08:00
failed_uploads , mo_attachments = mo_attachments . partition { | a | a . media_object . nil? }
unless failed_uploads . empty?
2011-07-14 04:18:55 +08:00
migration . add_warning ( t ( 'errors.import.kaltura' , " There was an error importing Kaltura media objects. Some or all of your media was not imported. " ) , failed_uploads . map ( & :id ) . join ( ',' ) )
2011-04-29 01:08:25 +08:00
end
2011-04-28 00:25:47 +08:00
to_remove = mo_attachments . find_all { | a | a . full_path . starts_with? ( File . join ( Folder :: ROOT_FOLDER_NAME , CC :: CCHelper :: MEDIA_OBJECTS_FOLDER ) + '/' ) }
to_remove . each do | a |
a . destroy ( false )
end
2011-04-28 06:45:31 +08:00
folder = to_remove . last . folder if to_remove . last
2011-04-28 00:25:47 +08:00
if folder && folder . file_attachments . active . count == 0 && folder . active_sub_folders . count == 0
folder . destroy
end
2011-04-28 23:23:03 +08:00
rescue Exception = > e
2011-06-22 00:23:08 +08:00
migration . add_warning ( t ( 'errors.import.kaltura' , " There was an error importing Kaltura media objects. Some or all of your media was not imported. " ) , e )
2011-04-28 00:25:47 +08:00
end
2011-02-01 09:57:29 +08:00
def backup_to_json
backup . to_json
end
def backup
res = [ ]
res += self . folders . active
res += self . attachments . active
res += self . assignment_groups . active
res += self . assignments . active
res += self . submissions
res += self . quizzes
res += self . discussion_topics . active
res += self . discussion_entries . active
res += self . wiki . wiki_pages . active
res += self . calendar_events . active
res
end
def may_have_links_to_migrate ( item )
@to_migrate_links || = [ ]
@to_migrate_links << item
end
def map_merge ( old_item , new_item )
@merge_mappings || = { }
@merge_mappings [ old_item . asset_string ] = new_item && new_item . id
end
def merge_mapped_id ( old_item )
@merge_mappings || = { }
return nil unless old_item
return @merge_mappings [ old_item ] if old_item . is_a? ( String )
@merge_mappings [ old_item . asset_string ]
end
def same_dates? ( old , new , columns )
old && new && columns . all? { | column |
old . respond_to? ( column ) && new . respond_to? ( column ) && old . send ( column ) == new . send ( column )
}
end
attr_accessor :merge_mappings
2011-09-07 22:02:47 +08:00
def merge_into_course ( course , options , course_import = nil )
2011-02-01 09:57:29 +08:00
@merge_mappings = { }
@merge_results = [ ]
to_shift_dates = [ ]
@to_migrate_links = [ ]
added_items = [ ]
delete_placeholder = nil
2011-02-19 05:15:07 +08:00
if bool_res ( options [ :course_settings ] )
#Copy the course settings too
course . attributes . slice ( * Course . clonable_attributes . map ( & :to_s ) ) . keys . each do | attr |
self . send ( " #{ attr } = " , course . send ( attr ) )
end
2011-07-07 00:12:28 +08:00
may_have_links_to_migrate ( self )
2011-02-19 05:15:07 +08:00
self . save
end
2011-06-22 00:23:08 +08:00
if self . assignment_groups . length == 1 && self . assignment_groups . first . name == t ( '#assignment_group.default_name' , " Assignments " ) && self . assignment_groups . first . assignments . empty?
2011-02-01 09:57:29 +08:00
delete_placeholder = self . assignment_groups . first
self . group_weighting_scheme = course . group_weighting_scheme
elsif self . assignment_groups . length == 0
self . group_weighting_scheme = course . group_weighting_scheme
end
# There are groups to migrate
course . assignment_groups . active . each do | group |
if bool_res ( options [ :everything ] ) || bool_res ( options [ :all_assignments ] ) || bool_res ( options [ group . asset_string . to_sym ] )
new_group = group . clone_for ( self )
added_items << new_group
new_group . save_without_broadcasting!
map_merge ( group , new_group )
end
end
course . assignments . no_graded_quizzes_or_topics . active . select { | a | a . assignment_group_id } . each do | assignment |
course_import . tick ( 15 ) if course_import
if bool_res ( options [ :everything ] ) || bool_res ( options [ :all_assignments ] ) || bool_res ( options [ assignment . asset_string . to_sym ] )
new_assignment = assignment . clone_for ( self , nil , :migrate = > false )
to_shift_dates << new_assignment if new_assignment . clone_updated || same_dates? ( assignment , new_assignment , [ :due_at , :lock_at , :unlock_at , :peer_reviews_due_at ] )
added_items << new_assignment
new_assignment . save_without_broadcasting!
map_merge ( assignment , new_assignment )
end
end
# next, attachments
map_merge ( Folder . root_folders ( course ) . first , Folder . root_folders ( self ) . first )
course . attachments . all ( :conditions = > " file_state <> 'deleted' " ) . each do | file |
course_import . tick ( 30 ) if course_import
if bool_res ( options [ :everything ] ) || bool_res ( options [ :all_files ] ) || bool_res ( options [ file . asset_string . to_sym ] )
new_file = file . clone_for ( self )
added_items << new_file
new_folder_id = merge_mapped_id ( file . folder )
# make sure the file has somewhere to go
if ! new_folder_id
# gather mapping of needed folders from old course to new course
old_folders = [ ]
old_folders << file . folder
new_folders = [ ]
new_folders << old_folders . last . clone_for ( self , nil , options . merge ( { :include_subcontent = > false } ) )
while old_folders . last . parent_folder && old_folders . last . parent_folder . parent_folder_id && ! merge_mapped_id ( old_folders . last . parent_folder )
old_folders << old_folders . last . parent_folder
new_folders << old_folders . last . clone_for ( self , nil , options . merge ( { :include_subcontent = > false } ) )
end
added_items += new_folders
old_folders . reverse!
new_folders . reverse!
# try to use folders that already match if possible
final_new_folders = [ ]
parent_folder = Folder . root_folders ( self ) . first
old_folders . each_with_index do | folder , idx |
if f = parent_folder . active_sub_folders . find_by_name ( folder . name )
final_new_folders << f
else
final_new_folders << new_folders [ idx ]
end
parent_folder = final_new_folders . last
end
# add or update the folder structure needed for the file
final_new_folders . first . parent_folder_id || =
merge_mapped_id ( old_folders . first . parent_folder ) ||
Folder . root_folders ( self ) . first . id
old_folders . each_with_index do | folder , idx |
final_new_folders [ idx ] . save!
map_merge ( folder , final_new_folders [ idx ] )
final_new_folders [ idx + 1 ] . parent_folder_id || = final_new_folders [ idx ] . id if final_new_folders [ idx + 1 ]
end
new_folder_id = merge_mapped_id ( file . folder )
end
new_file . folder_id = new_folder_id
new_file . save_without_broadcasting!
map_merge ( file , new_file )
end
end
course . discussion_topics . active . each do | topic |
course_import . tick ( 40 ) if course_import
if bool_res ( options [ :everything ] ) || bool_res ( options [ :all_topics ] ) || bool_res ( options [ topic . asset_string . to_sym ] ) || ( topic . assignment_id && bool_res ( options [ " assignment_ #{ topic . assignment_id } " ] ) )
2011-02-08 05:27:56 +08:00
include_entries = options [ " discussion_topic_ #{ topic . id } _entries " ] == " 1 "
new_topic = topic . clone_for ( self , nil , :migrate = > bool_res ( options [ " #{ topic . asset_string } _entries " . to_sym ] ) , :include_entries = > include_entries )
2011-02-01 09:57:29 +08:00
to_shift_dates << new_topic if new_topic . delayed_post_at && ( new_topic . clone_updated || same_dates? ( topic , new_topic , [ :delayed_post_at ] ) )
to_shift_dates << new_topic . assignment if new_topic . assignment && ( new_topic . assignment_clone_updated || same_dates? ( topic . assignment , new_topic . assignment , [ :due_at , :lock_at , :unlock_at , :peer_reviews_due_at ] ) )
added_items << new_topic
added_items << new_topic . assignment if new_topic . assignment
new_topic . save_without_broadcasting!
map_merge ( topic , new_topic )
end
end
course . calendar_events . active . each do | event |
course_import . tick ( 50 ) if course_import
if bool_res ( options [ :everything ] ) || bool_res ( options [ :all_calendar_events ] ) || bool_res ( options [ event . asset_string . to_sym ] )
new_event = event . clone_for ( self , nil , :migrate = > false )
to_shift_dates << new_event if new_event . clone_updated || same_dates? ( event , new_event , [ :start_at , :end_at ] )
added_items << new_event
new_event . save_without_broadcasting!
map_merge ( event , new_event )
end
end
course . quizzes . active . each do | quiz |
course_import . tick ( 60 ) if course_import
if bool_res ( options [ :everything ] ) || bool_res ( options [ :all_quizzes ] ) || bool_res ( options [ quiz . asset_string . to_sym ] ) || ( quiz . assignment_id && bool_res ( [ " assignment_ #{ quiz . assignment_id } " ] ) )
new_quiz = quiz . clone_for ( self )
to_shift_dates << new_quiz if new_quiz . clone_updated || same_dates? ( quiz , new_quiz , [ :due_at , :lock_at , :unlock_at ] )
added_items << new_quiz
added_items << new_quiz . assignment if new_quiz . assignment
new_quiz . save!
map_merge ( quiz , new_quiz )
end
end
course . wiki_namespaces . each do | wiki_namespace |
wiki_namespace . wiki . wiki_pages . each do | page |
course_import . tick ( 70 ) if course_import
if bool_res ( options [ :everything ] ) || bool_res ( options [ :all_wiki_pages ] ) || bool_res ( options [ page . asset_string . to_sym ] )
2011-06-08 22:21:24 +08:00
if page . title . blank?
next if page . body . blank?
2011-06-22 00:23:08 +08:00
page . title = t ( '#wiki_page.missing_name' , " Unnamed Page " )
2011-06-08 22:21:24 +08:00
end
2011-02-01 09:57:29 +08:00
new_page = page . clone_for ( self , nil , :migrate = > false , :old_context = > course )
added_items << new_page
new_page . wiki_id = self . wiki . id
new_page . ensure_unique_title
log_merge_result ( " Wiki Page \" #{ page . title } \" renamed to \" #{ new_page . title } \" " ) if new_page . title != page . title
new_page . save_without_broadcasting!
map_merge ( page , new_page )
end
end
end
course . context_modules . active . each do | mod |
course_import . tick ( 80 ) if course_import
if bool_res ( options [ :everything ] ) || bool_res ( options [ :all_modules ] ) || bool_res ( options [ mod . asset_string . to_sym ] )
new_mod = mod . clone_for ( self )
to_shift_dates << new_mod if new_mod . clone_updated || same_dates? ( mod , new_mod , [ :unlock_at , :start_at , :end_at ] )
new_mod . save!
added_items << new_mod
map_merge ( mod , new_mod )
end
end
2011-05-25 22:36:00 +08:00
orig_root = LearningOutcomeGroup . default_for ( course )
new_root = LearningOutcomeGroup . default_for ( self )
orig_root . sorted_content . each do | item |
course_import . tick ( 85 ) if course_import
use_outcome = lambda { | lo | bool_res ( options [ :everything ] ) || bool_res ( options [ :all_outcomes ] ) || bool_res ( options [ lo . asset_string . to_sym ] ) }
if item . is_a? LearningOutcome
next unless use_outcome [ item ]
lo = item . clone_for ( self , new_root )
added_items << lo
else
f = item . clone_for ( self , new_root , use_outcome )
added_items << f if f
end
end
2011-02-01 09:57:29 +08:00
# Groups could be created by objects with attached assignments as well. (like quizzes/topics)
# So don't delete the placeholder until everything has been cloned
delete_placeholder . destroy if delete_placeholder && self . assignment_groups . length > 1
@to_migrate_links . uniq . each do | obj |
2011-05-25 22:36:00 +08:00
course_import . tick ( 90 ) if course_import
2011-02-01 09:57:29 +08:00
if obj . is_a? ( Assignment )
obj . description = migrate_content_links ( obj . description , course )
elsif obj . is_a? ( CalendarEvent )
obj . description = migrate_content_links ( obj . description , course )
elsif obj . is_a? ( DiscussionTopic )
obj . message = migrate_content_links ( obj . message , course )
obj . discussion_entries . each do | entry |
entry . message = migrate_content_links ( obj . message , course )
entry . save_without_broadcasting!
end
elsif obj . is_a? ( WikiPage )
obj . body = migrate_content_links ( obj . body , course )
elsif obj . is_a? ( Quiz )
obj . description = migrate_content_links ( obj . description , course )
2011-07-07 00:12:28 +08:00
elsif obj . is_a? ( Course )
obj . syllabus_body = migrate_content_links ( obj . syllabus_body , course )
2011-02-01 09:57:29 +08:00
end
obj . save_without_broadcasting! rescue obj . save!
end
if ! to_shift_dates . empty? && bool_res ( options [ :shift_dates ] )
log_merge_result ( " Moving events to new dates " )
shift_options = ( bool_res ( options [ :shift_dates ] ) rescue false ) ? options : { }
2011-05-02 23:25:29 +08:00
shift_options = shift_date_options ( course , shift_options )
2011-02-01 09:57:29 +08:00
to_shift_dates . uniq . each do | event |
course_import . tick ( 100 ) if course_import
if event . is_a? ( Assignment )
2011-05-02 23:25:29 +08:00
event . due_at = shift_date ( event . due_at , shift_options )
event . lock_at = shift_date ( event . lock_at , shift_options )
event . unlock_at = shift_date ( event . unlock_at , shift_options )
event . peer_reviews_due_at = shift_date ( event . peer_reviews_due_at , shift_options )
2011-02-01 09:57:29 +08:00
elsif event . is_a? ( DiscussionTopic )
2011-05-02 23:25:29 +08:00
event . delayed_post_at = shift_date ( event . delayed_post_at , shift_options )
2011-02-01 09:57:29 +08:00
log_merge_result ( " The Topic \" #{ event . title } \" won't be posted until #{ event . delayed_post_at . to_s } " )
elsif event . is_a? ( CalendarEvent )
2011-05-02 23:25:29 +08:00
event . start_at = shift_date ( event . start_at , shift_options )
event . end_at = shift_date ( event . end_at , shift_options )
2011-02-01 09:57:29 +08:00
elsif event . is_a? ( Quiz )
2011-05-02 23:25:29 +08:00
event . due_at = shift_date ( event . due_at , shift_options )
event . lock_at = shift_date ( event . lock_at , shift_options )
event . unlock_at = shift_date ( event . unlock_at , shift_options )
2011-02-01 09:57:29 +08:00
elsif event . is_a? ( ContextModule )
2011-05-02 23:25:29 +08:00
event . unlock_at = shift_date ( event . unlock_at , shift_options )
event . start_at = shift_date ( event . start_at , shift_options )
event . end_at = shift_date ( event . end_at , shift_options )
2011-02-01 09:57:29 +08:00
end
event . respond_to? ( :save_without_broadcasting! ) ? event . save_without_broadcasting! : event . save!
end
2011-05-03 04:29:32 +08:00
self . start_at || = shift_options [ :new_start_date ]
self . conclude_at || = shift_options [ :new_end_date ]
2011-02-01 09:57:29 +08:00
end
2011-05-03 04:29:32 +08:00
self . save
2011-02-01 09:57:29 +08:00
if course_import
course_import . added_item_codes = added_items . map { | i | i . asset_string }
course_import . log = merge_results
course_import . save!
end
added_items . map { | i | i . asset_string }
end
def self . clonable_attributes
2011-05-03 04:29:32 +08:00
[ :group_weighting_scheme , :grading_standard_id , :is_public ,
:publish_grades_immediately , :allow_student_wiki_edits ,
:allow_student_assignment_edits , :hashtag , :show_public_context_messages ,
2011-09-14 01:21:09 +08:00
:syllabus_body , :allow_student_forum_attachments ,
2011-05-03 04:29:32 +08:00
:default_wiki_editing_roles , :allow_student_organized_groups ,
:default_view , :show_all_discussion_entries , :open_enrollment ,
:storage_quota , :tab_configuration , :allow_wiki_comments ,
2011-08-17 04:21:36 +08:00
:turnitin_comments , :self_enrollment , :license , :indexed , :settings ]
2011-02-01 09:57:29 +08:00
end
2011-05-03 04:29:32 +08:00
2011-02-01 09:57:29 +08:00
def clone_for ( account , opts = { } )
new_course = Course . new
root_account = account . root_account || account
self . attributes . delete_if { | k , v | [ :id , :section , :account_id , :workflow_state , :created_at , :updated_at , :root_account_id , :enrollment_term_id , :sis_source_id , :sis_batch_id ] . include? ( k . to_sym ) } . each do | key , val |
new_course . send ( " #{ key } = " , val )
end
new_course . workflow_state = 'created'
new_course . name = opts [ :name ] if opts [ :name ]
new_course . account_id = account . id
new_course . root_account_id = root_account . id
new_course . enrollment_term_id = opts [ :enrollment_term_id ]
2011-06-08 00:31:14 +08:00
new_course . abstract_course_id = self . abstract_course_id
2011-02-01 09:57:29 +08:00
new_course . save!
if opts [ :copy_content ]
new_course . send_later ( :merge_into_course , self , :everything = > true )
end
new_course
end
def assert_assignment_group
has_group = Rails . cache . fetch ( [ 'has_assignment_group' , self . id ] . cache_key ) do
self . assignment_groups . active . count > 0
end
if ! has_group
2011-06-22 00:23:08 +08:00
group = self . assignment_groups . new :name = > t ( '#assignment_group.default_name' , " Assignments " ) , :position = > 1
2011-02-01 09:57:29 +08:00
group . save
end
end
2011-05-02 23:25:29 +08:00
def shift_date_options ( course , options = { } )
result = { }
result [ :old_start_date ] = Date . parse ( options [ :old_start_date ] ) rescue course . real_start_date
result [ :old_end_date ] = Date . parse ( options [ :old_end_date ] ) rescue course . real_end_date
result [ :new_start_date ] = Date . parse ( options [ :new_start_date ] ) rescue self . real_start_date
result [ :new_end_date ] = Date . parse ( options [ :new_end_date ] ) rescue self . real_end_date
2011-09-13 01:09:53 +08:00
result [ :day_substitutions ] = options [ :day_substitutions ]
2011-05-02 23:25:29 +08:00
result
end
def shift_date ( time , options = { } )
2011-02-01 09:57:29 +08:00
return nil unless time
time = ActiveSupport :: TimeWithZone . new ( time . utc , Time . zone )
old_date = time . to_date
new_date = old_date . clone
2011-05-02 23:25:29 +08:00
old_start_date = options [ :old_start_date ]
old_end_date = options [ :old_end_date ]
new_start_date = options [ :new_start_date ]
new_end_date = options [ :new_end_date ]
2011-02-01 09:57:29 +08:00
return time unless old_start_date && old_end_date && new_start_date && new_end_date
old_full_diff = old_end_date - old_start_date
old_event_diff = old_date - old_start_date
old_event_percent = old_full_diff > 0 ? old_event_diff . to_f / old_full_diff . to_f : 0
new_full_diff = new_end_date - new_start_date
new_event_diff = ( new_full_diff . to_f * old_event_percent ) . to_i
new_date = new_start_date + new_event_diff
options [ :day_substitutions ] || = { }
options [ :day_substitutions ] [ old_date . wday . to_s ] || = old_date . wday . to_s
if options [ :day_substitutions ] && options [ :day_substitutions ] [ old_date . wday . to_s ]
if new_date . wday != options [ :day_substitutions ] [ old_date . wday . to_s ] . to_i
new_date += ( options [ :day_substitutions ] [ old_date . wday . to_s ] . to_i - new_date . wday ) % 7
new_date -= 7 unless new_date - 7 < new_start_date
end
end
new_time = ActiveSupport :: TimeWithZone . new ( Time . utc ( new_date . year , new_date . month , new_date . day , ( time . hour rescue 0 ) , ( time . min rescue 0 ) ) , Time . zone ) - Time . zone . utc_offset
log_merge_result ( " Events for #{ old_date . to_s } moved to #{ new_date . to_s } " )
new_time
end
def real_start_date
return self . start_at . to_date if self . start_at
all_dates . min
end
def all_dates
( self . calendar_events . active + self . assignments . active ) . inject ( [ ] ) { | list , e |
list << e . end_at if e . end_at
list << e . start_at if e . start_at
list
} . compact . flatten . map { | d | d . to_date } . uniq rescue [ ]
end
def real_end_date
return self . conclude_at . to_date if self . conclude_at
all_dates . max
end
def is_a_context?
true
end
def self . serialization_excludes ; [ :uuid ] ; end
def page_views_by_day ( options = { } )
conditions = {
:context_id = > self . id ,
:context_type = > self . class . to_s
}
if options [ :dates ]
conditions . merge! ( {
2011-09-27 12:53:08 +08:00
:created_at = > ( options [ :dates ] . first ) .. ( options [ :dates ] . last )
2011-02-01 09:57:29 +08:00
} )
end
PageView . count (
:group = > " date(created_at) " ,
:conditions = > conditions
)
end
memoize :page_views_by_day
2011-08-19 06:03:33 +08:00
def section_visibilities_for ( user )
Rails . cache . fetch ( [ 'section_visibilities_for' , user , self ] . cache_key ) do
Enrollment . find ( :all , :select = > " course_section_id, limit_priveleges_to_course_section, type, associated_user_id " , :conditions = > [ 'user_id = ? AND course_id = ? AND workflow_state != ?' , user . id , self . id , 'deleted' ] ) . map { | e | { :course_section_id = > e . course_section_id , :limit_priveleges_to_course_section = > e . limit_priveleges_to_course_section , :type = > e . type , :associated_user_id = > e . associated_user_id } }
2011-02-01 09:57:29 +08:00
end
2011-08-19 06:03:33 +08:00
end
2011-09-30 05:36:13 +08:00
memoize :section_visibilities_for
2011-08-19 06:03:33 +08:00
def visibility_limited_to_course_sections? ( user , visibilities = section_visibilities_for ( user ) )
! visibilities . any? { | s | ! s [ :limit_priveleges_to_course_section ] }
2011-02-01 09:57:29 +08:00
end
2011-06-30 04:05:59 +08:00
# returns a scope, not an array of users/enrollments
2011-02-01 09:57:29 +08:00
def students_visible_to ( user , include_priors = false )
2011-06-30 04:05:59 +08:00
enrollments_visible_to ( user , include_priors , true )
end
def enrollments_visible_to ( user , include_priors = false , return_users = false )
2011-08-19 06:03:33 +08:00
visibilities = section_visibilities_for ( user )
2011-06-30 04:05:59 +08:00
if return_users
scope = include_priors ? self . all_students : self . students
else
scope = include_priors ? self . all_student_enrollments : self . student_enrollments
end
2011-08-19 06:03:33 +08:00
# See also Users#messageable_users (same logic used to get users across multiple courses)
case enrollment_visibility_level_for ( user , visibilities )
when :full then scope
when :sections then scope . scoped ( { :conditions = > " enrollments.course_section_id IN ( #{ visibilities . map { | s | s [ :course_section_id ] } . join ( " , " ) } ) " } )
when :restricted then scope . scoped ( { :conditions = > " enrollments.user_id IN ( #{ ( visibilities . map { | s | s [ :associated_user_id ] } . compact + [ user . id ] ) . join ( " , " ) } ) " } )
else scope . scoped ( { :conditions = > " FALSE " } )
end
end
2011-09-30 05:36:13 +08:00
def sections_visible_to ( user , sections = active_course_sections )
visibilities = section_visibilities_for ( user )
2011-10-13 06:49:30 +08:00
section_ids = visibilities . map { | s | s [ :course_section_id ] }
2011-09-30 05:36:13 +08:00
case enrollment_visibility_level_for ( user , visibilities )
when :full
2011-10-13 06:49:30 +08:00
if visibilities . all? { | v | [ 'StudentEnrollment' , 'ObserverEnrollment' ] . include? v [ :type ] }
return sections . find_all_by_id ( section_ids )
else
return sections
end
2011-09-30 05:36:13 +08:00
when :sections
2011-10-13 06:49:30 +08:00
return sections . find_all_by_id ( section_ids )
2011-09-30 05:36:13 +08:00
end
2011-10-01 07:07:35 +08:00
[ ]
2011-09-30 05:36:13 +08:00
end
2011-08-19 06:03:33 +08:00
def enrollment_visibility_level_for ( user , visibilities = section_visibilities_for ( user ) )
if visibilities . empty? # i.e. not enrolled
2011-08-16 06:34:57 +08:00
if self . grants_rights? ( user , nil , :manage_grades , :manage_students , :manage_admin_users , :read_roster )
2011-08-19 06:03:33 +08:00
:full
2011-02-01 09:57:29 +08:00
else
2011-08-19 06:03:33 +08:00
:none
2011-02-01 09:57:29 +08:00
end
2011-08-19 06:03:33 +08:00
elsif visibilities . all? { | e | e [ :type ] == 'ObserverEnrollment' }
:restricted # e.g. observers shouldn't see anyone but the observed
elsif visibility_limited_to_course_sections? ( user , visibilities )
:sections
2011-02-01 09:57:29 +08:00
else
2011-08-19 06:03:33 +08:00
:full
2011-02-01 09:57:29 +08:00
end
end
def page_view_data ( options = { } )
# if they dont supply a date range then use the first day returned by page_views_by_day (which should be the first day that there is pageview statistics gathered)
dates = options [ :dates ] . nil? ? [ page_views_by_day . sort . first . first . to_datetime , Time . now ] : options [ :dates ]
days = [ ]
dates . first . to_datetime . upto ( dates . last ) do | d |
# this * 1000 part is because the Highcharts expects something like what Date.UTC(2006, 2, 28) would give you,
# which is MILLISECONDS from the unix epoch, ruby's to_f gives you SECONDS since then.
days << [ ( d . at_beginning_of_day . to_f * 1000 ) . to_i , page_views_by_day [ d . to_date . to_s ] . to_i ]
end
days
end
memoize :page_view_data
def unpublished?
self . created? || self . claimed?
end
def only_wiki_is_public
self . respond_to? ( :wiki_is_public ) && self . wiki_is_public && ! self . is_public
end
def reply_from ( opts )
user = opts [ :user ]
message = opts [ :text ] . strip
user = nil unless user && self . context . users . include? ( user )
if ! user
raise " Only comment participants may reply to messages "
elsif ! message || message . empty?
raise " Message body cannot be blank "
else
2011-07-31 14:36:10 +08:00
recipients = self . teachers . map ( & :id ) - [ user . id ]
conversation = user . initiate_conversation ( recipients )
conversation . add_message ( message )
2011-02-01 09:57:29 +08:00
end
end
def tab_configuration
super . map { | h | h . with_indifferent_access } rescue [ ]
end
TAB_HOME = 0
TAB_SYLLABUS = 1
TAB_PAGES = 2
TAB_ASSIGNMENTS = 3
TAB_QUIZZES = 4
TAB_GRADES = 5
TAB_PEOPLE = 6
TAB_GROUPS = 7
TAB_DISCUSSIONS = 8
TAB_CHAT = 9
TAB_MODULES = 10
TAB_FILES = 11
TAB_CONFERENCES = 12
TAB_SETTINGS = 13
TAB_ANNOUNCEMENTS = 14
TAB_OUTCOMES = 15
TAB_COLLABORATIONS = 16
2011-06-22 00:23:08 +08:00
2011-02-01 09:57:29 +08:00
def self . default_tabs
[
2011-08-06 05:14:21 +08:00
{ :id = > TAB_HOME , :label = > t ( '#tabs.home' , " Home " ) , :css_class = > 'home' , :href = > :course_path } ,
{ :id = > TAB_ANNOUNCEMENTS , :label = > t ( '#tabs.announcements' , " Announcements " ) , :css_class = > 'announcements' , :href = > :course_announcements_path } ,
{ :id = > TAB_ASSIGNMENTS , :label = > t ( '#tabs.assignments' , " Assignments " ) , :css_class = > 'assignments' , :href = > :course_assignments_path } ,
{ :id = > TAB_DISCUSSIONS , :label = > t ( '#tabs.discussions' , " Discussions " ) , :css_class = > 'discussions' , :href = > :course_discussion_topics_path } ,
{ :id = > TAB_GRADES , :label = > t ( '#tabs.grades' , " Grades " ) , :css_class = > 'grades' , :href = > :course_grades_path } ,
{ :id = > TAB_PEOPLE , :label = > t ( '#tabs.people' , " People " ) , :css_class = > 'people' , :href = > :course_users_path } ,
{ :id = > TAB_CHAT , :label = > t ( '#tabs.chat' , " Chat " ) , :css_class = > 'chat' , :href = > :course_chat_path } ,
{ :id = > TAB_PAGES , :label = > t ( '#tabs.pages' , " Pages " ) , :css_class = > 'pages' , :href = > :course_wiki_pages_path } ,
{ :id = > TAB_FILES , :label = > t ( '#tabs.files' , " Files " ) , :css_class = > 'files' , :href = > :course_files_path } ,
{ :id = > TAB_SYLLABUS , :label = > t ( '#tabs.syllabus' , " Syllabus " ) , :css_class = > 'syllabus' , :href = > :syllabus_course_assignments_path } ,
{ :id = > TAB_OUTCOMES , :label = > t ( '#tabs.outcomes' , " Outcomes " ) , :css_class = > 'outcomes' , :href = > :course_outcomes_path } ,
{ :id = > TAB_QUIZZES , :label = > t ( '#tabs.quizzes' , " Quizzes " ) , :css_class = > 'quizzes' , :href = > :course_quizzes_path } ,
{ :id = > TAB_MODULES , :label = > t ( '#tabs.modules' , " Modules " ) , :css_class = > 'modules' , :href = > :course_context_modules_path } ,
{ :id = > TAB_CONFERENCES , :label = > t ( '#tabs.conferences' , " Conferences " ) , :css_class = > 'conferences' , :href = > :course_conferences_path } ,
{ :id = > TAB_COLLABORATIONS , :label = > t ( '#tabs.collaborations' , " Collaborations " ) , :css_class = > 'collaborations' , :href = > :course_collaborations_path } ,
2011-08-31 04:32:58 +08:00
{ :id = > TAB_SETTINGS , :label = > t ( '#tabs.settings' , " Settings " ) , :css_class = > 'settings' , :href = > :course_settings_path } ,
2011-02-01 09:57:29 +08:00
]
end
def tabs_available ( user = nil , opts = { } )
# We will by default show everything in default_tabs, unless the teacher has configured otherwise.
tabs = self . tab_configuration . compact
default_tabs = Course . default_tabs
tabs = tabs . map do | tab |
default_tab = default_tabs . find { | t | t [ :id ] == tab [ :id ] }
if default_tab
tab [ :label ] = default_tab [ :label ]
tab [ :href ] = default_tab [ :href ]
2011-08-06 05:14:21 +08:00
tab [ :css_class ] = default_tab [ :css_class ]
2011-02-01 09:57:29 +08:00
default_tabs . delete_if { | t | t [ :id ] == tab [ :id ] }
tab
else
# Remove any tabs we don't know about in default_tabs (in case we removed them or something, like Groups)
nil
end
end
tabs . compact!
tabs += default_tabs
tabs . each do | tab |
2011-06-22 00:23:08 +08:00
tab [ :hidden_unused ] = true if tab [ :id ] == TAB_MODULES && ! active_record_types [ :modules ]
tab [ :hidden_unused ] = true if tab [ :id ] == TAB_FILES && ! active_record_types [ :files ]
tab [ :hidden_unused ] = true if tab [ :id ] == TAB_QUIZZES && ! active_record_types [ :quizzes ]
tab [ :hidden_unused ] = true if tab [ :id ] == TAB_ASSIGNMENTS && ! active_record_types [ :assignments ]
tab [ :hidden_unused ] = true if tab [ :id ] == TAB_PAGES && ! active_record_types [ :pages ] && ! allow_student_wiki_edits
tab [ :hidden_unused ] = true if tab [ :id ] == TAB_CONFERENCES && ! active_record_types [ :conferences ] && ! self . grants_right? ( user , nil , :create_conferences )
tab [ :hidden_unused ] = true if tab [ :id ] == TAB_ANNOUNCEMENTS && ! active_record_types [ :announcements ]
tab [ :hidden_unused ] = true if tab [ :id ] == TAB_OUTCOMES && ! active_record_types [ :outcomes ]
2011-02-01 09:57:29 +08:00
end
2011-08-16 06:34:57 +08:00
# remove tabs that the user doesn't have access to
unless opts [ :for_reordering ]
unless self . grants_rights? ( user , opts [ :session ] , :read , :manage_content ) . values . any?
tabs . delete_if { | t | t [ :id ] == TAB_HOME }
tabs . delete_if { | t | t [ :id ] == TAB_ANNOUNCEMENTS }
tabs . delete_if { | t | t [ :id ] == TAB_PAGES }
tabs . delete_if { | t | t [ :id ] == TAB_OUTCOMES }
tabs . delete_if { | t | t [ :id ] == TAB_CONFERENCES }
tabs . delete_if { | t | t [ :id ] == TAB_COLLABORATIONS }
tabs . delete_if { | t | t [ :id ] == TAB_MODULES }
end
unless self . grants_rights? ( user , opts [ :session ] , :read , :manage_content , :manage_assignments ) . values . any?
tabs . delete_if { | t | t [ :id ] == TAB_ASSIGNMENTS }
tabs . delete_if { | t | t [ :id ] == TAB_SYLLABUS }
tabs . delete_if { | t | t [ :id ] == TAB_QUIZZES }
end
2011-09-08 02:41:06 +08:00
if self . grants_rights? ( user , opts [ :session ] , :manage_content , :manage_assignments ) . values . any?
tabs . detect { | t | t [ :id ] == TAB_ASSIGNMENTS } [ :manageable ] = true
tabs . detect { | t | t [ :id ] == TAB_SYLLABUS } [ :manageable ] = true
tabs . detect { | t | t [ :id ] == TAB_QUIZZES } [ :manageable ] = true
end
2011-08-16 06:34:57 +08:00
tabs . delete_if { | t | t [ :id ] == TAB_GRADES } unless self . grants_rights? ( user , opts [ :session ] , :read_grades , :view_all_grades , :manage_grades ) . values . any?
2011-09-08 02:41:06 +08:00
tabs . detect { | t | t [ :id ] == TAB_GRADES } [ :manageable ] = true if self . grants_rights? ( user , opts [ :session ] , :view_all_grades , :manage_grades ) . values . any?
2011-08-16 06:34:57 +08:00
tabs . delete_if { | t | t [ :id ] == TAB_PEOPLE } unless self . grants_rights? ( user , opts [ :session ] , :read_roster , :manage_students , :manage_admin_users ) . values . any?
2011-09-08 02:41:06 +08:00
tabs . detect { | t | t [ :id ] == TAB_PEOPLE } [ :manageable ] = true if self . grants_rights? ( user , opts [ :session ] , :manage_students , :manage_admin_users ) . values . any?
2011-08-16 06:34:57 +08:00
tabs . delete_if { | t | t [ :id ] == TAB_FILES } unless self . grants_rights? ( user , opts [ :session ] , :read , :manage_files ) . values . any?
2011-09-08 02:41:06 +08:00
tabs . detect { | t | t [ :id ] == TAB_FILES } [ :manageable ] = true if self . grants_right? ( user , opts [ :session ] , :managed_files )
2011-08-16 06:34:57 +08:00
tabs . delete_if { | t | t [ :id ] == TAB_DISCUSSIONS } unless self . grants_rights? ( user , opts [ :session ] , :read , :moderate_forum , :post_to_forum ) . values . any?
2011-09-08 02:41:06 +08:00
tabs . detect { | t | t [ :id ] == TAB_DISCUSSIONS } [ :manageable ] = true if self . grants_right? ( user , opts [ :session ] , :moderate_forum )
2011-08-16 06:34:57 +08:00
tabs . delete_if { | t | t [ :id ] == TAB_SETTINGS } unless self . grants_right? ( user , opts [ :session ] , :read_as_admin )
2011-09-08 02:41:06 +08:00
if ! user || ! self . grants_right? ( user , nil , :manage_content )
# remove some tabs for logged-out users or non-students
if self . grants_right? ( user , nil , :read_as_admin )
tabs . delete_if { | t | [ TAB_CHAT ] . include? ( t [ :id ] ) }
elsif ! self . grants_right? ( user , nil , :participate_as_student )
tabs . delete_if { | t | [ TAB_PEOPLE , TAB_CHAT ] . include? ( t [ :id ] ) }
end
2011-08-16 06:34:57 +08:00
2011-09-08 02:41:06 +08:00
# remove hidden tabs from students
tabs . delete_if { | t | ( t [ :hidden ] || ( t [ :hidden_unused ] && ! opts [ :include_hidden_unused ] ) ) && ! t [ :manageable ] }
end
2011-02-01 09:57:29 +08:00
end
# Uncommenting these lines will always put hidden links after visible links
# tabs.each_with_index{|t, i| t[:sort_index] = i }
# tabs = tabs.sort_by{|t| [t[:hidden_unused] || t[:hidden] ? 1 : 0, t[:sort_index]] } if !self.tab_configuration || self.tab_configuration.empty?
tabs
end
memoize :tabs_available
def allow_wiki_comments
read_attribute ( :allow_wiki_comments )
end
def account_name
self . account . name rescue nil
end
def term_name
self . enrollment_term . name rescue nil
end
def enable_user_notes
( root_account || account ) . enable_user_notes rescue false
end
2011-02-03 05:30:07 +08:00
def equella_settings
account = self . account
while account
settings = account . equella_settings
return settings if settings
account = account . parent_account
end
end
2011-02-01 09:57:29 +08:00
# This will move the course to be in the specified account.
# All enrollments, sections, and other objects attached to the course will also be updated.
def move_to_account ( new_root_account , new_sub_account = nil )
self . account = new_sub_account || new_root_account
self . save if new_sub_account
self . root_account = new_root_account
user_ids = [ ]
CourseSection . find ( :all , :conditions = > " course_id = #{ self . id } " ) . each do | cs |
cs . update_attribute ( :root_account_id , new_root_account . id )
end
Enrollment . find ( :all , :conditions = > " course_id = #{ self . id } " ) . each do | e |
e . update_attribute ( :root_account_id , new_root_account . id )
user_ids << e . user_id
end
self . save
User . update_account_associations ( user_ids )
end
2011-08-11 01:48:34 +08:00
cattr_accessor :settings_options
self . settings_options = { }
def self . add_setting ( setting , opts = nil )
self . settings_options [ setting . to_sym ] = opts || { }
end
# these settings either are or could be easily added to
# the course settings page
add_setting :hide_final_grade , :boolean = > true
def settings = ( hash )
if hash . is_a? ( Hash )
hash . each do | key , val |
if settings_options [ key . to_sym ]
opts = settings_options [ key . to_sym ]
if opts [ :boolean ]
settings [ key . to_sym ] = ( val == true || val == 'true' || val == '1' || val == 'on' )
elsif opts [ :hash ]
new_hash = { }
if val . is_a? ( Hash )
val . each do | inner_key , inner_val |
if opts [ :values ] . include? ( inner_key . to_sym )
new_hash [ inner_key . to_sym ] = inner_val . to_s
end
end
end
settings [ key . to_sym ] = new_hash . empty? ? nil : new_hash
else
settings [ key . to_sym ] = val . to_s
end
end
end
end
settings
end
def settings
result = self . read_attribute ( :settings )
return result if result
return self . write_attribute ( :settings , { } ) unless frozen?
{ } . freeze
end
2011-08-23 06:25:28 +08:00
def reset_content
Course . transaction do
2011-08-31 00:14:27 +08:00
new_course = Course . new
2011-09-14 01:16:41 +08:00
self . attributes . delete_if { | k , v | [ :id , :created_at , :updated_at , :syllabus_body , :wiki_id , :default_view , :tab_configuration ] . include? ( k . to_sym ) } . each do | key , val |
2011-10-13 07:28:30 +08:00
new_course . write_attribute ( key , val )
2011-08-23 06:25:28 +08:00
end
2011-08-31 00:14:27 +08:00
# The order here is important; we have to set our sis id to nil and save first
# so that the new course can be saved, then we need the new course saved to
# get its id to move over sections and enrollments. Setting this course to
# deleted has to be last otherwise it would set all the enrollments to
# deleted before they got moved
2011-09-14 01:16:41 +08:00
self . uuid = self . sis_source_id = self . sis_batch_id = nil ;
2011-08-25 00:25:11 +08:00
self . save!
2011-10-13 07:28:30 +08:00
Course . process_as_sis { new_course . save! }
2011-08-31 00:14:27 +08:00
self . course_sections . update_all ( :course_id = > new_course . id )
# we also want to bring along prior enrollments, so don't use the enrollments
# association
2011-09-14 01:16:41 +08:00
case Enrollment . connection . adapter_name
when 'MySQL'
Enrollment . connection . execute ( " UPDATE users, enrollments SET users.updated_at= #{ Course . sanitize ( Time . now . utc ) } , enrollments.updated_at= #{ Course . sanitize ( Time . now . utc ) } , enrollments.course_id= #{ new_course . id } WHERE users.id=enrollments.user_id AND enrollments.course_id= #{ self . id } " )
else
Enrollment . update_all ( { :course_id = > new_course . id , :updated_at = > Time . now . utc } , :course_id = > self . id )
User . update_all ( { :updated_at = > Time . now . utc } , " id IN (SELECT user_id FROM enrollments WHERE course_id= #{ new_course . id } ) " )
end
2011-08-31 00:14:27 +08:00
self . replacement_course_id = new_course . id
self . workflow_state = 'deleted'
self . save!
2011-10-13 07:28:30 +08:00
Course . find ( new_course . id )
2011-08-23 06:25:28 +08:00
end
end
2011-02-01 09:57:29 +08:00
end