socialforge/app/models/course.rb

508 lines
16 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#coding=utf-8
require 'elasticsearch/model'
class Course < ActiveRecord::Base
include Redmine::SafeAttributes
STATUS_ACTIVE = 1
STATUS_CLOSED = 5
STATUS_ARCHIVED = 9
#elasticsearch
include Elasticsearch::Model
#elasticsearch kaminari init
Kaminari::Hooks.init
Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model::Response::Pagination::Kaminari
settings index: { number_of_shards: 5 } do
mappings dynamic: 'false' do
indexes :name, analyzer: 'smartcn',index_options: 'offsets'
indexes :description, analyzer: 'smartcn',index_options: 'offsets'
indexes :updated_at, index:"not_analyzed",type:"date"
end
end
attr_accessible :code, :extra, :name, :state, :tea_id, :time , :location, :state, :term, :password,:is_public,:description,:class_period, :open_student, :enterprise_name, :is_delete
#belongs_to :project, :class_name => 'Course', :foreign_key => :extra, primary_key: :identifier
belongs_to :teacher, :class_name => 'User', :foreign_key => :tea_id # 定义一个方法teacher该方法通过tea_id来调用User表
belongs_to :school, :class_name => 'School', :foreign_key => :school_id #定义一个方法school该方法通过school_id来调用School表
belongs_to :syllabus
# has_many :bid
has_many :members, :include => [:principal, :roles], :conditions => "#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}"
has_many :memberships, :class_name => 'Member'
has_many :member_principals, :class_name => 'Member',
:include => :principal,
:conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE})"
has_many :principals, :through => :member_principals, :source => :principal
has_many :users, :through => :members
has_many :org_courses, :dependent => :destroy
has_many :organizations, :through => :org_courses
# has_many :homeworks, :through => :homework_for_courses, :source => :bid, :dependent => :destroy
has_many :journals_for_messages, :as => :jour, :dependent => :destroy
# has_many :homework_for_courses, :dependent => :destroy
has_many :student, :class_name => 'StudentsForCourse', :source => :user
has_many :course_infos, :class_name => 'CourseInfos',:dependent => :destroy
has_many :enabled_modules, :dependent => :delete_all
has_many :boards, :dependent => :destroy, :order => "position ASC"
#has_many :course_journals_for_messages, :class_name => 'CourseJournalsForMessage', :as => :jour, :dependent => :destroy
has_many :news, :dependent => :destroy, :include => :author
has_one :course_status, :class_name => "CourseStatus", :dependent => :destroy
has_many :homework_commons, :dependent => :destroy
has_many :student_works, :through => :homework_commons, :dependent => :destroy
has_many :course_groups, :dependent => :destroy
# 课程动态
has_many :course_acts, :class_name => 'CourseActivity',:as =>:course_act ,:dependent => :destroy
has_many :course_activities
# 课程消息
has_many :course_messages, :class_name =>'CourseMessage', :as => :course_message, :dependent => :destroy
has_many :exercises, :dependent => :destroy
# 课程贡献榜
has_many :course_contributor_scores, :dependent => :destroy
acts_as_taggable
acts_as_nested_set :order => 'name', :dependent => :destroy
acts_as_attachable :view_permission => :view_course_files,
:delete_permission => :manage_files
validates_presence_of :term,:name
validates_format_of :class_period, :with =>/^[1-9]\d*$/
validates_format_of :name,:with =>/^[^ ]+[a-zA-Z0-9_\u4e00-\u9fa5\s\S]+$/
validates_length_of :description, :maximum => 10000
before_save :self_validate
# 公开课程变成私有课程,所有资源都变成私有
after_update :update_files_public,:update_course_ealasticsearch_index
after_create :create_board_sync, :act_as_course_activity, :act_as_course_message,:create_course_ealasticsearch_index
before_destroy :delete_all_members,:delete_course_ealasticsearch_index
safe_attributes 'extra',
'time',
'name',
'extra',
'code',
'location',
'tea_id',
'password',
'term',
'is_public',
'description',
'class_period',
'open_student',
'is_delete',
'syllabus_id'
acts_as_customizable
scope :not_deleted, lambda{where(is_delete: 0)}
scope :all_course
scope :active, lambda { where(:status => STATUS_ACTIVE) }
scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
scope :all_public, lambda { where(:is_public => true) }
scope :visible, lambda {|*args| where(Course.where("is_delete =?", 0).visible_condition(args.shift || User.current, *args)) }
scope :allowed_to, lambda {|*args|
user = User.current
permission = nil
if args.first.is_a?(Symbol)
permission = args.shift
else
user = args.shift
permission = args.shift
end
where(Course.allowed_to_condition(user, permission, *args))
}
scope :like, lambda {|arg|
if arg.blank?
where(nil)
else
pattern = "%#{arg.to_s.strip.downcase}%"
where(" LOWER(name) LIKE :p ", :p => pattern)
end
}
scope :indexable,lambda { where('is_public = 1 and is_delete = 0') }
def self.search(query)
__elasticsearch__.search(
{
query: {
multi_match: {
query: query,
type:"most_fields",
operator: "or",
fields: ['name', 'description^0.5']
}
},
sort: {
_score:{order: "desc" },
updated_at:{order:"desc"}
},
highlight: {
pre_tags: ['<span class="c_red">'],
post_tags: ['</span>'],
fields: {
name: {},
description: {}
}
}
}
)
end
def self.e_search(query)
__elasticsearch__.search(
{
query: {
multi_match: {
query: query,
type:"most_fields",
operator: "or",
fields: ['name', 'description^0.5']
}
},
sort: {
_score:{order: "desc" },
updated_at:{order:"desc"}
}
}
)
end
def visible?(user=User.current)
user.allowed_to?(:view_course, self)
end
def parent_id_changed?
false
end
# Returns the mail adresses of users that should be always notified on project events
def recipients
notified_users.collect {|user| user.mail}
end
# Returns the users that should be notified on project events
def notified_users
# TODO: User part should be extracted to User#notify_about?
members.select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal}
end
# 课程的短描述信息
def short_description(length = 255)
description.gsub(/<\/?.*?>/,"").html_safe if description
#description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
end
# 课程的短名称信息
def short_name(length = 8)
name.gsub(/<\/?.*?>/,"").html_safe if name
end
def strip_html(html)
return html if html.empty? || !html.include?('<')
output = ""
tokenizer = HTML::Tokenizer.new(html)
while token = tokenizer.next
node = HTML::Node.parse(nil, 0, 0, token, false)
output += token unless (node.kind_of? HTML::Tag) or (token =~ /^<!/)
end
return output
end
def extra_frozen?
errors[:extra].blank? && !(new_record? || extra.blank?)
end
def archived?
self.status == STATUS_ARCHIVED
end
def self.visible_condition(user, options={})
allowed_to_condition(user, :view_course, options)
end
# 获取课程的资源类型列表
def attachmenttypes
@attachmenttypes = Attachmentstype.find(:all, :conditions => ["#{Attachmentstype.table_name}.typeId= ?",self.attachmenttype ])
end
# 获取资源后缀名列表
def contenttypes
attachmenttypes
if @attachmenttypes.length >0
@attachmenttypes.last().suffixArr
end
end
def active?
self.status == STATUS_ACTIVE
end
#课程权限判断
def allows_to?(action)
if archived?
# No action allowed on archived projects
return false
end
unless active? || Redmine::AccessControl.read_action?(action)
# No write action allowed on closed projects
return false
end
# No action allowed on disabled modules
if action.is_a? Hash
allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
else
allowed_permissions.include? action
end
end
# 课程允许的权限集合
def allowed_permissions
@allowed_permissions ||= begin
module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
end
end
# 课程允许的动作集合
def allowed_actions
@actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
end
# 返回用户组可以访问的课程
def users_by_role
members.includes(:user, :roles).all.inject({}) do |h, m|
m.roles.each do |r|
h[r] ||= []
h[r] << m.user
end
h
end
end
#自定义验证
def self_validate
end
def update_files_public
unless self.is_public?
self.attachments.each do |a|
a.update_attributes(:is_public => false)
end
end
end
# 创建课程讨论区
def create_board_sync
@board = self.boards.build
#self.name=" #{l(:label_borad_course) }"
@board.name = " #{l(:label_borad_course) }"#self.name
@board.description = self.name.to_s
@board.project_id = -1
if @board.save
logger.debug "[Course Model] ===> #{@board.to_json}"
else
logger.error "[Course Model] ===> Auto create board when course saved, because #{@board.full_messages}"
end
end
# 新增课程留言
# add by nwb
def self.add_new_jour(user, notes, id, options={})
course = Course.find(id)
if options.count == 0
pjfm = course.journals_for_messages.build(:user_id => user.id, :notes => notes, :reply_id => 0)
else
pjfm = course.journals_for_messages.build(options)
end
pjfm.save
pjfm
end
# 删除课程所有成员
def delete_all_members
if self.members && self.members.count > 0
me, mr = Member.table_name, MemberRole.table_name
connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.course_id = #{id})")
Member.delete_all(['course_id = ?', id])
end
end
def get_endup_time
begin
end_time = Time.parse(self.endup_time)
rescue Exception => e
end_time = Time.parse("3000-01-01")
Rails.logger.error "[Error] course endup_time error. ===> #{e}"
ensure
return end_time
end
end
def get_time
begin
time = Date.new(self.time).to_time
rescue Exception => e
time = Time.parse("3000-01-01")
Rails.logger.error "[Error] course time error. ===> #{e}"
ensure
return time
end
end
def self.allowed_to_condition(user, permission, options={})
perm = Redmine::AccessControl.permission(permission)
base_statement = (perm && perm.read? ? "#{Course.table_name}.status <> #{Course::STATUS_ARCHIVED}" : "#{Course.table_name}.status = #{Course::STATUS_ACTIVE}")
if perm && perm.course_module
base_statement << " AND #{Course.table_name}.id IN (SELECT em.course_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.course_module}')"
end
if options[:course]
course_statement = "#{Course.table_name}.id = #{options[:course].id}"
course_statement << " OR (#{Course.table_name}.lft > #{options[:course].lft} AND #{Course.table_name}.rgt < #{options[:course].rgt})" if options[:with_subcourses]
base_statement = "(#{course_statement}) AND (#{base_statement})"
end
if user.admin?
base_statement
else
statement_by_role = {}
unless options[:member]
role = user.logged? ? Role.non_member : Role.anonymous
if role.allowed_to?(permission)
statement_by_role[role] = "#{Course.table_name}.is_public = #{connection.quoted_true}"
end
end
if user.logged?
user.courses_by_role.each do |role, courses|
if role.allowed_to?(permission) && courses.any?
statement_by_role[role] = "#{Course.table_name}.id IN (#{courses.collect(&:id).join(',')})"
end
end
end
if statement_by_role.empty?
"1=0"
else
if block_given?
statement_by_role.each do |role, statement|
if s = yield(role, user)
statement_by_role[role] = "(#{statement} AND (#{s}))"
end
end
end
"((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
end
end
end
#课程动态公共表记录
def act_as_course_activity
self.course_acts << CourseActivity.new(:user_id => self.tea_id,:course_id => self.id)
end
#创建课程后,给该用户发送消息
def act_as_course_message
self.course_messages << CourseMessage.new(:user_id => self.tea_id, :course_id => self.id, :viewed => false)
end
#项目与课程分离后,很多课程的名称等信息为空,这些数据信息存储在项目表中!!就是数据兼容的问题
#def name
# read_attribute('name') || Project.find_by_identifier(self.extra).try(:name)
#end
# after_commit on: [:create] do
# __elasticsearch__.index_document
# end
#
# after_commit on: [:update] do
# __elasticsearch__.update_document
# end
#
# after_commit on: [:destroy] do
# __elasticsearch__.delete_document
# end
def create_course_ealasticsearch_index
return if Rails.env.development?
if self.is_public == 1 and self.is_delete == 0 #公开 和 没有被删除的课程才被索引
self.__elasticsearch__.index_document
end
end
def update_course_ealasticsearch_index
return if Rails.env.development?
if self.is_public == 1 and self.is_delete == 0 #如果是初次更新成为公开或者恢复被删除的情况,会报错,那么这条记录尚未被索引过。没有报错就是更新的其他属性
begin
self.__elasticsearch__.update_document
rescue => e
self.__elasticsearch__.index_document
end
else #如果是更新成为私有的,那么索引就要被删除
begin
self.__elasticsearch__.delete_document
rescue => e
end
end
end
def delete_course_ealasticsearch_index
return if Rails.env.development?
begin
self.__elasticsearch__.delete_document
rescue => e
end
end
# 延迟生成邀请码
def invite_code
return generate_invite_code
end
# 生成邀请码
CODES = %W(2 3 4 5 6 7 8 9 A B C D E F G H J K L N M O P Q R S T U V W X Y Z)
def generate_invite_code
code = read_attribute(:invite_code)
if !code || code.size <5
code = CODES.sample(5).join
return generate_invite_code if Course.where(invite_code: code).present?
update_attribute(:invite_code, code)
end
code
end
def generate_qrcode
ticket = self.qrcode
if !ticket || ticket.size < 10
response = Wechat.api.qrcode_create_scene(invite_code)
logger.debug "response = #{response}"
self.qrcode = response['ticket']
save! && reload
ticket = qrcode
end
ticket
end
end
# Delete the previous articles index in Elasticsearch
# Course.__elasticsearch__.client.indices.delete index: Course.index_name rescue nil
#
# # Create the new index with the new mapping
# Course.__elasticsearch__.client.indices.create \
# index: Course.index_name,
# body: { settings: Course.settings.to_hash, mappings: Course.mappings.to_hash }
# Index all article records from the DB to Elasticsearch
#Course.where('is_public = 1').import :force=>true