diff --git a/.gitignore b/.gitignore
index bc936c34c..5e8a94339 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
*.swp
/config/database.yml
/config/configuration.yml
+/config/additional_environment.rb
/files/*
/log/*
/public/tmp/*
@@ -23,3 +24,7 @@
public/api_doc/
/.metadata
vendor/cache
+/files
+/public/images/avatars
+/public/files
+/tags
diff --git a/Gemfile b/Gemfile
index 58373cb06..0103a18b5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,10 +4,13 @@ source 'http://ruby.taobao.org'
unless RUBY_PLATFORM =~ /w32/
# unix-like only
gem 'iconv'
- gem 'rubyzip'
- gem 'zip-zip'
end
+gem "mysql2", "= 0.3.18"
+gem 'redis-rails'
+gem 'rubyzip'
+gem 'delayed_job_active_record'#, :group => :production
+gem 'daemons'
gem 'grape', '~> 0.9.0'
gem 'grape-entity'
gem 'seems_rateable', '~> 1.0.13'
@@ -20,9 +23,6 @@ gem "builder", "3.0.0"
gem 'acts-as-taggable-on', '2.4.1'
gem 'spreadsheet'
gem 'ruby-ole'
-#gem 'email_verifier', path: 'lib/email_verifier'
-gem 'rufus-scheduler'
-#gem 'dalli', path: 'lib/dalli-2.7.2'
gem 'rails_kindeditor',path:'lib/rails_kindeditor'
group :development do
gem 'grape-swagger'
@@ -58,8 +58,6 @@ group :test do
#end
end
- # gem 'rspec-rails' , '2.13.1'
- # gem 'guard-rspec','2.5.0'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
@@ -84,56 +82,13 @@ group :openid do
gem "rack-openid"
end
-# Optional gem for exporting the gantt to a PNG file, not supported with jruby
-platforms :jruby do
- # jruby-openssl is bundled with JRuby 1.7.0
- gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0'
- gem "activerecord-jdbc-adapter", "1.2.5"
-end
-# Include database gems for the adapters found in the database
-# configuration file
-require 'erb'
-require 'yaml'
database_file = File.join(File.dirname(__FILE__), "config/database.yml")
if File.exist?(database_file)
- database_config = YAML::load(ERB.new(IO.read(database_file)).result)
- adapters = database_config.values.map {|c| c['adapter']}.compact.uniq
- if adapters.any?
- adapters.each do |adapter|
- case adapter
- when 'mysql2'
- gem "mysql2", "= 0.3.18", :platforms => [:mri, :mingw]
- gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
- when 'mysql'
- gem "mysql", "~> 2.8.1", :platforms => [:mri, :mingw]
- gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
- when /postgresql/
- gem "pg", ">= 0.11.0", :platforms => [:mri, :mingw]
- gem "activerecord-jdbcpostgresql-adapter", :platforms => :jruby
- when /sqlite3/
- gem "sqlite3", :platforms => [:mri, :mingw]
- gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
- when /sqlserver/
- gem "tiny_tds", "~> 0.5.1", :platforms => [:mri, :mingw]
- gem "activerecord-sqlserver-adapter", :platforms => [:mri, :mingw]
- else
- warn("Unknown database adapter `#{adapter}` found in config/database.yml, use Gemfile.local to load your own database gems")
- end
- end
- else
- warn("No adapter found in config/database.yml, please configure it first")
- end
else
warn("Please configure your config/database.yml first")
end
-local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
-if File.exists?(local_gemfile)
- puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v`
- instance_eval File.read(local_gemfile)
-end
-
# Load plugins' Gemfiles
Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file|
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
diff --git a/app/api/mobile/api.rb b/app/api/mobile/api.rb
index 8f7f0342f..a59b01776 100644
--- a/app/api/mobile/api.rb
+++ b/app/api/mobile/api.rb
@@ -1,49 +1,49 @@
-module Mobile
- require_relative 'middleware/error_handler'
- require_relative 'apis/auth'
- require_relative 'apis/users'
- require_relative 'apis/courses'
- require_relative 'apis/watches'
- require_relative 'apis/upgrade'
- require_relative 'apis/homeworks'
- require_relative 'apis/comments'
- class API < Grape::API
- version 'v1', using: :path
- format :json
- content_type :json, "application/json;charset=UTF-8"
- use Mobile::Middleware::ErrorHandler
-
- helpers do
- def logger
- API.logger
- end
-
- def authenticate!
- raise('Unauthorized. Invalid or expired token.') unless current_user
- end
-
- def current_user
- token = ApiKey.where(access_token: params[:token]).first
- if token && !token.expired?
- @current_user = User.find(token.user_id)
- else
- nil
- end
- end
- end
-
- mount Apis::Auth
- mount Apis::Users
- mount Apis::Courses
- mount Apis::Watches
- mount Apis::Upgrade
- mount Apis::Homeworks
- mount Apis::Comments
-
- #add_swagger_documentation ({api_version: 'v1', base_path: 'http://u06.shellinfo.cn/trustie/api'})
- #add_swagger_documentation ({api_version: 'v1', base_path: '/api'}) if Rails.env.development?
-
- end
-end
-
-
+module Mobile
+ require_relative 'middleware/error_handler'
+ require_relative 'apis/auth'
+ require_relative 'apis/users'
+ require_relative 'apis/courses'
+ require_relative 'apis/watches'
+ require_relative 'apis/upgrade'
+ require_relative 'apis/homeworks'
+ require_relative 'apis/comments'
+ class API < Grape::API
+ version 'v1', using: :path
+ format :json
+ content_type :json, "application/json;charset=UTF-8"
+ use Mobile::Middleware::ErrorHandler
+
+ helpers do
+ def logger
+ API.logger
+ end
+
+ def authenticate!
+ raise('Unauthorized. Invalid or expired token.') unless current_user
+ end
+
+ def current_user
+ token = ApiKey.where(access_token: params[:token]).first
+ if token && !token.expired?
+ @current_user = User.find(token.user_id)
+ else
+ nil
+ end
+ end
+ end
+
+ mount Apis::Auth
+ mount Apis::Users
+ mount Apis::Courses
+ mount Apis::Watches
+ mount Apis::Upgrade
+ mount Apis::Homeworks
+ mount Apis::Comments
+
+ #add_swagger_documentation ({api_version: 'v1', base_path: 'http://u06.shellinfo.cn/trustie/api'})
+ #add_swagger_documentation ({api_version: 'v1', base_path: '/api'}) if Rails.env.development?
+
+ end
+end
+
+
diff --git a/app/api/mobile/apis/auth.rb b/app/api/mobile/apis/auth.rb
index fbec9a3b0..9e4cb1bc6 100644
--- a/app/api/mobile/apis/auth.rb
+++ b/app/api/mobile/apis/auth.rb
@@ -26,7 +26,7 @@ module Mobile
present :data, {token: key.access_token, user: api_user}, using: Entities::Auth
present :status, 0
else
- raise 'Unauthorized.'
+ raise "无效的用户名或密码"
end
end
diff --git a/app/api/mobile/apis/comments.rb b/app/api/mobile/apis/comments.rb
index 2e84fe5a9..edc7bcf54 100644
--- a/app/api/mobile/apis/comments.rb
+++ b/app/api/mobile/apis/comments.rb
@@ -80,7 +80,7 @@ module Mobile
end
post do
cs_params = {
- memo: {:subject => params[:subject],:content => '该贴来自手机App意见反馈'},
+ memo: {:subject => '该贴来自手机App意见反馈' ,:content => params[:subject]},
}
cs = CommentService.new
memo,message = cs.create_feedback cs_params, current_user
diff --git a/app/api/mobile/apis/courses.rb b/app/api/mobile/apis/courses.rb
index 6d68d549a..0d0c4651f 100644
--- a/app/api/mobile/apis/courses.rb
+++ b/app/api/mobile/apis/courses.rb
@@ -80,6 +80,10 @@ module Mobile
class_period: params[:class_period]
}
course = ::Course.find(params[:course_id])
+ # 如果没有传密码过来,那就把原来的密码给上,不然会不更新
+ if params[:password].nil? || params[:password].blank?
+ cs_params[:course][:password] = course[:password]
+ end
cs.edit_course_authorize(current_user,course)
course = cs.edit_course(cs_params, course,current_user)
present :data, course, with: Mobile::Entities::Course
@@ -224,6 +228,32 @@ module Mobile
present :status, 0
end
+ desc '课程课件'
+ params do
+ requires :token, type: String
+ requires :course_id,type: Integer,desc: '课程id'
+ optional :name,type:String,desc:'课件名称可能包含的字符'
+ end
+ get ":course_id/attachments" do
+ cs = CoursesService.new
+ count = cs.course_attachments params
+ present :data, count, with: Mobile::Entities::Attachment
+ present :status, 0
+ end
+
+ desc '课程学生'
+ params do
+ requires :token,type:String
+ requires :course_id,type:Integer,desc: '课程id'
+ optional :name,type:String,desc:'学生的姓名或者昵称或者学号可能包含的字符'
+ end
+ get ":course_id/members" do
+ cs = CoursesService.new
+ count = cs.course_members params
+ present :data, count, with: Mobile::Entities::Member
+ present :status, 0
+ end
+
end
end
end
diff --git a/app/api/mobile/apis/users.rb b/app/api/mobile/apis/users.rb
index b6d1db25c..063cfafae 100644
--- a/app/api/mobile/apis/users.rb
+++ b/app/api/mobile/apis/users.rb
@@ -14,7 +14,7 @@ module Mobile
us = UsersService.new
user = us.register params.merge(:password_confirmation => params[:password],
:should_confirmation_password => true)
- raise "register failed #{user.errors.full_messages}" if user.new_record?
+ raise "该邮箱已经被注册过了" if user.new_record?
present :data, user, with: Mobile::Entities::User
present :status, 0
diff --git a/app/api/mobile/entities/attachment.rb b/app/api/mobile/entities/attachment.rb
index 510db89be..080b24558 100644
--- a/app/api/mobile/entities/attachment.rb
+++ b/app/api/mobile/entities/attachment.rb
@@ -16,8 +16,11 @@ module Mobile
end
end
end
+ attachment_expose :id
attachment_expose :filename
attachment_expose :description
+ attachment_expose :downloads
+ attachment_expose :quotes
end
end
end
\ No newline at end of file
diff --git a/app/api/mobile/entities/course.rb b/app/api/mobile/entities/course.rb
index 4c91738c1..50812b349 100644
--- a/app/api/mobile/entities/course.rb
+++ b/app/api/mobile/entities/course.rb
@@ -46,6 +46,7 @@ module Mobile
course_expose :term
course_expose :time
course_expose :updated_at
+ course_expose :course_student_num
expose :teacher, using: Mobile::Entities::User do |c, opt|
if c.is_a? ::Course
c.teacher
@@ -58,6 +59,7 @@ module Mobile
end
course_expose :current_user_is_member
course_expose :current_user_is_teacher
+ course_expose :work_unit
end
end
end
diff --git a/app/api/mobile/entities/member.rb b/app/api/mobile/entities/member.rb
new file mode 100644
index 000000000..837ec788a
--- /dev/null
+++ b/app/api/mobile/entities/member.rb
@@ -0,0 +1,33 @@
+module Mobile
+ module Entities
+ class Member < Grape::Entity
+ include ApplicationHelper
+ include ApiHelper
+ def self.member_expose(f)
+ expose f do |u,opt|
+ if u.is_a?(Hash) && u.key?(f)
+ u[f]
+ elsif u.is_a?(::Member)
+ if u.respond_to?(f)
+ u.send(f)
+ else
+ case f
+ when :student_id
+ u.user.user_extensions.student_id
+ end
+ end
+ end
+
+ end
+ end
+
+ expose :user, using: Mobile::Entities::User do |c, opt|
+ if c.is_a?(::Member)
+ c.user
+ end
+ end
+ member_expose :student_id
+ member_expose :score
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/api/mobile/entities/user.rb b/app/api/mobile/entities/user.rb
index 1f52ae841..6b0064036 100644
--- a/app/api/mobile/entities/user.rb
+++ b/app/api/mobile/entities/user.rb
@@ -15,13 +15,13 @@ module Mobile
when :img_url
url_to_avatar(u)
when :gender
- u.user_extensions.gender.nil? ? 0 : u.user_extensions.gender
+ u.nil? || u.user_extensions.nil? || u.user_extensions.gender.nil? ? 0 : u.user_extensions.gender
when :work_unit
get_user_work_unit u
when :location
get_user_location u
when :brief_introduction
- u.user_extensions.brief_introduction
+ u.nil? || u.user_extensions.nil? ? "" : u.user_extensions.brief_introduction
end
end
end
@@ -34,6 +34,8 @@ module Mobile
user_expose :img_url
#昵称
expose :nickname
+ #真名
+ expose :realname
#性别
user_expose :gender
#我的二维码
diff --git a/app/controllers/account_controller.rb b/app/controllers/account_controller.rb
index 2cf404ffb..22db3d5b4 100644
--- a/app/controllers/account_controller.rb
+++ b/app/controllers/account_controller.rb
@@ -88,9 +88,7 @@ class AccountController < ApplicationController
# create a new token for password recovery
token = Token.new(:user => user, :action => "recovery")
if token.save
- Thread.new do
- Mailer.lost_password(token).deliver
- end
+ Mailer.run.lost_password(token)
flash[:notice] = l(:notice_account_lost_email_sent)
redirect_to signin_url
return
@@ -228,7 +226,7 @@ class AccountController < ApplicationController
user = User.find(params[:user]) if params[:user]
token = Token.new(:user => user, :action => "register")
if token.save
- Mailer.register(token).deliver
+ Mailer.run.register(token)
else
yield if block_given?
@@ -334,7 +332,7 @@ class AccountController < ApplicationController
token = Token.create(:user => user, :action => 'autologin')
cookie_options = {
:value => token.value,
- :expires => 1.year.from_now,
+ :expires => 7.days.from_now,
:path => (Redmine::Configuration['autologin_cookie_path'] || '/'),
:secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false),
:httponly => true
@@ -366,7 +364,7 @@ class AccountController < ApplicationController
token = Token.new(:user => user, :action => "register")
if user.save and token.save
UserStatus.create(:user_id => user.id, :changsets_count => 0, :watchers_count => 0)
- Mailer.register(token).deliver
+ Mailer.run.register(token)
flash[:notice] = l(:notice_account_register_done)
@@ -401,7 +399,7 @@ class AccountController < ApplicationController
if user.save
UserStatus.create(:user_id => user.id ,:changsets_count => 0, :watchers_count => 0)
# Sends an email to the administrators
- Mailer.account_activation_request(user).deliver
+ Mailer.run.account_activation_request(user)
account_pending
else
yield if block_given?
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 47251cf85..741af2aee 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -894,7 +894,6 @@ class ApplicationController < ActionController::Base
set_autologin_cookie(user)
end
call_hook(:controller_account_success_authentication_after, {:user => user })
-
-
end
+
end
diff --git a/app/controllers/applied_project_controller.rb b/app/controllers/applied_project_controller.rb
index f2c0eb056..b9824976b 100644
--- a/app/controllers/applied_project_controller.rb
+++ b/app/controllers/applied_project_controller.rb
@@ -1,59 +1,59 @@
-class AppliedProjectController < ApplicationController
-
- #申请加入项目
- def applied_join_project
- @user_id = params[:user_id]
- @project = Project.find_by_id(params[:project_id])
- if params[:project_join]
- if @project
- user = User.find @user_id
- if user.member_of?(@project)
- @status = 3
- else
- @applieds = AppliedProject.where("user_id = ? and project_id = ?", params[:user_id],params[:project_id])
- if @applieds.count == 0
- appliedproject = AppliedProject.create(:user_id => params[:user_id], :project_id => params[:project_id])
- Mailer.applied_project(appliedproject).deliver
- @status = 2
- else
- @status = 1
- end
- end
- else
- @status = 0
- end
- respond_to do |format|
- format.js
- end
- return
- end
-
- @applieds = AppliedProject.where("user_id = ? and project_id = ?", params[:user_id],params[:project_id])
- if @applieds.count == 0
- appliedproject = AppliedProject.create(:user_id => params[:user_id], :project_id => params[:project_id])
- Mailer.applied_project(appliedproject).deliver
- end
-
- #redirect_to project_path(params[:project_id])
- #redirect_to_referer_or {render :text => ( 'applied success.'), :layout => true}
- respond_to do |format|
- format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
- format.js { render :partial => 'set_applied'}
- end
- end
-
- #取消申请
- def unapplied_join_project
- @project = Project.find(params[:project_id])
- #@applied = AppliedProject.find(params[:id])
- #@applied.destroy
-
- AppliedProject.deleteappiled(params[:user_id], params[:project_id])
-
- respond_to do |format|
- format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
- format.js { render :partial => 'set_applied' }
- end
- end
-
-end
+class AppliedProjectController < ApplicationController
+
+ #申请加入项目
+ def applied_join_project
+ @user_id = params[:user_id]
+ @project = Project.find_by_id(params[:project_id])
+ if params[:project_join]
+ if @project
+ user = User.find @user_id
+ if user.member_of?(@project)
+ @status = 3
+ else
+ @applieds = AppliedProject.where("user_id = ? and project_id = ?", params[:user_id],params[:project_id])
+ if @applieds.count == 0
+ appliedproject = AppliedProject.create(:user_id => params[:user_id], :project_id => params[:project_id])
+ Mailer.run.applied_project(appliedproject)
+ @status = 2
+ else
+ @status = 1
+ end
+ end
+ else
+ @status = 0
+ end
+ respond_to do |format|
+ format.js
+ end
+ return
+ end
+
+ @applieds = AppliedProject.where("user_id = ? and project_id = ?", params[:user_id],params[:project_id])
+ if @applieds.count == 0
+ appliedproject = AppliedProject.create(:user_id => params[:user_id], :project_id => params[:project_id])
+ Mailer.run.applied_project(appliedproject)
+ end
+
+ #redirect_to project_path(params[:project_id])
+ #redirect_to_referer_or {render :text => ( 'applied success.'), :layout => true}
+ respond_to do |format|
+ format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
+ format.js { render :partial => 'set_applied'}
+ end
+ end
+
+ #取消申请
+ def unapplied_join_project
+ @project = Project.find(params[:project_id])
+ #@applied = AppliedProject.find(params[:id])
+ #@applied.destroy
+
+ AppliedProject.deleteappiled(params[:user_id], params[:project_id])
+
+ respond_to do |format|
+ format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
+ format.js { render :partial => 'set_applied' }
+ end
+ end
+
+end
diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb
index 23b080d94..d880ebe35 100644
--- a/app/controllers/attachments_controller.rb
+++ b/app/controllers/attachments_controller.rb
@@ -27,7 +27,7 @@ class AttachmentsController < ApplicationController
accept_api_auth :show, :download, :upload
require 'iconv'
include AttachmentsHelper
-
+ include ApplicationHelper
def show
respond_to do |format|
@@ -65,36 +65,7 @@ class AttachmentsController < ApplicationController
def download
# modify by nwb
# 下载添加权限设置
- candown = false
- if @attachment.container.class.to_s != "HomeworkAttach" && (@attachment.container.has_attribute?(:project) || @attachment.container.has_attribute?(:project_id)) && @attachment.container.project
- project = @attachment.container.project
- candown= User.current.member_of?(project) || (project.is_public && @attachment.is_public == 1)
- elsif @attachment.container.is_a?(Project)
- project = @attachment.container
- candown= User.current.member_of?(project) || (project.is_public && @attachment.is_public == 1)
- elsif (@attachment.container.has_attribute?(:board) || @attachment.container.has_attribute?(:board_id)) && @attachment.container.board &&
- @attachment.container.board.project
- project = @attachment.container.board.project
- candown = User.current.member_of?(project) || (project.is_public && @attachment.is_public == 1)
- elsif (@attachment.container.has_attribute?(:course) ||@attachment.container.has_attribute?(:course_id) ) && @attachment.container.course
- course = @attachment.container.course
- candown = User.current.member_of_course?(course) || (course.is_public==1 && @attachment.is_public == 1)
- elsif @attachment.container.is_a?(Course)
- course = @attachment.container
- candown= User.current.member_of_course?(course) || (course.is_public==1 && @attachment.is_public == 1)
- elsif (@attachment.container.has_attribute?(:board) || @attachment.container.has_attribute?(:board_id)) && @attachment.container.board &&
- @attachment.container.board.course
- course = @attachment.container.board.course
- candown= User.current.member_of_course?(course) || (course.is_public==1 && @attachment.is_public == 1)
- elsif @attachment.container.class.to_s=="HomeworkAttach" && @attachment.container.bid.reward_type == 3
- candown = true
- elsif @attachment.container_type == "Bid" && @attachment.container && @attachment.container.courses.first
- course = @attachment.container.courses.first
- candown = User.current.member_of_course?(course) || (course.is_public == 1 && @attachment.is_public == 1)
- else
-
- candown = @attachment.is_public == 1
- end
+ candown = attachment_candown @attachment
if candown || User.current.admin? || User.current.id == @attachment.author_id
@attachment.increment_download
if stale?(:etag => @attachment.digest)
@@ -108,7 +79,7 @@ class AttachmentsController < ApplicationController
end
rescue => e
- redirect_to "http://" + (Setting.host_name.to_s) +"/file_not_found.html"
+ redirect_to "http: //" + (Setting.host_name.to_s) +"/file_not_found.html"
end
#更新资源文件类型
@@ -349,6 +320,46 @@ class AttachmentsController < ApplicationController
end
end
+ def add_exist_file_to_projects
+ file = Attachment.find(params[:file_id])
+ projects = params[:projects][:project]
+ @message = ""
+ projects.each do |project|
+ c = Project.find(project);
+ if project_contains_attachment?(c,file)
+ if @message && @message == ""
+ @message += l(:label_project_prompt) + c.name + l(:label_contain_resource) + file.filename + l(:label_quote_resource_failed)
+ next
+ else
+ @message += "
" + l(:label_project_prompt) + c.name + l(:label_contain_resource) + file.filename + l(:label_quote_resource_failed)
+ next
+ end
+ end
+ attach_copied_obj = file.copy
+ attach_copied_obj.tag_list.add(file.tag_list) # tag关联
+ attach_copied_obj.container = c
+ attach_copied_obj.created_on = Time.now
+ attach_copied_obj.author_id = User.current.id
+ attach_copied_obj.copy_from = file.copy_from.nil? ? file.id : file.copy_from
+ if attach_copied_obj.attachtype == nil
+ attach_copied_obj.attachtype = 4
+ end
+ @obj = c
+ @save_flag = attach_copied_obj.save
+ @save_message = attach_copied_obj.errors.full_messages
+ update_quotes attach_copied_obj
+ end
+ respond_to do |format|
+ format.js
+ end
+ rescue NoMethodError
+ @save_flag = false
+ @save_message = [] << l(:label_course_empty_select)
+ respond_to do |format|
+ format.js
+ end
+ end
+
def add_exist_file_to_courses
file = Attachment.find(params[:file_id])
courses = params[:courses][:course]
diff --git a/app/controllers/bids_controller.rb b/app/controllers/bids_controller.rb
index be97fdb6e..b1cca68ed 100644
--- a/app/controllers/bids_controller.rb
+++ b/app/controllers/bids_controller.rb
@@ -490,7 +490,7 @@ class BidsController < ApplicationController
(SELECT stars FROM seems_rateable_rates WHERE rateable_type = 'HomeworkAttach' AND rateable_id = homework_attaches.id AND is_teacher_score = 1 AND stars IS NOT NULL ORDER BY updated_at DESC limit 0,1) AS t_score,
(SELECT AVG(stars) FROM seems_rateable_rates WHERE rateable_type = 'HomeworkAttach' AND rateable_id = homework_attaches.id AND is_teacher_score = 0) AS s_score
FROM homework_attaches WHERE bid_id = #{@bid.id} ORDER BY s_score DESC,created_at ASC) AS table1
- WHERE table1.t_score IS NULL")
+ WHERE table1.t_score IS NULL OR table1.t_score = 0")
@not_batch_homework = true
@cur_type = 1
else
@@ -506,7 +506,8 @@ class BidsController < ApplicationController
end
@cur_page = params[:page] || 1
- @homework_list = paginateHelper all_homework_list,10
+ # @homework_list = paginateHelper all_homework_list,10
+ @homework_list = all_homework_list
@jours_count = @bid.journals_for_messages.where('m_parent_id IS NULL').count
if params[:student_id].present?
@temp = []
@@ -792,7 +793,7 @@ class BidsController < ApplicationController
@bid.is_evaluation = params[:bid][:is_evaluation]
@bid.proportion = params[:bid][:proportion]
@bid.evaluation_num = params[:bid][:evaluation_num]
- @bid.open_anonymous_evaluation = params[:bid][:open_anonymous_evaluation]
+ params[:bid][:open_anonymous_evaluation] ? @bid.open_anonymous_evaluation = 1 : @bid.open_anonymous_evaluation = 0
@bid.reward_type = 3
# @bid.budget = params[:bid][:budget]
@bid.deadline = params[:bid][:deadline]
@@ -1035,11 +1036,12 @@ class BidsController < ApplicationController
def alert_anonymous_comment
@bid = Bid.find params[:id]
@course = @bid.courses.first
+ @cur_size = 0
+ @totle_size = 0
if @bid.comment_status == 0
@totle_size = searchStudent(@course).size
@cur_size = @bid.homeworks.size
elsif @bid.comment_status == 1
- @totle_size = 0
@bid.homeworks.map { |homework| @totle_size += homework.homework_evaluations.count}
@cur_size = 0
@bid.homeworks.map { |homework| @cur_size += homework.rates(:quality).where("seems_rateable_rates.is_teacher_score = 0").count}
diff --git a/app/controllers/boards_controller.rb b/app/controllers/boards_controller.rb
index bdbbb8e37..f6ec09b2a 100644
--- a/app/controllers/boards_controller.rb
+++ b/app/controllers/boards_controller.rb
@@ -42,12 +42,19 @@ class BoardsController < ApplicationController
elsif @course
if (User.current.admin? || @course.is_public == 1 || (@course.is_public == 0 && User.current.member_of_course?(@course)))
@boards = @course.boards.includes(:last_message => :author).all
- @boards = [] << @boards[0] if @boards.any?
- if @boards.size == 1
- @board = @boards.first
- show and return
+ if @course.boards.empty?
+ @board = @course.boards.build
+ @board.name = " #{l(:label_borad_course) }"
+ @board.description = @course.name.to_s
+ @board.project_id = -1
+ if @board.save
+ @boards = @course.boards.includes(:last_message => :author).all
+ end
end
- render :layout => 'base_courses'
+ unless @course.boards.empty?
+ @board = @course.boards.first
+ end
+ show and return
else
render_403
end
@@ -65,7 +72,7 @@ class BoardsController < ApplicationController
'replies' => "#{Message.table_name}.replies_count",
'updated_on' => "COALESCE(last_replies_messages.created_on, #{Message.table_name}.created_on)"
- @topic_count = @board.topics.count
+ @topic_count = @board ? @board.topics.count : 0
if @project
@topic_pages = Paginator.new @topic_count, per_page_option, params['page']
@topics = @board.topics.
@@ -77,14 +84,13 @@ class BoardsController < ApplicationController
preload(:author, {:last_reply => :author}).
all
elsif @course
- board_topics = @board.topics.
- reorder("#{Message.table_name}.sticky DESC").
+ board_topics = @board ? @board.topics.reorder("#{Message.table_name}.sticky DESC").
includes(:last_reply).
# limit(@topic_pages.per_page).
# offset(@topic_pages.offset).
order(sort_clause).
preload(:author, {:last_reply => :author}).
- all
+ all : []
@topics = paginateHelper board_topics,10
end
diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb
index 9f61306ab..d226000c0 100644
--- a/app/controllers/comments_controller.rb
+++ b/app/controllers/comments_controller.rb
@@ -17,6 +17,7 @@
class CommentsController < ApplicationController
default_search_scope :news
+ include ApplicationHelper
model_object News
before_filter :find_model_object
before_filter :find_project_from_association
@@ -26,9 +27,13 @@ class CommentsController < ApplicationController
raise Unauthorized unless @news.commentable?
@comment = Comment.new
- @comment.safe_attributes = params[:comment]
+ @project ? @comment.comments = params[:comment][:comments] : @comment.comments = params[:comment]
@comment.author = User.current
if @news.comments << @comment
+ if params[:asset_id]
+ ids = params[:asset_id].split(',')
+ update_kindeditor_assets_owner ids,@comment.id,OwnerTypeHelper::COMMENT
+ end
flash[:notice] = l(:label_comment_added)
end
diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb
index ebdb18c08..a1e547a84 100644
--- a/app/controllers/courses_controller.rb
+++ b/app/controllers/courses_controller.rb
@@ -1,6 +1,7 @@
class CoursesController < ApplicationController
- layout 'base_courses'
+ # layout 'base_courses'
include CoursesHelper
+ include ActivitiesHelper
helper :activities
helper :members
helper :words
@@ -30,19 +31,19 @@ class CoursesController < ApplicationController
def join
if User.current.logged?
cs = CoursesService.new
- join = cs.join_course params,User.current
+ user = User.current
+ join = cs.join_course params,user
@state = join[:state]
course = join[:course]
else
@state = 5 #未登录
end
respond_to do |format|
- format.js { render :partial => 'set_join', :locals => {:user => User.current, :course => course, :object_id => params[:object_id]} }
+ format.js { render :partial => 'set_join', :locals => {:user => user, :course => course, :object_id => params[:object_id]} }
end
rescue Exception => e
@state = 4 #已经加入了课程
respond_to do |format|
- # format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
format.js { render :partial => 'set_join', :locals => {:user => User.current, :course => nil, :object_id => nil} }
end
end
@@ -72,6 +73,7 @@ class CoursesController < ApplicationController
if @course.errors.full_messages.count <= 0
respond_to do |format|
format.html {
+ # render :layout => 'base_courses'
flash[:notice] = l(:notice_successful_update)
redirect_to settings_course_url(@course)
}
@@ -81,7 +83,7 @@ class CoursesController < ApplicationController
respond_to do |format|
format.html {
settings
- render :action => 'settings'
+ redirect_to settings_course_url(@course)
}
format.api { render_validation_errors(@course) }
end
@@ -189,8 +191,9 @@ class CoursesController < ApplicationController
results = searchmember_by_name(student_homework_score(@group.id,0,0,"desc"), q)
end
@is_remote = true
- @result_count = results.count
- @results = paginateHelper results, 10
+ #@result_count = results.count
+ #@results = paginateHelper results, 10
+ @results = results
@search_name = q
end
@@ -313,16 +316,19 @@ class CoursesController < ApplicationController
when '1'
@subPage_title = l :label_teacher_list
@all_members = searchTeacherAndAssistant(@course)
- @members = paginateHelper @all_members, 10
+ #@members = paginateHelper @all_members, 10
+ @members = @all_members
when '2'
@subPage_title = l :label_student_list
page = params[:page].nil? ? 0 : (params['page'].to_i - 1)
@all_members = student_homework_score(0,page, 10,"desc")
# @all_members = @course.members
- @members = paginateHelper_for_members @all_members, 10
+ # @members = paginateHelper_for_members @all_members, 10
+ @members = @all_members
end
respond_to do |format|
if params[:page]
+ format.html {render :layout => 'base_courses'}
format.js
else
format.html {render :layout => 'base_courses'}
@@ -402,23 +408,46 @@ class CoursesController < ApplicationController
@issue_category ||= IssueCategory.new
@member ||= @course.members.new
@trackers = Tracker.sorted.all
+
+ @roles = Role.givable.all[3..5]
+ @members = @course.member_principals.includes(:roles, :principal).all.sort
+ respond_to do |format|
+ format.html { render :layout => 'base_courses' }
+ format.api { render_validation_errors(@course) }
+ end
else
render_403
end
end
+ def search_member
+ if User.current.allowed_to?(:as_teacher,@course) || User.current.admin
+ q = "#{params[:name].strip}"
+ @roles = Role.givable.all[3..5]
+ if q.nil? || q == ""
+ @members = @course.member_principals.includes(:roles, :principal).all.sort
+ else
+ @members = searchmember_by_name(@course.member_principals.includes(:roles, :principal).all.sort,q)
+ end
+
+ else
+ render_403
+ end
+ end
+
def create
cs = CoursesService.new
@course = cs.create_course(params,User.current)[:course]
if @course.new_record?
respond_to do |format|
- format.html { render :action => 'new', :layout => 'base' } #Added by young
+ format.html { render :action => 'new', :layout => 'new_base' } #Added by young
format.api { render_validation_errors(@course) }
end
else
respond_to do |format|
format.html {
+ # render :layout => 'base_courses'
flash[:notice] = l(:notice_successful_create)
if params[:continue]
redirect_to new_course_url(attrs, :course => '0')
@@ -482,7 +511,7 @@ class CoursesController < ApplicationController
respond_to do |format|
format.html {
- render :layout => 'base'
+ render :layout => 'new_base'
}
format.api {
}
@@ -676,26 +705,50 @@ class CoursesController < ApplicationController
"show_course_files" => true,
"show_course_news" => true,
"show_course_messages" => true,
+ #"show_course_journals_for_messages" => true,
"show_bids" => true,
- "show_course_journals_for_messages" => true,
- "show_homeworks" => true
+ "show_homeworks" => true,
+ "show_polls" => true
}
@date_to ||= Date.today + 1
@date_from = (@date_to - @days) > @course.created_at.to_date ? (@date_to - @days) : @course.created_at.to_date
@author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
- @author ||= @course.teacher
- # 决定显示所用用户或单个用户活动
- @activity = Redmine::Activity::Fetcher.new(User.current, :course => @course,
- :with_subprojects => false,
- :author => @author)
- @activity.scope_select {|t| has["show_#{t}"]}
- # modify by nwb
- # 添加私密性判断
- if User.current.member_of_course?(@course)|| User.current.admin?
- events = @activity.events(@days, @course.created_at)
+ if @author.nil?
+ # 显示老师和助教的活动
+ # @authors = searchTeacherAndAssistant(@course)
+ @authors = course_all_member(@course)
+ events = []
+ @authors.each do |author|
+ @activity = Redmine::Activity::Fetcher.new(User.current, :course => @course,
+ :with_subprojects => false,
+ :author => author.user)
+
+ @activity.scope_select {|t| has["show_#{t}"]}
+ # modify by nwb
+ # 添加私密性判断
+ if User.current.member_of_course?(@course)|| User.current.admin?
+ events += @activity.events(@days, @course.created_at)
+ else
+ events += @activity.events(@days, @course.created_at, :is_public => 1)
+ end
+
+ end
else
- events = @activity.events(@days, @course.created_at, :is_public => 1)
+ # @author = @course.teacher
+ @activity = Redmine::Activity::Fetcher.new(User.current, :course => @course,
+ :with_subprojects => false,
+ :author => @author)
+
+ @activity.scope_select {|t| has["show_#{t}"]}
+ # modify by nwb
+ # 添加私密性判断
+ if User.current.member_of_course?(@course)|| User.current.admin?
+ events = @activity.events(@days, @course.created_at)
+ else
+ events = @activity.events(@days, @course.created_at, :is_public => 1)
+ end
end
+
# 无新动态时,显示老动态
if events.count == 0
if User.current.member_of_course?(@course)|| User.current.admin?
@@ -704,13 +757,17 @@ class CoursesController < ApplicationController
events = @activity.events(:is_public => 1)
end
end
- events = paginateHelper events,10
- @events_by_day = events.group_by {|event| User.current.time_to_date(event.event_datetime)}
- # documents
@sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
if(User.find_by_id(CourseInfos.find_by_course_id(@course.id).try(:user_id)))
@user = User.find_by_id(CourseInfos.find_by_course_id(@course.id).user_id)
end
+
+ sorted_events = sort_activity_events_course(events)
+ events = paginateHelper sorted_events,10
+ @events_by_day = events.group_by {|event| User.current.time_to_date(event.event_datetime)}
+ # documents
+
+
respond_to do |format|
format.html{render :layout => 'base_courses'}
format.api
@@ -823,7 +880,7 @@ class CoursesController < ApplicationController
students_for_courses.course_id = #{@course.id} and members.user_id = students_for_courses.student_id AND
members.user_id NOT IN (SELECT homework_attaches.user_id FROM homework_attaches WHERE homework_attaches.bid_id in (SELECT bid_id FROM homework_for_courses WHERE course_id = #{@course.id} )
)
- GROUP BY members.user_id ORDER BY score #{score_sort_by} limit #{start_from}, #{nums}"
+ GROUP BY members.user_id ORDER BY score #{score_sort_by} " #limit #{start_from}, #{nums}"
end
else
diff --git a/app/controllers/discuss_demos_controller.rb b/app/controllers/discuss_demos_controller.rb
deleted file mode 100644
index b0c9791cb..000000000
--- a/app/controllers/discuss_demos_controller.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-class DiscussDemosController < ApplicationController
- def index
-
- @discuss_demo_list = DiscussDemo.where("body is not null").order("created_at desc").page(params[:page] || 1).per(10)
- end
-
- def new
- @discuss_demo = DiscussDemo.create
- @discuss_demo.save!
- @discuss_demo
- end
-
- def create
-
- end
-
- def update
- @discuss_demo = DiscussDemo.find(params[:id])
- @discuss_demo.update_attributes(:title => params[:discuss_demo][:title],:body => params[:discuss_demo][:body])
- redirect_to :controller=> 'discuss_demos',:action => 'show',:id => params[:id]
- end
-
- def delete
-
- end
-
- def destroy
- asset = Kindeditor::Asset.find_by_owner_id(params[:id])
- if !asset.nil?
- filepath = File.join(Rails.root,"public","files","uploads",
- asset[:created_at].to_s.gsub("+0800","").to_datetime.strftime("%Y%m").to_s,
- asset[:asset].to_s)
- File.delete(filepath) if File.exist?filepath
- end
- DiscussDemo.destroy(params[:id])
- redirect_to :controller=> 'discuss_demos',:action => 'index'
- end
-
- def show
- @discuss_demo = DiscussDemo.find(params[:id])
- end
-end
diff --git a/app/controllers/documents_controller.rb b/app/controllers/documents_controller.rb
index 9bf2ee846..570726320 100644
--- a/app/controllers/documents_controller.rb
+++ b/app/controllers/documents_controller.rb
@@ -110,7 +110,7 @@ class DocumentsController < ApplicationController
render_attachment_warning_if_needed(@document)
if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
- Mailer.attachments_added(attachments[:files]).deliver
+ Mailer.run.attachments_added(attachments[:files])
end
redirect_to document_url(@document)
end
diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb
index 28156ac0c..69e7105aa 100644
--- a/app/controllers/files_controller.rb
+++ b/app/controllers/files_controller.rb
@@ -23,13 +23,14 @@ class FilesController < ApplicationController
before_filter :auth_login1, :only => [:index]
before_filter :logged_user_by_apptoken,:only => [:index]
before_filter :find_project_by_project_id#, :except => [:getattachtype]
- before_filter :authorize, :except => [:getattachtype,:quote_resource_show,:search]
+ before_filter :authorize, :except => [:getattachtype,:quote_resource_show,:search,:search_project,:quote_resource_show_project,:search_tag_attachment]
helper :sort
include SortHelper
include FilesHelper
helper :project_score
include CoursesHelper
+ include ApplicationHelper
def show_attachments obj
@attachments = []
@@ -41,8 +42,8 @@ class FilesController < ApplicationController
@feedback_count = @all_attachments.count
@feedback_pages = Paginator.new @feedback_count, @limit, params['page']
@offset ||= @feedback_pages.offset
- @curse_attachments_all = @all_attachments[@offset, @limit]
- @curse_attachments = paginateHelper @all_attachments,10
+ #@curse_attachments_all = @all_attachments[@offset, @limit]
+ @obj_attachments = paginateHelper @all_attachments,10
end
def search
@@ -50,6 +51,7 @@ class FilesController < ApplicationController
@sort = ""
@order = ""
@is_remote = true
+ @q = params[:name].strip
if params[:sort]
order_by = params[:sort].split(":")
@sort = order_by[0]
@@ -59,25 +61,66 @@ class FilesController < ApplicationController
sort = "#{@sort} #{@order}"
end
+ # show_attachments [@course]
+
begin
q = "%#{params[:name].strip}%"
#(redirect_to stores_url, :notice => l(:label_sumbit_empty);return) if params[:name].blank?
if params[:insite]
- @result = find_public_attache q,sort
- @result = visable_attachemnts_insite @result,@course
- @searched_attach = paginateHelper @result,10
+ if q == "%%"
+ @result = []
+ @searched_attach = paginateHelper @result,10
+ else
+ @result = find_public_attache q,sort
+ @result = visable_attachemnts_insite @result,@course
+ @searched_attach = paginateHelper @result,10
+ end
else
@result = find_course_attache q,@course,sort
@result = visable_attachemnts @result
@searched_attach = paginateHelper @result,10
- end
+ @tag_list = get_course_tag_list @course
+ end
#rescue Exception => e
# #render 'stores'
# redirect_to search_course_files_url
end
end
+ def search_project
+ sort = ""
+ @sort = ""
+ @order = ""
+ @is_remote = true
+ if params[:sort]
+ order_by = params[:sort].split(":")
+ @sort = order_by[0]
+ if order_by.count > 1
+ @order = order_by[1]
+ end
+ sort = "#{@sort} #{@order}"
+ end
+
+ begin
+ q = "%#{params[:name].strip}%"
+ #(redirect_to stores_url, :notice => l(:label_sumbit_empty);return) if params[:name].blank?
+ if params[:insite]
+ @result = find_public_attache q,sort
+ @result = visable_attachemnts_insite @result,@project
+ @searched_attach = paginateHelper @result,10
+ else
+ @result = find_project_attache q,@project,sort
+ @result = visable_attachemnts @result
+ @searched_attach = paginateHelper @result,10
+ end
+
+ #rescue Exception => e
+ # #render 'stores'
+ # redirect_to search_course_files_url
+ end
+ end
+
def find_course_attache keywords,course,sort = ""
if sort == ""
sort = "created_on DESC"
@@ -87,6 +130,30 @@ class FilesController < ApplicationController
#resultSet = Attachment.find_by_sql("SELECT `attachments`.* FROM `attachments` LEFT OUTER JOIN `homework_attaches` ON `attachments`.container_type = 'HomeworkAttach' AND `attachments`.container_id = `homework_attaches`.id LEFT OUTER JOIN `homework_for_courses` ON `homework_attaches`.bid_id = `homework_for_courses`.bid_id LEFT OUTER JOIN `homework_for_courses` AS H_C ON `attachments`.container_type = 'Bid' AND `attachments`.container_id = H_C.bid_id WHERE (`homework_for_courses`.course_id = 117 OR H_C.course_id = 117 OR (`attachments`.container_type = 'Course' AND `attachments`.container_id = 117)) AND `attachments`.filename LIKE '%#{keywords}%'").reorder("created_on DESC")
end
+ def find_project_attache keywords,project,sort = ""
+ if sort == ""
+ sort = "created_on DESC"
+ end
+ ids = ""
+ len = 0
+ count = project.versions.count
+ project.versions.each do |version|
+ len = len + 1
+ if len != count
+ ids += version.id.to_s + ','
+ else
+ ids += version.id.to_s
+ end
+ end
+ if ids.blank?
+ resultSet = Attachment.where("attachments.container_type = 'Project' And attachments.container_id = '#{project.id}' AND filename LIKE :like ", like: "%#{keywords}%").
+ reorder(sort)
+ else
+ resultSet = Attachment.where("((attachments.container_type = 'Project' And attachments.container_id = '#{project.id}') OR (container_type = 'Version' AND container_id IN (#{ids}))) AND filename LIKE :like ", like: "%#{keywords}%").
+ reorder(sort)
+ end
+ end
+
def find_public_attache keywords,sort = ""
# StoresController#search 将每条文件都查出来,再次进行判断过滤。---> resultSet.to_a.map
# 此时内容不多速度还可,但文件增长,每条判断多则进行3-4次表连接。
@@ -132,17 +199,27 @@ class FilesController < ApplicationController
attribute = "downloads"
when "created_on"
attribute = "created_on"
+ when "quotes"
+ attribute = "quotes"
+ else
+ attribute = "created_on"
end
-
- if order_by.count == 1
- sort += "#{Attachment.table_name}.#{attribute} asc " if attribute
- elsif order_by.count == 2
- sort += "#{Attachment.table_name}.#{attribute} #{order_by[1]} " if attribute && order_by[1]
- end
- if sort_type != params[:sort].split(",").last
- sort += ","
+ @sort = order_by[0]
+ @order = order_by[1]
+ if order_by.count == 1 && attribute
+ sort += "#{Attachment.table_name}.#{attribute} asc "
+ if sort_type != params[:sort].split(",").last
+ sort += ","
+ end
+ elsif order_by.count == 2 && order_by[1]
+ sort += "#{Attachment.table_name}.#{attribute} #{order_by[1]} "
+ if sort_type != params[:sort].split(",").last
+ sort += ","
+ end
end
end
+ else
+ sort = "#{Attachment.table_name}.created_on desc"
end
@containers = [ Project.includes(:attachments).find(@project.id)]
@@ -184,6 +261,8 @@ class FilesController < ApplicationController
attribute = "created_on"
when "quotes"
attribute = "quotes"
+ else
+ attribute = "created_on"
end
@sort = order_by[0]
@order = order_by[1]
@@ -199,12 +278,16 @@ class FilesController < ApplicationController
end
end
end
+ else
+ sort = "#{Attachment.table_name}.created_on desc"
end
@containers = [ Course.includes(:attachments).reorder(sort).find(@course.id)]
show_attachments @containers
+ @tag_list = attachment_tag_list @all_attachments
+
render :layout => 'base_courses'
end
@@ -215,6 +298,11 @@ class FilesController < ApplicationController
@can_quote = attachment_candown @file
end
+ def quote_resource_show_project
+ @file = Attachment.find(params[:id])
+ @can_quote = attachment_candown @file
+ end
+
def new
@versions = @project.versions.sort
@course_tag = @project.project_type
@@ -241,7 +329,7 @@ class FilesController < ApplicationController
render_attachment_warning_if_needed(container)
if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
- Mailer.attachments_added(attachments[:files]).deliver
+ Mailer.run.attachments_added(attachments[:files])
end
# TODO: 临时用 nyan
@@ -270,7 +358,28 @@ class FilesController < ApplicationController
attachments = Attachment.attach_filesex(@course, params[:attachments], params[:attachment_type])
if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
- Mailer.attachments_added(attachments[:files]).deliver
+ Mailer.run.attachments_added(attachments[:files])
+ end
+
+ if params[:course_attachment_type] && params[:course_attachment_type] != "5"
+ case params[:course_attachment_type]
+ when "1"
+ tag_name = l(:label_courseware)
+ when "2"
+ tag_name = l(:label_software)
+ when "3"
+ tag_name = l(:label_media)
+ when "4"
+ tag_name = l(:label_code)
+ else
+ tag_name = ""
+ end
+ if !attachments.empty? && attachments[:files] && tag_name != ""
+ attachments[:files].each do |attachment|
+ attachment.tag_list.add(tag_name)
+ attachment.save
+ end
+ end
end
# TODO: 临时用 nyan
@@ -283,6 +392,7 @@ class FilesController < ApplicationController
@containers = [Course.includes(:attachments).reorder("#{Attachment.table_name}.created_on DESC").find(@course.id)]
show_attachments @containers
+ @tag_list = attachment_tag_list @all_attachments
@attachtype = 0
@contenttype = 0
@@ -416,8 +526,31 @@ class FilesController < ApplicationController
format.html
end
end
+ end
+ #查找指定TAG的按条件过滤的资源列表,只有课程内搜索有此功能
+ def search_tag_attachment
+ @q,@tag_name,@order = params[:q],params[:tag_name]
+ @is_remote = true
+ if params[:sort]
+ order_by = params[:sort].split(":")
+ @sort = order_by[0]
+ if order_by.count > 1
+ @order = order_by[1]
+ end
+ sort = "#{@sort} #{@order}"
+ end
+ q = "%#{@q.strip}%"
+ @result = find_course_attache q,@course,sort
+ @result = visable_attachemnts @result
+ @result = @result.select{|attachment| attachment.tag_list.include?(@tag_name)}
+ @searched_attach = paginateHelper @result,10
+ @tag_list = get_course_tag_list @course
+ respond_to do |format|
+ format.js
+ # format.html
+ end
end
end
diff --git a/app/controllers/forums_controller.rb b/app/controllers/forums_controller.rb
index c347ba6b8..253e1d28d 100644
--- a/app/controllers/forums_controller.rb
+++ b/app/controllers/forums_controller.rb
@@ -63,8 +63,10 @@ class ForumsController < ApplicationController
respond_to do |format|
if @memo.save
- ids = params[:asset_id].split(',')
- update_kindeditor_assets_owner ids ,@memo.id,1
+ if params[:asset_id]
+ ids = params[:asset_id].split(',')
+ update_kindeditor_assets_owner ids ,@memo.id,OwnerTypeHelper::MEMO
+ end
#end
format.html { redirect_to (forum_memo_url(@forum, (@memo.parent_id.nil? ? @memo : @memo.parent_id))), notice: "#{l :label_memo_create_succ}" }
format.json { render json: @memo, status: :created, location: @memo }
@@ -170,8 +172,10 @@ class ForumsController < ApplicationController
# Author lizanle
# Description after save后需要进行资源记录的更新
# owner_type = 2 对应的是 forum
- ids = params[:asset_id].split(',')
- update_kindeditor_assets_owner ids ,@forum.id,2
+ if params[:asset_id]
+ ids = params[:asset_id].split(',')
+ update_kindeditor_assets_owner ids ,@forum.id,OwnerTypeHelper::FORUM
+ end
#end
respond_to do |format|
diff --git a/app/controllers/homework_attach_controller.rb b/app/controllers/homework_attach_controller.rb
index ba7e92467..4982a4b6b 100644
--- a/app/controllers/homework_attach_controller.rb
+++ b/app/controllers/homework_attach_controller.rb
@@ -22,6 +22,7 @@ class HomeworkAttachController < ApplicationController
#获取未批作业列表
def get_not_batch_homework
@not_batch_homework = true
+ @search_name = params[:name]
sort, direction = params[:sort] || "s_socre", params[:direction] || "desc"
get_not_batch_homework_list sort,direction, @bid.id
@cur_page = params[:page] || 1
@@ -39,6 +40,7 @@ class HomeworkAttachController < ApplicationController
#获取已评作业列表
def get_batch_homeworks
+ @search_name = params[:name]
sort, direction = params[:sort] || "s_socre", params[:direction] || "desc"
@is_batch_homeworks = true
if sort == 't_socre'
@@ -49,14 +51,16 @@ class HomeworkAttachController < ApplicationController
order_by = "created_at #{direction}"
end
all_homework_list = HomeworkAttach.eager_load(:attachments,:user,:rate_averages).find_by_sql("SELECT * FROM (SELECT homework_attaches.*,
- (SELECT stars FROM seems_rateable_rates WHERE rateable_type = 'HomeworkAttach' AND rateable_id = homework_attaches.id AND is_teacher_score = 1 AND stars IS NOT NULL ORDER BY updated_at DESC limit 0,1) AS t_score,
+ (SELECT stars FROM seems_rateable_rates WHERE rateable_type = 'HomeworkAttach' AND rateable_id = homework_attaches.id AND is_teacher_score = 1 AND stars IS NOT NULL AND stars > 0 ORDER BY updated_at DESC limit 0,1) AS t_score,
(SELECT AVG(stars) FROM seems_rateable_rates WHERE rateable_type = 'HomeworkAttach' AND rateable_id = homework_attaches.id AND is_teacher_score = 0) AS s_score
FROM homework_attaches WHERE bid_id = #{@bid.id}
ORDER BY #{order_by}) AS table1
WHERE table1.t_score IS NOT NULL")
+ all_homework_list = search_homework_member(all_homework_list,@search_name.to_s.downcase) if @search_name
@cur_page = params[:page] || 1
@cur_type = 2
- @homework_list = paginateHelper all_homework_list,10
+ # @homework_list = paginateHelper all_homework_list,10
+ @homework_list = all_homework_list
@direction = direction == 'asc'? 'desc' : 'asc'
respond_to do |format|
format.js
@@ -72,6 +76,7 @@ class HomeworkAttachController < ApplicationController
#获取所有作业列表
def get_homeworks
@is_all_homeworks = true
+ @search_name = params[:name]
sort, direction = params[:sort] || "s_socre", params[:direction] || "desc"
if sort == 't_socre'
order_by = "t_score #{direction}"
@@ -85,9 +90,12 @@ class HomeworkAttachController < ApplicationController
(SELECT AVG(stars) FROM seems_rateable_rates WHERE rateable_type = 'HomeworkAttach' AND rateable_id = homework_attaches.id AND is_teacher_score = 0) AS s_score
FROM homework_attaches WHERE bid_id = #{@bid.id}
ORDER BY #{order_by}")
+
+ all_homework_list = search_homework_member(all_homework_list,@search_name.to_s.downcase) if @search_name
@cur_page = params[:page] || 1
@cur_type = 3
- @homework_list = paginateHelper all_homework_list,10
+ # @homework_list = paginateHelper all_homework_list,10
+ @homework_list = all_homework_list
@direction = direction == 'asc'? 'desc' : 'asc'
respond_to do |format|
format.js
@@ -104,7 +112,8 @@ class HomeworkAttachController < ApplicationController
all_homework_list = get_student_batch_homework_list @bid,User.current
@cur_page = params[:page] || 1
@cur_type = 4
- @homework_list = paginateHelper all_homework_list,10
+ # @homework_list = paginateHelper all_homework_list,10
+ @homework_list = all_homework_list
respond_to do |format|
format.js
end
@@ -128,7 +137,8 @@ class HomeworkAttachController < ApplicationController
WHERE homework_attaches.bid_id = #{@bid.id} AND homework_users.user_id = #{User.current.id}")
end
@cur_page = params[:page] || 1
- @homework_list = paginateHelper all_homework_list,10
+ # @homework_list = paginateHelper all_homework_list,10
+ @homework_list = all_homework_list
respond_to do |format|
format.js
end
@@ -308,7 +318,7 @@ class HomeworkAttachController < ApplicationController
def edit
bid = @homework.bid
- if (bid.comment_status == 0 || bid.open_anonymous_evaluation == 0) && (User.current.admin? || User.current.member_of_course?(bid.courses.first))
+ if (bid.comment_status == 0 || bid.open_anonymous_evaluation == 0 || bid.comment_status == 2) && (User.current.admin? || User.current.member_of_course?(bid.courses.first))
get_homework_member @homework
else
render_403 :message => :notice_not_authorized
@@ -341,7 +351,7 @@ class HomeworkAttachController < ApplicationController
def destroy
bid = @homework.bid
- if (bid.comment_status == 0 || bid.open_anonymous_evaluation == 0) && (User.current.admin? || User.current == @homework.user)
+ if (bid.comment_status == 0 || bid.open_anonymous_evaluation == 0 || bid.comment_status == 0) && (User.current.admin? || User.current == @homework.user)
if @homework.destroy
#respond_to do |format|
# format.html { redirect_to course_for_bid_url @homework.bid }
@@ -434,14 +444,15 @@ class HomeworkAttachController < ApplicationController
homework = @homework
is_teacher = @is_teacher ? 1 : 0
#保存评分@homework.rate(@m_score.to_i,User.current.id,:quality, (@is_teacher ? 1 : 0))
- if @m_score
+ @is_comprehensive_evaluation = @is_teacher ? 1 : (@is_anonymous_comments ? 2 : 3) #判断当前评论是老师评论?匿评?留言
+ if @m_score && (@is_teacher || @is_anonymous_comments)
rate = @homework.rates(:quality).where(:rater_id => User.current.id, :is_teacher_score => is_teacher).first
if rate
rate.stars = @m_score
- rate.save!
else
- @homework.rates(:quality).new(:stars => @m_score, :rater_id => User.current.id, :is_teacher_score => is_teacher).save!
+ rate = @homework.rates(:quality).new(:stars => @m_score, :rater_id => User.current.id, :is_teacher_score => is_teacher)
end
+ rate.save!
if homework.is_teacher_score == 0
if is_teacher == 1
@@ -459,12 +470,26 @@ class HomeworkAttachController < ApplicationController
end
end
homework.save!
- end
+ end
#保存评论
- @is_comprehensive_evaluation = @is_teacher ? 1 : (@is_anonymous_comments ? 2 : 3) #判断当前评论是老师评论?匿评?留言
- if params[:new_form] && params[:new_form][:user_message] && params[:new_form][:user_message] != "" #有没有留言
- @homework.addjours User.current.id, params[:new_form][:user_message],0,@is_comprehensive_evaluation
+ if params[:new_form] && params[:new_form][:user_message] #有没有留言
+ jour = @homework.journals_for_messages.where("is_comprehensive_evaluation = 1 and user_id = #{User.current.id}").order("created_on DESC").first
+ if params[:new_form][:user_message] == ""
+ if @is_teacher
+ unless jour
+ jour = @homework.addjours User.current.id, "",0,@is_comprehensive_evaluation
+ end
+ end
+ else
+ jour = @homework.addjours User.current.id, params[:new_form][:user_message],0,@is_comprehensive_evaluation
+ end
+ end
+
+ if jour
+ jour.save_attachments(params[:attachments])
+ render_attachment_warning_if_needed(jour)
+ jour.save
end
@teacher_stars = @stars_reates.where("is_teacher_score = 1") #老师评分列表
@@ -477,7 +502,7 @@ class HomeworkAttachController < ApplicationController
get_not_batch_homework_list params[:cur_sort] || "s_socre",params[:cur_direction] || "desc",@homework.bid_id
elsif @cur_type == "2" #老师已批列表
@result_homework = HomeworkAttach.find_by_sql("SELECT homework_attaches.*,
- (SELECT stars FROM seems_rateable_rates WHERE rateable_type = 'HomeworkAttach' AND rateable_id = homework_attaches.id AND is_teacher_score = 1 AND stars IS NOT NULL ORDER BY updated_at DESC limit 0,1) AS t_score,
+ (SELECT stars FROM seems_rateable_rates WHERE rateable_type = 'HomeworkAttach' AND rateable_id = homework_attaches.id AND is_teacher_score = 1 AND stars IS NOT NULL AND stars > 0 ORDER BY updated_at DESC limit 0,1) AS t_score,
(SELECT AVG(stars) FROM seems_rateable_rates WHERE rateable_type = 'HomeworkAttach' AND rateable_id = homework_attaches.id AND is_teacher_score = 0) AS s_score
FROM homework_attaches WHERE id = #{@homework.id}").first
elsif @cur_type == "3" #全部作业列表
@@ -538,6 +563,14 @@ class HomeworkAttachController < ApplicationController
end
private
+ #根据条件过滤作业结果
+ def search_homework_member homeworks,name
+ select_homework = homeworks.select{ |homework|
+ homework.user[:login].to_s.downcase.include?(name) || homework.user.user_extensions[:student_id].to_s.downcase.include?(name) || (homework.user[:lastname].to_s.downcase + homework.user[:firstname].to_s.downcase).include?(name)
+ }
+ select_homework
+ end
+
#验证是否显示课程
def can_show_course
@first_page = FirstPage.find_by_page_type('project')
@@ -596,8 +629,10 @@ class HomeworkAttachController < ApplicationController
(SELECT AVG(stars) FROM seems_rateable_rates WHERE rateable_type = 'HomeworkAttach' AND rateable_id = homework_attaches.id AND is_teacher_score = 0) AS s_score
FROM homework_attaches WHERE bid_id = #{bid_id}
ORDER BY #{order_by}) AS table1
- WHERE table1.t_score IS NULL")
- @homework_list = paginateHelper @all_homework_list,10
+ WHERE table1.t_score IS NULL OR table1.t_score = 0 ")
+ @all_homework_list = search_homework_member(@all_homework_list,@search_name.to_s.downcase) if @search_name
+ # @homework_list = paginateHelper @all_homework_list,10
+ @homework_list = @all_homework_list
end
#获取指定作业的所有成员
diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb
index 790718e5d..80be8c5d5 100644
--- a/app/controllers/issues_controller.rb
+++ b/app/controllers/issues_controller.rb
@@ -95,6 +95,7 @@ class IssuesController < ApplicationController
format.api {
Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
}
+ # format.json { render :json => @issues.map { |issue| issue.to_json}} #:json => @issues.map { |issue| issue.to_json}
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
format.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') }
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'issues.pdf') }
diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb
index aaaa4484d..6271a6832 100644
--- a/app/controllers/members_controller.rb
+++ b/app/controllers/members_controller.rb
@@ -60,8 +60,12 @@ class MembersController < ApplicationController
user_ids.each do |user_id|
AppliedProject.deleteappiled(user_id, @project.id)
end
+ @succes_message = "拒绝成功"
end
end
+ respond_to do |format|
+ format.js
+ end
else
#modify by nwb
#更改课程成员逻辑
@@ -117,9 +121,14 @@ class MembersController < ApplicationController
format.html { redirect_to invite_members_project_url(@project) }
end
else
+ unless members.present? && members.all? {|m| m.valid? }
+ @project_error_message = members.empty? ? l(:label_user_role_null) :members.collect {|m| m.errors.full_messages}.flatten.uniq.join(', ')
+ else
+ @succes_message = "添加成功"
+ end
respond_to do |format|
format.html { redirect_to_settings_in_projects }
- format.js { @members = members; @applied_members = applied_members; }
+ format.js
format.api {
@member = members.first
if @member.valid?
@@ -133,6 +142,9 @@ class MembersController < ApplicationController
elsif @course
course_info = []
if params[:membership]
+ @create_member_error_messages = "角色不能留空" unless params[:membership][:role_ids]
+ @create_member_error_messages = "用户不能留空" unless params[:membership][:user_ids]
+
if params[:membership][:user_ids]
attrs = params[:membership].dup
user_ids = attrs.delete(:user_ids)
@@ -140,7 +152,7 @@ class MembersController < ApplicationController
member = Member.new(:role_ids => params[:membership][:role_ids], :user_id => user_id)
role = Role.find_by_id(params[:membership][:role_ids])
# 这里的判断只能通过角色名,可以弄成常量
- if role.name == "学生" || role.name == "Student"
+ if role && (role.name == "学生" || role.name == "Student")
StudentsForCourse.create(:student_id => user_id, :course_id =>@course.id)
end
members << member
@@ -159,6 +171,11 @@ class MembersController < ApplicationController
end
@course.members << members
@course.course_infos << course_info
+
+ @roles = Role.givable.all[3..5]
+ members = @course.member_principals.includes(:roles, :principal).all.sort
+ else
+ @create_member_error_messages = l(:label_user_role_null)
end
respond_to do |format|
format.html { redirect_to_settings_in_courses }
@@ -176,6 +193,8 @@ class MembersController < ApplicationController
end # end of params[:refusal_button]
+
+
end
def update
@@ -242,6 +261,9 @@ class MembersController < ApplicationController
end
end
end
+ @roles = Role.givable.all[3..5]
+ @members = @course.member_principals.includes(:roles, :principal).all.sort
+ @member = @course.members.new
end
saved = @member.save
@@ -304,7 +326,8 @@ class MembersController < ApplicationController
joined.each do |join|
join.delete
end
-
+ @roles = Role.givable.all[3..5]
+ @members = @course.member_principals.includes(:roles, :principal).all.sort
end
respond_to do |format|
format.html { redirect_to_settings_in_courses }
diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb
index b50b2720f..436418430 100644
--- a/app/controllers/messages_controller.rb
+++ b/app/controllers/messages_controller.rb
@@ -86,6 +86,11 @@ class MessagesController < ApplicationController
if request.post?
@message.save_attachments(params[:attachments])
if @message.save
+ # 更新kindeditor上传的图片资源所有者
+ if params[:asset_id]
+ ids = params[:asset_id].split(',')
+ update_kindeditor_assets_owner ids,@message.id,OwnerTypeHelper::MESSAGE
+ end
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
render_attachment_warning_if_needed(@message)
redirect_to board_message_url(@board, @message)
@@ -117,6 +122,10 @@ class MessagesController < ApplicationController
@topic.children << @reply
#@topic.update_attribute(:updated_on, Time.now)
if !@reply.new_record?
+ if params[:asset_id]
+ ids = params[:asset_id].split(',')
+ update_kindeditor_assets_owner ids,@reply.id,OwnerTypeHelper::MESSAGE
+ end
call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
attachments = Attachment.attach_files(@reply, params[:attachments])
render_attachment_warning_if_needed(@reply)
@@ -183,16 +192,7 @@ class MessagesController < ApplicationController
@content = "> #{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> "
@temp = Message.new
- #@temp.content = "> #{ll(Setting.default_language, :text_user_wrote, @message.author)}> "
- @content << @message.content.to_s.strip.gsub(%r{
((.|\s)*?)}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" - @content_html = textilizable(@content) - @temp.content = @content_html - #@content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}
((.|\s)*?)}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n") + "\n\n
" << @content - #@temp = Message.new - #@temp.content = @content - + @temp.content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}".html_safe end def preview diff --git a/app/controllers/my_controller.rb b/app/controllers/my_controller.rb index 7ff015fbc..d42a750a1 100644 --- a/app/controllers/my_controller.rb +++ b/app/controllers/my_controller.rb @@ -20,7 +20,7 @@ class MyController < ApplicationController # edit before_filter :auth_login1, :only => [:account] # - before_filter :require_login + before_filter :require_login, except: [:change_mail_notification] helper :issues helper :users @@ -75,6 +75,19 @@ class MyController < ApplicationController end end + def change_mail_notification + token = params[:token] + user = try_to_autologin1 + if user + user.mail_notification = params[:mail_notification] + user.save + flash[:notice] = l(:notice_mail_notification_updated) + redirect_to my_account_url + else + redirect_to signin_url + end + end + # Edit user's account def account @user = User.current diff --git a/app/controllers/news_controller.rb b/app/controllers/news_controller.rb index 617e56575..f71a77ca7 100644 --- a/app/controllers/news_controller.rb +++ b/app/controllers/news_controller.rb @@ -17,6 +17,7 @@ class NewsController < ApplicationController layout 'base_projects'# by young + include ApplicationHelper before_filter :authorize1, :only => [:show] default_search_scope :news model_object News @@ -136,6 +137,10 @@ class NewsController < ApplicationController @news.safe_attributes = params[:news] @news.save_attachments(params[:attachments]) if @news.save + if params[:asset_id] + ids = params[:asset_id].split(',') + update_kindeditor_assets_owner ids,@news.id,OwnerTypeHelper::NEWS + end render_attachment_warning_if_needed(@news) flash[:notice] = l(:notice_successful_create) redirect_to course_news_index_url(@course) @@ -147,6 +152,9 @@ class NewsController < ApplicationController end def edit + if @course + render :layout => "base_courses" + end end def update diff --git a/app/controllers/poll_controller.rb b/app/controllers/poll_controller.rb index b897d039a..32ec3dad2 100644 --- a/app/controllers/poll_controller.rb +++ b/app/controllers/poll_controller.rb @@ -29,7 +29,7 @@ class PollController < ApplicationController end #已提交问卷的用户不能再访问该界面 if has_commit_poll?(@poll.id,User.current.id) && (!User.current.admin?) - render_403 + redirect_to poll_index_url(:polls_type => "Course", :polls_group_id => @course.id) else @can_edit_poll = (!has_commit_poll?(@poll.id,User.current.id)) || User.current.admin? @percent = get_percent(@poll,User.current) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 41876e041..96807d2dc 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -27,15 +27,7 @@ class ProjectsController < ApplicationController menu_item :feedback, :only => :feedback menu_item :share, :only => :share - before_filter :find_project, :except => [ :index, :search,:list, :new, :create, :copy, :statistics, :new_join, - :course, :enterprise_course, :course_enterprise,:view_homework_attaches] - before_filter :authorize, :only => [:show, :settings, :edit, :sort_project_members, :update, :modules, :close, - :reopen,:view_homework_attaches,:course] before_filter :find_project, :except => [ :index, :search,:list, :new, :create, :copy, :statistics, :new_join, :course, :enterprise_course, :course_enterprise,:view_homework_attaches,:join_project] - # before_filter :authorize, :except => [:new_join, :new_homework, :homework, :statistics, :search, :watcherlist, :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy, :member, :focus, :file, - # :statistics, :feedback, :course, :enterprise_course, :course_enterprise, :project_respond, :share, - # :show_projects_score, :issue_score_index, :news_score_index, :file_score_index, :code_submit_score_index, :projects_topic_score_index] - #此条勿删 课程相关权限 ,:new_homework,:homework,:feedback,,:member before_filter :authorize, :only => [:show, :settings, :edit, :sort_project_members, :update, :modules, :close, :reopen,:view_homework_attaches,:course] before_filter :authorize_global, :only => [:new, :create,:view_homework_attaches] before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy, :calendar] @@ -181,6 +173,7 @@ class ProjectsController < ApplicationController @project.safe_attributes = params[:project] @project.organization_id = params[:organization_id] @project.user_id = User.current.id + @project.project_new_type = 1 if validate_parent_id && @project.save @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') # Add current user as a project member if he is not admin @@ -250,18 +243,13 @@ class ProjectsController < ApplicationController # Author lizanle # Description 项目动态展示方法,删除了不必要的代码 def show - - # 试图跳转到请求的按钮 - if params[:login] - login = params[:login] - login = login.sub(/%40/,'@') - mail = params[:login] - password = params[:password] - us = UsersService.new - user = us.register_auto(login,mail, password) + # params[:login]为邮箱邀请用户加入,主要功能: + # 1、自动注册 + # 2、加入项目、创建角色 + # 3、用户得分 + if params[:email] + user = User.find_by_mail(params[:email].to_s) Member.create(:role_ids => [4], :user_id => user.id,:project_id => @project.id) - UserGrade.create(:user_id => user.id, :project_id => @project.id) - User.current = user unless User.current.nil? end if params[:jump] && redirect_to_project_menu_item(@project, params[:jump]) return @@ -331,14 +319,46 @@ class ProjectsController < ApplicationController @member ||= @project.members.new @trackers = Tracker.sorted.all @wiki ||= @project.wiki + @select_tab = params[:tab] + + # 处理从新建版本库返回来的错误信息 + if !params[:repository_error_message].to_s.blank? + html = "" + errors = params[:repository_error_message].flatten + errors.each do |error| + # 版本库路径为空的错误信息不予提示 + if(error!=l(:label_repository_path_not_null)) + html << error << ";" + end + end + if params[:repository] == "pswd_is_null" + html << l(:label_password_not_null) + end + flash[:error] = html if !html.to_s.blank? + end + scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first + @repository = Repository.factory(scm) + @repository.is_default = @project.repository.nil? + @repository.project = @project + end + # 两种情况:1、系统外用户;2、系统内用户 (通过邮件判定) def send_mail_to_member if !params[:mail].blank? && User.find_by_mail(params[:mail].to_s).nil? email = params[:mail] - Mailer.send_invite_in_project(email, @project, User.current).deliver + Mailer.run.send_invite_in_project(email, @project, User.current) @is_zhuce =false flash[:notice] = l(:notice_email_sent, :value => email) + elsif !User.find_by_mail(params[:mail].to_s).nil? + user = User.find_by_mail(params[:mail].to_s) + if !user.member_of?(@project) + email = params[:mail] + Mailer.run.request_member_to_project(email, @project, User.current) + flash[:notice] = l(:notice_email_sent, :value => email) + else + flash[:error] = l(:label_member_of_project, :value => email) + end else flash[:error] = l(:notice_registed_error, :value => email) @is_zhuce = true @@ -347,6 +367,7 @@ class ProjectsController < ApplicationController format.html{redirect_to invite_members_by_mail_project_url(@project)} end end + #发送邮件邀请新用户 def invite_members_by_mail if User.current.member_of?(@project) || User.current.admin? @@ -379,7 +400,7 @@ class ProjectsController < ApplicationController # include CoursesHelper def member ## 有角色参数的才是课程,没有的就是项目 - @render_file = 'member_list' + @render_file = 'project_member_list' # 判断是否课程 if @project.project_type == Project::ProjectType_course @teachers= searchTeacherAndAssistant(@project) @@ -455,22 +476,10 @@ class ProjectsController < ApplicationController def update @project.safe_attributes = params[:project] @project.organization_id = params[:organization_id] - #@project.dts_test = params[:project][:dts_test] + params[:project][:is_public] ? @project.is_public = 1 : @project.is_public = 0 + params[:project][:hidden_repo] ? @project.hidden_repo = 1 : @project.hidden_repo = 0 if validate_parent_id && @project.save - @course = Course.find_by_extra(@project.identifier) - unless @course.nil? - @course.password = params[:project][:course][:password] - # added by bai - @course.term = params[:term] - @course.time = params[:time] - @course.setup_time = params[:setup_time] - @course.endup_time = params[:endup_time] - @course.class_period = params[:class_period] - # end - @course.save - end @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') - if params[:project][:is_public] == '0' project_status = ProjectStatus.find_by_project_id(@project.id) project_status.destroy if project_status @@ -618,6 +627,17 @@ class ProjectsController < ApplicationController end end + #朋友圈、科研组、开发组之间的切换 + def change_project_type + @project.project_new_type = params[:project_type] + if @project.save + message = @project.project_new_type + else + message = "0" + end + render :json => message + end + private def memberAccess diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 6c115fc11..5fe953563 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -25,6 +25,7 @@ class ChangesetNotFound < Exception; end class InvalidRevisionParam < Exception; end class RepositoriesController < ApplicationController + include ApplicationHelper menu_item :repository menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers] default_search_scope :changesets @@ -122,7 +123,7 @@ update if request.post? && @repository.save redirect_to settings_project_url(@project, :tab => 'repositories') else - render :action => 'new' + redirect_to settings_project_url(@project, :tab => 'repositories') end else # 原逻辑 ##xianbo @@ -131,7 +132,7 @@ update @project_path=@root_path+"htdocs/"+@repository_name @repository_tag=params[:repository][:upassword] || params[:repository][:password] @repo_name=User.current.login.to_s+"_"+params[:repository][:identifier] - logger.info "htpasswd -mb "+@root_path+"user.passwd "+@repo_name+": "+@repository_tag + logger.info "htpasswd -mb "+@root_path+"htdocs/user.passwd "+@repo_name+": "+@repository_tag logger.info "the value of create repository"+@root_path+": "+@repository_name+": "+@project_path+": "+@repo_name attrs = pickup_extra_info if((@repository_tag!="")&¶ms[:repository_scm]=="Git") @@ -147,9 +148,9 @@ update @repository.project = @project if request.post? && @repository.save if(params[:repository_scm]=="Git") - system "htpasswd -mb "+@root_path+"user.passwd "+@repo_name+" "+@repository_tag + system "htpasswd -mb "+@root_path+"htdocs/user.passwd "+@repo_name+" "+@repository_tag system "echo -e '"+@repo_name+"-write:"+ - " "+@repo_name+"' >> "+@root_path+"group.passwd" + " "+@repo_name+"' >> "+@root_path+"htdocs/group.passwd" system "mkdir "+@root_path+"htdocs/"+User.current.login.to_s system "git init --bare "+@project_path system "mv "+@project_path+"/hooks/post-update{.sample,}" @@ -167,11 +168,12 @@ update @repository.update_attributes(:login => User.current.login.to_s) end - redirect_to settings_project_url(@project, :tab => 'repositories') - else if(@repository_tag) - render :action => 'newrepo', :layout =>'base_projects' + redirect_to settings_project_url(@project, :tab => 'repositories',:repository_error_message=>@repository.errors.full_messages) + else if(@repository_tag.blank?) + #render :action => 'newrepo', :layout =>'base_projects' + redirect_to settings_project_url(@project, :tab => 'repositories',:repository => "pswd_is_null",:repository_error_message=>@repository.errors.full_messages) else - render :action => 'new', :layout =>'base_projects' + redirect_to settings_project_url(@project, :tab => 'repositories',:repository => @repository,:repository_error_message=>@repository.errors.full_messages) end end @@ -234,24 +236,10 @@ update end def destroy - @root_path=RepositoriesHelper::ROOT_PATH - @repo_name=User.current.login.to_s+"_"+@repository.identifier.to_s - @repository_name=User.current.login.to_s+"/"+@repository.identifier.to_s+".git" - @middle=User.current.login.to_s+"_"+@repository.identifier.to_s+"-write:" - @repository.destroy if request.delete? + DestroyRepositoryTask.new.destroy(User.current.id, @repository.id) + @repository.hidden = true + @repository.save redirect_to settings_project_url(@project, :tab => 'repositories') - if(@repository.type=="Repository::Git") - logger.info "destory the repository value"+"root path"+@root_path+"repo_name"+@repo_name+ - "repository_name"+@repository_name+"user group"+@middle - system "sed -i /"+@repo_name+"/{d} "+@root_path+"user.passwd" - system "sed -i /"+@middle+"/{d} "+@root_path+"group.passwd" - system "rm -r "+@root_path+"htdocs/"+@repository_name - # if(@sed_user&&@sed_group&&@remove) - # else - # logger.info "An error occured when destory the repository"+"delete form passwd: \n"+ - # @sed_user+"delete from group"+@sed_group+"delete from file"+@remove - # end - end end def show diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index c60d1bd8f..93e0e9c4b 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -29,6 +29,9 @@ class SettingsController < ApplicationController end def edit + hidden_non_project = Setting.find_by_name("hidden_non_project") + @text = (hidden_non_project && hidden_non_project.value == "0") ? l(:label_show_non_project) : l(:label_hidden_non_project) + @notifiables = Redmine::Notifiable.all if request.post? && params[:settings] && params[:settings].is_a?(Hash) settings = (params[:settings] || {}).dup.symbolize_keys @@ -70,4 +73,20 @@ class SettingsController < ApplicationController rescue Redmine::PluginNotFound render_404 end + + #隐藏/显示非项目信息 + def hidden_non_project + @notifiable = Setting.find_by_name("hidden_non_project") + if @notifiable + @notifiable.value == "1" ? @notifiable.value = 0 : @notifiable.value = 1 + @notifiable.save + else + @notifiable = Setting.new() + @notifiable.name = "hidden_non_project" + @notifiable.value = 0 + @notifiable.save + end + + redirect_to settings_url + end end diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index e26901320..253faa2b1 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -220,11 +220,18 @@ class TagsController < ApplicationController @tag = ActsAsTaggableOn::Tag.find_by_id(@tag_id) @tag.delete unless @tag.nil? end + + if @obj && @object_flag == '6' && @obj.container.kind_of?(Course) + @course = @obj.container + @tag_list = get_course_tag_list @course + @select_tag_name = params[:select_tag_name] + end # end end end def tag_save + @select_tag_name = params[:tag_for_save][:tag_name] @tags = params[:tag_for_save][:name] @obj_id = params[:tag_for_save][:object_id] @obj_flag = params[:tag_for_save][:object_flag] @@ -263,6 +270,10 @@ class TagsController < ApplicationController else logger.error "#{__FILE__}:#{__LINE__} ===> #{@obj.errors.try(:full_messages)}" end + if @obj && @obj_flag == '6' && @obj.container.kind_of?(Course) + @course = @obj.container + @tag_list = @tag_list = get_course_tag_list @course + end respond_to do |format| format.js format.html diff --git a/app/controllers/test_controller.rb b/app/controllers/test_controller.rb index 22cf7d1d5..51ff293f1 100644 --- a/app/controllers/test_controller.rb +++ b/app/controllers/test_controller.rb @@ -57,5 +57,98 @@ class TestController < ApplicationController attach.filename end + def mailer() + raise unless Rails.env.development? + @user = User.find(params[:user_id]) + send_for_user_activities(@user, Time.now,1) + render 'mailer/send_for_user_activities' + end + def send_for_user_activities(user, date_to, days) + date_from = date_to - days.days + + subject = "[ #{user.show_name}#{l(:label_day_mail)}]" + @subject = " #{user.show_name}#{l(:label_day_mail)}" + + date_from = "#{date_from} 17:59:59" + date_to = "#{date_to} 17:59:59" + + # 生成token用于直接点击登录 + @user = user + token = Token.new(:user =>user , :action => 'autologin') + token.save + @token = token + + # 查询user参加的项目及课程 + projects = user.projects + courses = user.courses + project_ids = projects.map{|project| project.id}.join(",") + course_ids = courses.map {|course| course.id}.join(",") + + # 查询user的缺陷,包括发布的,跟踪的以及被指派的缺陷 + sql = "select DISTINCT i.* from issues i, watchers w + where (i.assigned_to_id = #{user.id} or i.author_id = #{user.id} + or (w.watchable_type = 'Issue' and w.watchable_id = i.id and w.user_id = #{user.id})) + and (i.created_on between '#{date_from}' and '#{date_to}') order by i.created_on desc" + @issues = Issue.find_by_sql(sql) + + # @bids 查询课程作业,包括老师发布的作业,以及user提交作业 + # @attachments查询课程课件更新 + @attachments ||= [] + + @bids ||= [] # 老师发布的作业 + + unless courses.first.nil? + count = courses.count + count = count - 1 + for i in 0..count do + bids = courses[i].homeworks.where("bids.created_on between '#{date_from}' and '#{date_to}'").order("bids.created_on desc") + attachments = courses[i].attachments.where("attachments.created_on between '#{date_from}' and '#{date_to}'").order('attachments.created_on DESC') + @bids += bids if bids.count > 0 + @attachments += attachments if attachments.count > 0 + end + end + # user 提交的作业 + @homeworks = HomeworkAttach.where("user_id=#{user.id} and (created_at between '#{date_from}' and '#{date_to}')").order("created_at desc") + + # 查询user在课程。项目中发布的讨论帖子 + messages = Message.find_by_sql("select DISTINCT * from messages where author_id = #{user.id} and (created_on between '#{date_from}' and '#{date_to}') order by created_on desc") + @course_messages ||= [] + @project_messages ||= [] + unless messages.first.nil? + messages.each do |msg| + if msg.project + @project_messages << msg + elsif msg.course + @course_messages << msg + end + end + end + # 查询user在课程中发布的通知,项目中发的新闻 + @course_news = (course_ids && !course_ids.empty?) ? News.find_by_sql("select DISTINCT n.* from news n + where n.course_id in (#{course_ids}) + and (created_on between '#{date_from}' and '#{date_to}') order by created_on desc") : [] + @project_news = (project_ids && !project_ids.empty?) ? News.find_by_sql("select DISTINCT n.* from news n where n.project_id in (#{project_ids}) + and (created_on between '#{date_from}' and '#{date_to}') order by created_on desc") : [] + + # 查询user在课程及个人中留言 + @course_journal_messages = JournalsForMessage.find_by_sql("select DISTINCT * from journals_for_messages where + jour_type='Course' and user_id = #{user.id} + and (created_on between '#{date_from}' and '#{date_to}') order by created_on desc") + @user_journal_messages = user.journals_for_messages.where("m_parent_id IS NULL and (created_on between '#{date_from}' and '#{date_to}')").order('created_on DESC') + + + # 查询user新建贴吧或发布帖子 + @forums = Forum.find_by_sql("select DISTINCT * from forums where creator_id = #{user.id} and (created_at between '#{date_from}' and '#{date_to}') order by created_at desc") + @memos = Memo.find_by_sql("select DISTINCT m.* from memos m, forums f where (m.author_id = #{user.id} or (m.forum_id = f.id and f.creator_id = #{user.id})) + and (m.created_at between '#{date_from}' and '#{date_to}') order by m.created_at desc") + + + has_content = [@issues,@homeworks,@course_messages,@project_messages,@course_news,@project_news, + @course_journal_messages,@user_journal_messages,@forums,@memos,@attachments,@bids].any? {|o| + !o.empty? + } + #有内容才发,没有不发 + end + -end \ No newline at end of file +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4db471ab4..2a10af260 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -438,13 +438,84 @@ class UsersController < ApplicationController # Description 所有动态 where_condition = nil; # where_condition = "act_type <> 'JournalsForMessage'" + user_ids = [] if @user == User.current watcher = User.watched_by(@user) watcher.push(User.current) - activity = Activity.where(where_condition).where('user_id in (?)', watcher).order('id desc') + user_ids = watcher.map{|x| x.id} else - activity = Activity.where(where_condition).where('user_id = ?', @user.id).order('id desc') + user_ids << @user.id end + activity = Activity.where(where_condition).where(user_id: user_ids).order('id desc') + + permission = !User.current.admin? + if permission + #Issue + act_ids = activity.where(act_type: 'Issue').select('act_id').map{|x| x.act_id} + project_ids = Issue.where(id: act_ids).select('distinct project_id').map{|x| x.project_id} + p_ids = [] + Project.where(id: project_ids).each do |x| + p_ids << x.id unless x.visible?(User.current) + end + ids = [] + ids << Issue.where(id: act_ids, project_id: p_ids).map{|x| x.id} + + #Bid + act_ids = activity.where(act_type: 'Bid').select('act_id').map{|x| x.act_id} + course_ids = HomeworkForCourse.where(bid_id: act_ids).select('distinct course_id').map{|x| x.course_id} + c_ids = [] + Course.where(id: course_ids).each do |x| + c_ids << x.id unless x.is_public !=0 && User.current.member_of_course?(x) + end + ids << HomeworkForCourse.where(bid_id: act_ids, course_id: c_ids).map{|x| x.id} + + #Journal + act_ids = activity.where(act_type: 'Journal').select('act_id').map{|x| x.act_id} + project_ids = Journal.where(id:act_ids, journalized_type: 'Project').select('distinct journalized_id').map{|x| x.journalized_id} + p_ids = [] + Project.where(id: project_ids).each do |x| + p_ids << x.id unless x.visible?(User.current) + end + ids << Journal.where(id: act_ids, journalized_id: p_ids, journalized_type: 'Project').map{|x| x.id} + + #News + act_ids = activity.where(act_type: 'News').select('act_id').map{|x| x.act_id} + project_ids = News.where(id: act_ids).select('distinct project_id').map{|x| x.project_id} + p_ids = [] + Project.where(id: project_ids).each do |x| + p_ids << x.id unless x.visible?(User.current) + end + ids << News.where(id: act_ids, project_id: p_ids).map{|x| x.id} + + project_ids = News.where(id: act_ids).select('distinct course_id').map{|x| x.course_id} + c_ids = [] + Course.where(id: project_ids).each do |x| + c_ids << x.id unless x.is_public !=0 && User.current.member_of_course?(x) + end + ids << News.where(id: act_ids, course_id: p_ids).map{|x| x.id} + + #Message + act_ids = activity.where(act_type: 'Message').select('act_id').map{|x| x.act_id} + board_ids = Message.where(id: act_ids).select('distinct board_id').map{|x| x.board_id} + project_ids = Board.where(id: board_ids).select('distinct project_id').map{|x| x.project_id} + p_ids = [] + Project.where(id: project_ids).each do |x| + p_ids << x.id unless x.visible?(User.current) + end + ids << Message.where(id: act_ids, board_id: p_ids).map{|x| x.id} + + project_ids = Board.where(id: board_ids).select('distinct course_id').map{|x| x.course_id} + c_ids = [] + Course.where(id: project_ids).each do |x| + c_ids << x.id unless x.is_public !=0 && User.current.member_of_course?(x) + end + ids << Message.where(id: act_ids, board_id: c_ids).map{|x| x.id} + + logger.debug "filter ids #{ids}" + + activity = activity.where('act_id not in (?)', ids.flatten ).order('id desc') unless ids.flatten.empty? + end + # activity = activity.reject { |e| # e.act.nil? || # (!User.current.admin? && !e.act.nil? @@ -454,14 +525,11 @@ class UsersController < ApplicationController # (e.act_type == "News" && ((!e.act.project.nil? && !e.act.project.visible?(User.current)) || (!e.act.course.nil? && e.act.course.is_public == 0 && !User.current.member_of_course?(e.act.course)))) || # (e.act_type == "Message" && !e.act.board.nil? && ((!e.act.board.project.nil? && !e.act.board.project.visible?(User.current)) || (!e.act.board.course.nil? && e.act.board.course.is_public == 0 && !User.current.member_of_course?(e.act.board.course)))))) # } + # + @activity_count = activity.count @activity_pages = Paginator.new @activity_count, pre_count, params['page'] @activity = activity.slice(@activity_pages.offset,@activity_pages.per_page) - # @activity = @activity.reject { |e| - # ((e.act_type=="Issue") && ( !e.act.visible?(User.current))) || - # ((e.act_type == "Journal") && (!e.act.project.visible?(User.current))) || - # ((e.act_type == "Bid") && ((!User.current.member_of_course?(e.act.courses.first) || !User.current.admin?))) - # } @state = 0 end @@ -553,7 +621,7 @@ class UsersController < ApplicationController @user.pref.save @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) - Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information] + Mailer.run.account_information(@user, params[:user][:password]) if params[:send_information] respond_to do |format| format.html { @@ -620,9 +688,9 @@ class UsersController < ApplicationController @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) if was_activated - Mailer.account_activated(@user).deliver + Mailer.run.account_activated(@user) elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil? - Mailer.account_information(@user, params[:user][:password]).deliver + Mailer.run.account_information(@user, params[:user][:password]) end respond_to do |format| diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index 57f26103c..defc36868 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -85,13 +85,14 @@ class VersionsController < ApplicationController end def new - @version = @project.versions.build - @version.safe_attributes = params[:version] - - respond_to do |format| - format.html - format.js - end + # @version = @project.versions.build + # @version.safe_attributes = params[:version] + # + # respond_to do |format| + # format.html + # format.js + # end + redirect_to settings_project_url(@project, :tab => 'versions') end def create @@ -116,7 +117,8 @@ class VersionsController < ApplicationController end else respond_to do |format| - format.html { render :action => 'new' } + format.html { flash[:error] = @version.errors.full_messages.flatten.to_s + redirect_to settings_project_url(@project, :tab => 'versions') } format.js { render :action => 'new' } format.api { render_validation_errors(@version) } end @@ -136,7 +138,7 @@ class VersionsController < ApplicationController respond_to do |format| format.html { flash[:notice] = l(:notice_successful_update) - redirect_back_or_default settings_project_path(@project, :tab => 'versions') + redirect_to settings_project_path(@project, :tab => 'versions') } format.api { render_api_ok } end diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb index f35210f01..6e067fb83 100644 --- a/app/controllers/welcome_controller.rb +++ b/app/controllers/welcome_controller.rb @@ -58,12 +58,18 @@ class WelcomeController < ApplicationController else case @first_page.sort_type when 0 + @my_projects = find_my_projects + @other_projects = @my_projects.count < 9 ? find_miracle_project( 9 - @my_projects.count, 3,"score desc") : [] @projects = find_miracle_project(10, 3,"created_on desc") #@projects = @projects_all.order("created_on desc") when 1 + @my_projects = find_my_projects + @other_projects = @my_projects.count < 9 ? find_miracle_project( 9 - @my_projects.count, 3,"score desc") : [] @projects = find_miracle_project(10, 3,"score desc") #@projects = @projects_all.order("grade desc") when 2 + @my_projects = find_my_projects + @other_projects = @my_projects.count < 9 ? find_miracle_project( 9 - @my_projects.count, 3,"score desc") : [] @projects = find_miracle_project(10, 3,"watchers_count desc") #@projects = @projects_all.order("watchers_count desc") diff --git a/app/controllers/words_controller.rb b/app/controllers/words_controller.rb index 2fc4fef9a..3d6772ea8 100644 --- a/app/controllers/words_controller.rb +++ b/app/controllers/words_controller.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #####leave message fq class WordsController < ApplicationController - + include ApplicationHelper before_filter :find_user, :only => [:new, :create, :destroy, :more, :back] def create if params[:new_form][:user_message].size>0 @@ -81,9 +81,10 @@ class WordsController < ApplicationController @journal_destroyed = JournalsForMessage.delete_message(params[:object_id]) if @journal_destroyed.jour_type == "Bid" @bid = Bid.find(@journal_destroyed.jour_id) - end - if @bid @jours_count = @bid.journals_for_messages.where('m_parent_id IS NULL').count + elsif @journal_destroyed.jour_type == "Course" + @course = Course.find @journal_destroyed.jour_id + @jours_count = @course.journals_for_messages.where('m_parent_id IS NULL').count end respond_to do |format| format.js @@ -209,6 +210,10 @@ class WordsController < ApplicationController message = params[:new_form][:course_message] feedback = Course.add_new_jour(user, message, params[:id]) if(feedback.errors.empty?) + if params[:asset_id] + ids = params[:asset_id].split(',') + update_kindeditor_assets_owner ids,feedback[:id],OwnerTypeHelper::JOURNALSFORMESSAGE + end redirect_to course_feedback_url(params[:id]), notice: l(:label_feedback_success) else flash[:error] = feedback.errors.full_messages[0] diff --git a/app/controllers/zipdown_controller.rb b/app/controllers/zipdown_controller.rb index 6c5385115..06d69f72e 100644 --- a/app/controllers/zipdown_controller.rb +++ b/app/controllers/zipdown_controller.rb @@ -5,24 +5,48 @@ class ZipdownController < ApplicationController #检查权限 #勿删 before_filter :authorize, :only => [:assort,:download_user_homework] SAVE_FOLDER = "#{Rails.root}/files" - OUTPUT_FOLDER = "#{Rails.root}/tmp/archiveZip" + OUTPUT_FOLDER = "#{Rails.root}/files/archiveZip" + #统一下载功能 + def download + begin + send_file "#{OUTPUT_FOLDER}/#{params[:file]}", :filename => params[:filename], :type => detect_content_type(params[:file]) + rescue => e + render file: 'public/no_file_found.html' + end + end + #一个作业下所有文件打包下载,只有admin和课程老师有权限 def assort if params[:obj_class] == "Bid" bid = Bid.find params[:obj_id] + render_403 if User.current.allowed_to?(:as_teacher,bid.courses.first) file_count = 0 bid.homeworks.map { |homework| file_count += homework.attachments.count} if file_count > 0 zipfile = zip_bid bid else render file: 'public/no_file_found.html' + return end else logger.error "[ZipDown#assort] ===> #{params[:obj_class]} unKown !!" end - send_file zipfile, :filename => bid.name + ".zip", :type => detect_content_type(zipfile) if zipfile + # if zipfile + # if zipfile.length > 1 + # @mut_down_files = zipfile #zipfile.each{|x| File.basename(x)} + # else + # send_file zipfile.first[:real_file], :filename => bid.name + ".zip", :type => detect_content_type(zipfile.first[:real_file]) + # return + # end + # end + + respond_to do |format| + format.json { + render json: zipfile.to_json + } + end #rescue Exception => e # render file: 'public/no_file_found.html' end @@ -34,9 +58,9 @@ class ZipdownController < ApplicationController if homework != nil unless homework.attachments.empty? zipfile = zip_homework_by_user homework - send_file zipfile, :filename => ((homework.user.user_extensions.nil? || homework.user.user_extensions.student_id.nil?) ? "" : homework.user.user_extensions.student_id) + + send_file zipfile.file_path, :filename => ((homework.user.user_extensions.nil? || homework.user.user_extensions.student_id.nil?) ? "" : homework.user.user_extensions.student_id) + "_" + (homework.user.lastname.nil? ? "" : homework.user.lastname) + (homework.user.firstname.nil? ? "" : homework.user.firstname) + - "_" + homework.name + ".zip", :type => detect_content_type(zipfile) if(zipfile) + "_" + homework.name + ".zip", :type => detect_content_type(zipfile.file_path) if(zipfile) else render file: 'public/no_file_found.html' end @@ -66,68 +90,146 @@ class ZipdownController < ApplicationController def zip_bid(bid) # Todo: User Access Controll bid_homework_path = [] + digests = [] bid.homeworks.each do |homeattach| unless homeattach.attachments.empty? - bid_homework_path << zip_homework_by_user(homeattach) + out_file = zip_homework_by_user(homeattach) + bid_homework_path << out_file.file_path + digests << out_file.file_digest end end - zipping "#{Time.now.to_i}_#{bid.name}.zip", bid_homework_path, OUTPUT_FOLDER + + homework_id = bid.id + user_id = bid.author_id + + + out_file = find_or_pack(homework_id, user_id, digests.sort){ + zipping("#{Time.now.to_i}_#{bid.name}.zip", + bid_homework_path, OUTPUT_FOLDER) + } + + + # zips = split_pack_files(bid_homework_path, Setting.pack_attachment_max_size.to_i*1024) + # x = 0 + # + # + # zips.each { |o| + # x += 1 + # file = zipping "#{Time.now.to_i}_#{bid.name}_#{x}.zip", o[:files], OUTPUT_FOLDER + # o[:real_file] = file + # o[:file] = File.basename(file) + # o[:size] = (File.size(file) / 1024.0 / 1024.0).round(2) + # } + + [{files:[out_file.file_path], count: 1, index: 1, + real_file: out_file.file_path, file: File.basename(out_file.file_path), + size:(out_file.pack_size / 1024.0 / 1024.0).round(2) + }] end - def zip_homework_by_user(homeattach) + def zip_homework_by_user(homework_attach) homeworks_attach_path = [] not_exist_file = [] # 需要将所有homework.attachments遍历加入zip - # 并且返回zip路径 - homeattach.attachments.each do |attach| + + + digests = [] + homework_attach.attachments.each do |attach| if File.exist?(attach.diskfile) homeworks_attach_path << attach.diskfile + digests << attach.digest else not_exist_file << attach.filename + digests << 'not_exist_file' end end - zipping("#{homeattach.user.lastname}#{homeattach.user.firstname}_#{((homeattach.user.user_extensions.nil? || homeattach.user.user_extensions.student_id.nil?) ? "" : homeattach.user.user_extensions.student_id)}_#{Time.now.to_i.to_s}.zip", homeworks_attach_path, OUTPUT_FOLDER, true, not_exist_file) + + out_file = find_or_pack(homework_attach.bid_id, homework_attach.user_id, digests.sort){ + zipping("#{homework_attach.user.lastname}#{homework_attach.user.firstname}_#{((homework_attach.user.user_extensions.nil? || homework_attach.user.user_extensions.student_id.nil?) ? "" : homework_attach.user.user_extensions.student_id)}_#{Time.now.to_i.to_s}.zip", + homeworks_attach_path, OUTPUT_FOLDER, true, not_exist_file) + } + end - def zipping(zip_name_refer, files_paths, output_path, is_attachment=false, not_exist_file=[]) - # 输入待打包的文件列表,已经打包文件定位到ouput_path - ic = Iconv.new('GBK//IGNORE', 'UTF-8//IGNORE') - input_filename = files_paths + def find_or_pack(homework_id, user_id, digests) + raise "please given a pack block" unless block_given? + + out_file = ZipPack.packed?(homework_id, user_id, digests.sort) + + unless out_file && out_file.file_valid? + file = yield + + ZipPack.where(homework_id: homework_id, + user_id: user_id).delete_all + out_file = ZipPack.create(homework_id: homework_id, + user_id: user_id, + file_digest: Trustie::Utils.digest(file), + file_path: file, + pack_size: File.size(file), + file_digests: digests.join(',') + ) + else + out_file.pack_times = out_file.pack_times + 1 + out_file.save + end + + out_file + end + + + def zipping(zip_name_refer, files_paths, output_path, is_attachment=false, not_exist_file=[]) rename_zipfile = zip_name_refer ||= "#{Time.now.to_i.to_s}.zip" zipfile_name = "#{output_path}/#{rename_zipfile}" Dir.mkdir(File.dirname(zipfile_name)) unless File.exist?(File.dirname(zipfile_name)) - Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile| - input_filename.each do |filename| - flag = true - index = 1 - rename_file = ic.iconv( (File.basename(filename)) ).to_s - rename_file = ic.iconv( filename_to_real( File.basename(filename))).to_s if is_attachment + files_paths.each do |filename| + rename_file = File.basename(filename) + rename_file = filename_to_real( File.basename(filename)) if is_attachment begin zipfile.add(rename_file, filename) - flag = false rescue Exception => e - zipfile.get_output_stream('FILE_NOTICE.txt') do |os| - os.write l(:label_file_exist) - end + zipfile.get_output_stream('FILE_NOTICE.txt'){|os| os.write l(:label_file_exist)} next end end unless not_exist_file.empty? - zipfile.get_output_stream('FILE_LOST.txt') do |os| - os.write l(:label_file_lost) + not_exist_file.join(',').to_s - end + zipfile.get_output_stream('FILE_LOST.txt'){|os| os.write l(:label_file_lost) + not_exist_file.join(',').to_s} end end zipfile_name - #rescue Errno => e - # logger.error "[zipdown#zipping] ===> #{e}" - # @error = e end + + # 合理分配文件打包 + # 如果小于 pack_attachment_max_size, 则返回单个文件 + # 反之则切分为多个文件组返回 + def split_pack_files(files, pack_attachment_max_size) + max_size = 0 + last_files = [] + ret_files = [] + files.each_with_index do |f,i| + if (max_size += File.size(f)) > pack_attachment_max_size + max_size = 0 + if last_files.empty? #如果单个文件超过大小,也将此文件作为一组 + ret_files << {files: [f], count: 1, index: ret_files.count+1} + last_files.clear + else + ret_files << {files:last_files, count: last_files.count, index: ret_files.count+1} + last_files.clear + redo + end + else + last_files << f + end + end + + ret_files << {files:last_files, count: last_files.count, index: ret_files.count+1} unless last_files.empty? + ret_files + end + def detect_content_type(name) content_type = Redmine::MimeType.of(name) content_type.to_s @@ -137,4 +239,4 @@ class ZipdownController < ApplicationController attach = Attachment.find_by_disk_filename(name) attach.filename end -end \ No newline at end of file +end diff --git a/app/helpers/account_helper.rb b/app/helpers/account_helper.rb index 8ef2d6095..7ad6fe65b 100644 --- a/app/helpers/account_helper.rb +++ b/app/helpers/account_helper.rb @@ -1,62 +1,62 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module AccountHelper - - def email_activation_register(user, &block) - token = Token.new(:user => user, :action => "register") - if user.save and token.save - UserStatus.create(:user_id => user.id, :changsets_count => 0, :watchers_count => 0) - Mailer.register(token).deliver - #flash[:notice] = l(:notice_account_register_done) - #render action: 'email_valid', locals: {:mail => user.mail} - else - yield if block_given? - end - user - end - - def automatically_register(user, &block) - # Automatic activation - user.activate - user.last_login_on = Time.now - if user.save - UserStatus.create(:user_id => user.id, :changsets_count => 0, :watchers_count => 0) - #self.logged_user = user - #flash[:notice] = l(:notice_account_activated) - #redirect_to my_account_url - else - yield if block_given? - end - user - end - - def administrator_manually__register(user, &block) - if user.save - UserStatus.create(:user_id => user.id ,:changsets_count => 0, :watchers_count => 0) - # Sends an email to the administrators - Mailer.account_activation_request(user).deliver - #account_pending - else - yield if block_given? - end - user - end - -end +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module AccountHelper + + def email_activation_register(user, &block) + token = Token.new(:user => user, :action => "register") + if user.save and token.save + UserStatus.create(:user_id => user.id, :changsets_count => 0, :watchers_count => 0) + Mailer.run.register(token) + #flash[:notice] = l(:notice_account_register_done) + #render action: 'email_valid', locals: {:mail => user.mail} + else + yield if block_given? + end + user + end + + def automatically_register(user, &block) + # Automatic activation + user.activate + user.last_login_on = Time.now + if user.save + UserStatus.create(:user_id => user.id, :changsets_count => 0, :watchers_count => 0) + #self.logged_user = user + #flash[:notice] = l(:notice_account_activated) + #redirect_to my_account_url + else + yield if block_given? + end + user + end + + def administrator_manually__register(user, &block) + if user.save + UserStatus.create(:user_id => user.id ,:changsets_count => 0, :watchers_count => 0) + # Sends an email to the administrators + Mailer.run.account_activation_request(user) + #account_pending + else + yield if block_given? + end + user + end + +end diff --git a/app/helpers/activities_helper.rb b/app/helpers/activities_helper.rb index c15d89e0c..ede2ed78a 100644 --- a/app/helpers/activities_helper.rb +++ b/app/helpers/activities_helper.rb @@ -1,33 +1,45 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module ActivitiesHelper - def sort_activity_events(events) - events_by_group = events.group_by(&:event_group) - sorted_events = [] - events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each do |event| - if group_events = events_by_group.delete(event.event_group) - group_events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each_with_index do |e, i| - sorted_events << [e, i > 0] unless e.event_description.nil? - end - end - end - sorted_events - end -end +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module ActivitiesHelper + def sort_activity_events(events) + events_by_group = events.group_by(&:event_group) + sorted_events = [] + events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each do |event| + if group_events = events_by_group.delete(event.event_group) + group_events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each_with_index do |e, i| + sorted_events << [e, i > 0] unless e.event_description.nil? + end + end + end + sorted_events + end + def sort_activity_events_course(events) + events_by_group = events.group_by(&:event_group) + sorted_events = [] + events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each do |event| + if group_events = events_by_group.delete(event.event_group) + group_events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each_with_index do |e, i| + sorted_events << e unless e.event_description.nil? + end + end + end + sorted_events + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c8168455c..405dc72ed 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -53,6 +53,10 @@ module ApplicationHelper # Author lizanle # Description after save后需要进行资源记录的更新 # owner_type = 1 对应的是 memo + # owner_type = 2 对应的是forum + # owner_type = 3 对应的是message + # owner_type = 4 对应的是news + # owner_type = 5 对应的是comment def update_kindeditor_assets_owner ids,owner_id,owner_type ids.each do |id| asset = Kindeditor::Asset.find(id.to_i) @@ -77,7 +81,7 @@ module ApplicationHelper def authorize_for(controller, action) User.current.allowed_to?({:controller => controller, :action => action}, @project) end - + # add by nwb def authorize_for_course(controller, action) User.current.allowed_to?({:controller => controller, :action => action}, @course) @@ -124,13 +128,32 @@ module ApplicationHelper end end + def link_to_isuue_user(user, options={}) + if user.is_a?(User) + name = h(user.name(options[:format])) + link_to name, {:controller=> 'users', :action => 'show', id: user.id, host: Setting.user_domain}, :class => "pro_info_p" + else + h(user.to_s) + end + end + + def link_to_settings_user(user, options={}) + if user.is_a?(User) + name = h(user.name(options[:format])) + link_to name, {:controller=> 'users', :action => 'show', id: user.id, host: Setting.user_domain}, :class => "w90 c_orange fl" + else + h(user.to_s) + end + end + #重载上面方法,增加样式显示 def link_to_user_header user,canShowRealName=false,options={} if user.is_a?(User) if canShowRealName - name = h(user.realname(options[:format])) + name = user.show_name + name = user.login if name == "" else - name = h(user.name(options[:format])) + name = user.login end link_to name, {:controller=> 'users', :action => 'show', id: user.id, host: Setting.user_domain}, :class => options[:class] else @@ -165,6 +188,28 @@ module ApplicationHelper s end + def link_to_issue_version(issue, options={}) + title = nil + subject = nil + text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}" + if options[:subject] == false + title = truncate(issue.subject, :length => 60) + else + subject = issue.subject + if options[:truncate] + subject = truncate(subject, :length => 60) + end + end + if issue.status_id == 5 + s = link_to text, issue_path(issue), :class => "text_line_s", :title => title + else + s = link_to text, issue_path(issue), :class => "c_blue", :title => title + end + s << h(": #{subject}") if subject + s = h("#{issue.project} - ") + s if options[:project] + s + end + # Generates a link to an attachment. # Options: # * :text - Link text (default to attachment filename) @@ -188,7 +233,7 @@ module ApplicationHelper route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path html_options = options.slice!(:only_path) url = send(route_method, attachment, attachment.filename, options) - url << "?token=#{token}" unless token.nil? + url << "?token=#{token}" unless token.nil? link_to text, url, html_options end @@ -213,18 +258,18 @@ module ApplicationHelper h(text), {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev}, :title => l(:label_revision_id, format_revision(revision)) - ) + ) end # Generates a link to a message def link_to_message(message, options={}, html_options = nil) link_to( - truncate(message.subject, :length => 60), - board_message_path(message.board_id, message.parent_id || message.id, { - :r => (message.parent_id && message.id), - :anchor => (message.parent_id ? "message-#{message.id}" : nil) - }.merge(options)), - html_options + truncate(message.subject, :length => 60), + board_message_path(message.board_id, message.parent_id || message.id, { + :r => (message.parent_id && message.id), + :anchor => (message.parent_id ? "message-#{message.id}" : nil) + }.merge(options)), + html_options ) end @@ -276,8 +321,23 @@ module ApplicationHelper def thumbnail_tag(attachment) link_to image_tag(thumbnail_path(attachment)), - named_attachment_path(attachment, attachment.filename), - :title => attachment.filename + named_attachment_path(attachment, attachment.filename), + :title => attachment.filename + end + + def thumbnail_issue_tag(attachment) + imagesize = attachment.thumbnail(:size => "200*200") + imagepath = named_attachment_path(attachment, attachment.filename) + if imagesize + link_to image_tag(thumbnail_path(attachment), height: '73', width: '100', class: 'issue_attachment_picture'), + imagepath, + :title => attachment.filename + + else + link_to image_tag(imagepath , height: '73', width: '100', class: 'issue_attachment_picture'), + imagepath, + :title => attachment.filename + end end # 图片缩略图链接 @@ -299,15 +359,15 @@ module ApplicationHelper onclick = "$('##{id}').slideToggle(); " onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") onclick << "return false;" - link_to(name, "#", :onclick => onclick,:class => options[:class]) + link_to(name, "javascript:void(0)", :onclick => onclick,:class => options[:class]) end def image_to_function(name, function, html_options = {}) html_options.symbolize_keys! tag(:input, html_options.merge({ - :type => "image", :src => image_path(name), - :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" - })) + :type => "image", :src => image_path(name), + :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" + })) end def format_activity_title(text) @@ -325,9 +385,9 @@ module ApplicationHelper def format_version_name(version) if version.project == @project - h(version) + h(truncate(version.name,:length=>20)) else - h("#{version.project} - #{version}") + h("#{version.project} - #{truncate(version.name,:length=>20)}") end end @@ -341,7 +401,7 @@ module ApplicationHelper # The given collection may be a subset of the whole project tree # (eg. some intermediate nodes are private and can not be seen) #Modified by nie. - def render_project_nested_lists(projects) + def render_project_nested_lists(projects) s = '' if projects.any? ancestors = [] @@ -370,9 +430,9 @@ module ApplicationHelper if project.try(:project_type) == Project::ProjectType_project unless User.current.member_of?(@project) - s << "" - s << watcher_link(@project, User.current)#, ['whiteButton']) - s << "" + s << "" + s << watcher_link(@project, User.current)#, ['whiteButton']) + s << "" end s << (render :partial => 'projects/project', :locals => {:project => project}).to_s else @@ -385,7 +445,7 @@ module ApplicationHelper @project = original_project end s.html_safe - end + end def render_course_nested_lists(courses) s = '' @@ -420,7 +480,7 @@ module ApplicationHelper end - #added by young + #added by young def render_project_nested_lists_new(projects) s = '' if projects.any? @@ -449,7 +509,7 @@ module ApplicationHelper end s.html_safe end - #end + #end def render_page_hierarchy(pages, node=nil, options={}) content = '' if pages[node] @@ -484,14 +544,22 @@ module ApplicationHelper end end + def render_project_settings_tabs(tabs) + if tabs.any? + render :partial => 'common/project_tab', :locals => {:tabs => tabs} + else + content_tag 'p', l(:label_no_data), :class => "nodata" + end + end + # Renders the project quick-jump box def render_project_jump_box return unless User.current.logged? projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq if projects.any? options = - ("" + - '').html_safe + ("" + + '').html_safe options << project_tree_options_for_select(projects, :selected => @project) do |p| { :value => project_path(:id => p, :jump => current_menu_item) } @@ -542,6 +610,17 @@ module ApplicationHelper s.html_safe end + #缺陷追踪者列表复选框生成 + def issue_watcher_check_box_tags_ex name, principals + s = '' + principals.each do |principal| + s << "
#{@message.content.html_safe}#{ check_box_tag name, principal.id, false, :id => nil } #{h link_to principal.userInfo, user_path( principal.id)} \n" + end + s.html_safe + end + + + #扩展的checkbox生成 def principals_check_box_tags_ex(name, principals) s = '' @@ -551,6 +630,15 @@ module ApplicationHelper s.html_safe end + # li标签checkbos扩展 + def principals_check_box_tags_li(name, principals) + s = '' + principals.each do |principal| + s << "#{ check_box_tag name, principal.id, false, :id => nil } #{h link_to principal.userInfo, user_path( principal.id) } \n" + end + s.html_safe + end + #扩展的checkbox生成 def principals_radio_box_tags_ex(name, principals) s = '' @@ -661,24 +749,24 @@ module ApplicationHelper link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => method, :title => l(:label_sort_highest)) + - link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), - url.merge({"#{name}[move_to]" => 'higher'}), - :method => method, :title => l(:label_sort_higher)) + - link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), - url.merge({"#{name}[move_to]" => 'lower'}), - :method => method, :title => l(:label_sort_lower)) + - link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), - url.merge({"#{name}[move_to]" => 'lowest'}), - :method => method, :title => l(:label_sort_lowest)) + link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), + url.merge({"#{name}[move_to]" => 'higher'}), + :method => method, :title => l(:label_sort_higher)) + + link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), + url.merge({"#{name}[move_to]" => 'lower'}), + :method => method, :title => l(:label_sort_lower)) + + link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), + url.merge({"#{name}[move_to]" => 'lowest'}), + :method => method, :title => l(:label_sort_lowest)) end def breadcrumb(*args) elements = args.flatten - elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil + elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'wiki_con_tit"') : nil end def other_formats_links(&block) - concat(''.html_safe + l(:label_export_to)) + concat('
'.html_safe + l(:label_export_to)) yield Redmine::Views::OtherFormatsBuilder.new(self) concat('
'.html_safe) end @@ -753,15 +841,15 @@ module ApplicationHelper def textilizable(*args) options = args.last.is_a?(Hash) ? args.pop : {} case args.size - when 1 - obj = options[:object] - text = args.shift - when 2 - obj = args.shift - attr = args.shift - text = obj.send(attr).to_s - else - raise ArgumentError, 'invalid arguments to textilizable' + when 1 + obj = options[:object] + text = args.shift + when 2 + obj = args.shift + attr = args.shift + text = obj.send(attr).to_s + else + raise ArgumentError, 'invalid arguments to textilizable' end return '' if text.blank? project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) @@ -917,18 +1005,18 @@ module ApplicationHelper # check if page exists wiki_page = link_project.wiki.find_page(page) url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page - "##{anchor}" - else - case options[:wiki_links] - when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') - when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export - else - wiki_page_id = page.present? ? Wiki.titleize(page) : nil - parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil - url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, - :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent) - end - end + "##{anchor}" + else + case options[:wiki_links] + when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') + when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export + else + wiki_page_id = page.present? ? Wiki.titleize(page) : nil + parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil + url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, + :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent) + end + end link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) else # project or wiki doesn't exist @@ -1003,110 +1091,110 @@ module ApplicationHelper # project.changesets.visible raises an SQL error because of a double join on repositories if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier)) link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision}, - :class => 'changeset', - :title => truncate_single_line(changeset.comments, :length => 100)) + :class => 'changeset', + :title => truncate_single_line(changeset.comments, :length => 100)) end end elsif sep == '#' oid = identifier.to_i case prefix - when nil - if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) - anchor = comment_id ? "note-#{comment_id}" : nil - link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, - :class => issue.css_classes, - :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") - end - when 'document' - if document = Document.visible.find_by_id(oid) - link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, - :class => 'document' - end - when 'version' - if version = Version.visible.find_by_id(oid) - link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, - :class => 'version' - end - when 'message' - if message = Message.visible.find_by_id(oid, :include => :parent) - link = link_to_message(message, {:only_path => only_path}, :class => 'message') - end - when 'forum' - if board = Board.visible.find_by_id(oid) - link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, - :class => 'board' - end - when 'news' - if news = News.visible.find_by_id(oid) - link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, - :class => 'news' - end - when 'project' - if p = Project.visible.find_by_id(oid) - link = link_to_project(p, {:only_path => only_path}, :class => 'project') - end + when nil + if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) + anchor = comment_id ? "note-#{comment_id}" : nil + link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, + :class => issue.css_classes, + :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") + end + when 'document' + if document = Document.visible.find_by_id(oid) + link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, + :class => 'document' + end + when 'version' + if version = Version.visible.find_by_id(oid) + link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, + :class => 'version' + end + when 'message' + if message = Message.visible.find_by_id(oid, :include => :parent) + link = link_to_message(message, {:only_path => only_path}, :class => 'message') + end + when 'forum' + if board = Board.visible.find_by_id(oid) + link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, + :class => 'board' + end + when 'news' + if news = News.visible.find_by_id(oid) + link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, + :class => 'news' + end + when 'project' + if p = Project.visible.find_by_id(oid) + link = link_to_project(p, {:only_path => only_path}, :class => 'project') + end end elsif sep == ':' # removes the double quotes if any name = identifier.gsub(%r{^"(.*)"$}, "\\1") case prefix - when 'document' - if project && document = project.documents.visible.find_by_title(name) - link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, - :class => 'document' - end - when 'version' - if project && version = project.versions.visible.find_by_name(name) - link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, - :class => 'version' - end - when 'forum' - if project && board = project.boards.visible.find_by_name(name) - link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, - :class => 'board' - end - when 'news' - if project && news = project.news.visible.find_by_title(name) - link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, - :class => 'news' - end - when 'commit', 'source', 'export' - if project - repository = nil - if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$} - repo_prefix, repo_identifier, name = $1, $2, $3 - repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} - else - repository = project.repository + when 'document' + if project && document = project.documents.visible.find_by_title(name) + link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, + :class => 'document' + end + when 'version' + if project && version = project.versions.visible.find_by_name(name) + link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, + :class => 'version' end - if prefix == 'commit' - if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first) - link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier}, - :class => 'changeset', - :title => truncate_single_line(changeset.comments, :length => 100) + when 'forum' + if project && board = project.boards.visible.find_by_name(name) + link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, + :class => 'board' + end + when 'news' + if project && news = project.news.visible.find_by_title(name) + link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, + :class => 'news' + end + when 'commit', 'source', 'export' + if project + repository = nil + if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$} + repo_prefix, repo_identifier, name = $1, $2, $3 + repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} + else + repository = project.repository end - else - if repository && User.current.allowed_to?(:browse_repository, project) - name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$} - path, rev, anchor = $1, $3, $5 - link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param, - :path => to_path_param(path), - :rev => rev, - :anchor => anchor}, - :class => (prefix == 'export' ? 'source download' : 'source') + if prefix == 'commit' + if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first) + link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier}, + :class => 'changeset', + :title => truncate_single_line(changeset.comments, :length => 100) + end + else + if repository && User.current.allowed_to?(:browse_repository, project) + name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$} + path, rev, anchor = $1, $3, $5 + link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param, + :path => to_path_param(path), + :rev => rev, + :anchor => anchor}, + :class => (prefix == 'export' ? 'source download' : 'source') + end end + repo_prefix = nil + end + when 'attachment' + attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) + if attachments && attachment = Attachment.latest_attach(attachments, name) + link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment') + end + when 'project' + if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first + link = link_to_project(p, {:only_path => only_path}, :class => 'project') end - repo_prefix = nil - end - when 'attachment' - attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) - if attachments && attachment = Attachment.latest_attach(attachments, name) - link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment') - end - when 'project' - if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first - link = link_to_project(p, {:only_path => only_path}, :class => 'project') - end end end end @@ -1123,9 +1211,9 @@ module ApplicationHelper @current_section += 1 if @current_section > 1 content_tag('div', - link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), - :class => 'contextual', - :title => l(:button_edit_section)) + heading.html_safe + link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), + :class => 'contextual', + :title => l(:button_edit_section)) + heading.html_safe else heading end @@ -1244,10 +1332,10 @@ module ApplicationHelper # Same as Rails' simple_format helper without using paragraphs def simple_format_without_paragraph(text) text.to_s. - gsub(/\r\n?/, "\n"). # \r\n and \r -> \n - gsub(/\n\n+/, "
"). # 2+ newline -> 2 br - gsub(/([^\n]\n)(?=[^\n])/, '\1
'). # 1 newline -> br - html_safe + gsub(/\r\n?/, "\n"). # \r\n and \r -> \n + gsub(/\n\n+/, "
"). # 2+ newline -> 2 br + gsub(/([^\n]\n)(?=[^\n])/, '\1
'). # 1 newline -> br + html_safe end def wiki_simple_format_without_paragraph(text) @@ -1262,7 +1350,7 @@ module ApplicationHelper end def lang_options_for_select(blank=true) - { 'Chinese简体中文 '=> 'zh', :English => :en} + { 'Chinese简体中文 '=> 'zh', :English => :en} end def label_tag_for(name, option_tags = nil, options = {}) @@ -1329,9 +1417,31 @@ module ApplicationHelper def delete_link(url, options={}) options = { - :method => :delete, - :data => {:confirm => l(:text_are_you_sure)}, - :class => 'icon icon-del' + :method => :delete, + :data => {:confirm => l(:text_are_you_sure)}, + :class => 'icon icon-del' + }.merge(options) + + link_to l(:button_delete), url, options + end + + def delete_link_version(url, options={}) + options = { + :method => :delete, + :data => {:confirm => l(:text_are_you_sure)}, + :class => 'c_purple' + }.merge(options) + + link_to l(:button_delete), url, options + end + + + + def delete_new_link(url, options={}) + options = { + :method => :delete, + :data => {:confirm => l(:text_are_you_sure)}, + :class => "c_purple" }.merge(options) link_to l(:button_delete), url, options @@ -1342,11 +1452,11 @@ module ApplicationHelper :href => "#", :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|, :accesskey => accesskey(:preview) - }.merge(options) + }.merge(options) end def link_to_function(name, function, html_options={}) - content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options)) + content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(:class => " c_purple")) end # Helper to render JSON in views @@ -1368,9 +1478,8 @@ module ApplicationHelper end def check_all_links(form_name) - link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + - " | ".html_safe + - link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") + link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + " ".html_safe + " | "+ " ".html_safe + + link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") end def progress_bar(pcts, options={}) @@ -1381,12 +1490,12 @@ module ApplicationHelper width = options[:width] || '100px;' legend = options[:legend] || '' content_tag('table', - content_tag('tr', - (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + - (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) + - (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe) - ), :class => 'progress', :style => "width: #{width};").html_safe + - content_tag('p', legend, :class => 'percent').html_safe + content_tag('tr', + (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + + (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) + + (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe) + ), :class => 'progress', :style => "width: #{width};").html_safe + + content_tag('p', legend, :class => 'percent').html_safe end def checked_image(checked=true) @@ -1399,7 +1508,7 @@ module ApplicationHelper unless @context_menu_included content_for :header_tags do javascript_include_tag('context_menu') + - stylesheet_link_tag('context_menu') + stylesheet_link_tag('context_menu') end if l(:direction) == 'rtl' content_for :header_tags do @@ -1430,7 +1539,7 @@ module ApplicationHelper tags = javascript_tag( "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + "showOn: 'button', buttonImageOnly: true, buttonImage: '" + - path_to_image('/images/calendar.png') + + path_to_image('/images/public_icon.png') + "', showButtonPanel: true, showWeek: true, showOtherMonths: true, selectOtherMonths: true};") jquery_locale = l('jquery.locale', :default => current_language.to_s) unless jquery_locale == 'en' @@ -1452,7 +1561,7 @@ module ApplicationHelper tags = javascript_tag( "var datepickerOptions={dateFormat: 'yy-mm-dd',minDate: new Date(), firstDay: #{start_of_week}, " + "showOn: 'button', buttonImageOnly: true, buttonImage: '" + - path_to_image('/images/calendar.png') + + path_to_image('/images/public_icon.png') + "', showButtonPanel: true, showWeek: true, showOtherMonths: true, selectOtherMonths: true, onClose: function(dateText, inst) {TimeClose(dateText,inst);}, beforeShow : function(input){TimeBeforeShow(input);} };") jquery_locale = l('jquery.locale', :default => current_language.to_s) unless jquery_locale == 'en' @@ -1658,7 +1767,7 @@ module ApplicationHelper end s end - + def get_memo @new_memo = Memo.new #@new_memo.subject = "有什么想说的,尽管来咆哮吧~~" @@ -1676,6 +1785,53 @@ module ApplicationHelper courses_doing end + + def attachment_candown attachment + candown = false + if attachment.container + if attachment.container.class.to_s != "HomeworkAttach" && (attachment.container.has_attribute?(:project) || attachment.container.has_attribute?(:project_id)) && attachment.container.project + project = attachment.container.project + candown= User.current.member_of?(project) || (project.is_public && attachment.is_public == 1) + elsif attachment.container.is_a?(Project) + project = attachment.container + candown= User.current.member_of?(project) || (project.is_public && attachment.is_public == 1) + elsif (attachment.container.has_attribute?(:board) || attachment.container.has_attribute?(:board_id)) && attachment.container.board && + attachment.container.board.project + project = attachment.container.board.project + candown = User.current.member_of?(project) || (project.is_public && attachment.is_public == 1) + elsif (attachment.container.has_attribute?(:course) ||attachment.container.has_attribute?(:course_id) ) && attachment.container.course + course = attachment.container.course + candown = User.current.member_of_course?(course) || (course.is_public==1 && attachment.is_public == 1) + elsif attachment.container.is_a?(Course) + course = attachment.container + candown= User.current.member_of_course?(course) || (course.is_public==1 && attachment.is_public == 1) + elsif (attachment.container.has_attribute?(:board) || attachment.container.has_attribute?(:board_id)) && attachment.container.board && + attachment.container.board.course + course = attachment.container.board.course + candown= User.current.member_of_course?(course) || (course.is_public==1 && attachment.is_public == 1) + elsif attachment.container.class.to_s=="HomeworkAttach" && attachment.container.bid.reward_type == 3 + candown = true + elsif attachment.container_type == "Bid" && attachment.container && attachment.container.courses + course = attachment.container.courses.first + candown = User.current.member_of_course?(attachment.container.courses.first) || (course.is_public == 1 && attachment.is_public == 1) + else + candown = (attachment.is_public == 1 || attachment.is_public == true) + end + end + candown + end + + def project_type_link(text, value) + if value == 1 + link_to "#{text}".html_safe,"javascript:void(0)" ,:onClick => "show_window();", :class => "pr_join_a",:id => "setting_project_type" + elsif value == 2 + link_to "#{text}".html_safe,"javascript:void(0)" ,:onClick => "show_window();", :class => "pr_join_a",:id => "setting_project_type" + else + link_to "#{text}".html_safe,"javascript:void(0)" ,:onClick => "show_window();", :class => "pr_join_a",:id => "setting_project_type" + end + + end + private def wiki_helper @@ -1705,11 +1861,11 @@ module ApplicationHelper html << (content_tag "span", l(:label_no_current_watchers)) end for user in User.watched_by(obj.id) - html << (link_to image_tag(url_to_avatar(user), :class => "avatar"), user_path(user), :class => "avatar", :title => "#{user.name}") - count = count + 1 - if count >= 12 - break - end + html << (link_to image_tag(url_to_avatar(user), :class => "avatar"), user_path(user), :class => "avatar", :title => "#{user.name}") + count = count + 1 + if count >= 12 + break + end end html.html_safe end @@ -1735,13 +1891,13 @@ module ApplicationHelper html.html_safe end - def show_bid_fans_picture(obj) + def show_bid_fans_picture(obj) html = '' if obj.watcher_users.count == 0 html << (content_tag "span", l(:label_project_no_follow)) else obj.watcher_users.take(12).each do |user| - html << (link_to image_tag(url_to_avatar(user), :class => "avatar"), user_path(user), :class => "avatar", :title => user.name) + html << (link_to image_tag(url_to_avatar(user), :class => "avatar"), user_path(user), :class => "avatar", :title => user.name) end end html.html_safe @@ -1776,7 +1932,7 @@ module ApplicationHelper html.html_safe end - def show_contest_project(contest) + def show_contest_project(contest) html = '' if contest.projects.where('is_public = 1').count == 0 html << (content_tag "p", l(:label_no_bid_project), :class => "font_lighter") @@ -1788,7 +1944,7 @@ module ApplicationHelper html.html_safe end - def show_contest_softapplication(contest) + def show_contest_softapplication(contest) html = '' if contest.softapplications.where('is_public = 1').count == 0 html << (content_tag "p", l(:label_no_contest_softapplication), :class => "font_lighter") @@ -1800,17 +1956,17 @@ module ApplicationHelper html.html_safe end - def show_contest_fans_picture(obj) + def show_contest_fans_picture(obj) html = '' if obj.watcher_users.count == 0 html << (content_tag "span", l(:label_project_no_follow)) else obj.watcher_users.take(12).each do |user| - html << (link_to image_tag(url_to_avatar(user), :class => "avatar"), user_path(user), :class => "avatar", :title => user.name) + html << (link_to image_tag(url_to_avatar(user), :class => "avatar"), user_path(user), :class => "avatar", :title => user.name) end end html.html_safe - end + end #display fans picture def show_more_fans?(obj) @@ -1827,13 +1983,13 @@ module ApplicationHelper html << (content_tag "span", l(:label_no_current_fans)) else obj.watcher_users.take(12).each do |user| - html << (link_to image_tag(url_to_avatar(user), :class => "avatar"), user_path(user), :class => "avatar", :title => user.name) + html << (link_to image_tag(url_to_avatar(user), :class => "avatar"), user_path(user), :class => "avatar", :title => user.name) end end html.html_safe end - # added by bai + # added by bai def show_more_participate?(obj) if obj.join_in_contests.count > 12 return true @@ -1842,18 +1998,18 @@ module ApplicationHelper end end - def show_participate_picture(obj) + def show_participate_picture(obj) html = '' count = 0 if obj.join_in_contests.count == 0 html << (content_tag "span", l(:label_no_current_participate)) end for temp in obj.join_in_contests - html << (link_to image_tag(url_to_avatar(temp.user), :class => "avatar"), user_path(temp.user), :class => "avatar", :title => "#{temp.user.name}") - count = count + 1 - if count >= 12 - break - end + html << (link_to image_tag(url_to_avatar(temp.user), :class => "avatar"), user_path(temp.user), :class => "avatar", :title => "#{temp.user.name}") + count = count + 1 + if count >= 12 + break + end end html.html_safe end @@ -1862,14 +2018,14 @@ module ApplicationHelper # add by huang def show_watcher_list(user) - html = '' - count = 0 + html = '' + count = 0 for user in User.watched_by(user.id) - html << (link_to image_tag(url_to_avatar(user), :class => "avatar"), user_path(user), :class => "avatar", :title => "#{user.name}") - count = count + 1 - if count >= 12 - break - end + html << (link_to image_tag(url_to_avatar(user), :class => "avatar"), user_path(user), :class => "avatar", :title => "#{user.name}") + count = count + 1 + if count >= 12 + break + end end html.html_safe end @@ -1887,14 +2043,14 @@ module ApplicationHelper return true if bid.nil? case bid.homework_type - when Bid::HomeworkFile - attaches = HomeworkAttach.where(bid_id: curb) - attaches.map(&:user_id).include? cur - when Bid::HomeworkProject - attaches = BidingProject.where(user_id: User.current, bid_id: bid) - attaches.count > 0 # > 0 则有提交记录 - else - true + when Bid::HomeworkFile + attaches = HomeworkAttach.where(bid_id: curb) + attaches.map(&:user_id).include? cur + when Bid::HomeworkProject + attaches = BidingProject.where(user_id: User.current, bid_id: bid) + attaches.count > 0 # > 0 则有提交记录 + else + true end end @@ -1907,6 +2063,8 @@ module ApplicationHelper end def bootstrap_render_dynamic_nav + hidden_non_project = Setting.find_by_name("hidden_non_project") + visiable = !(hidden_non_project && hidden_non_project.value == "0") main_course_link = link_to l(:label_course_practice), {:controller => 'welcome', :action => 'index', :host => Setting.course_domain} main_project_link = link_to l(:label_project_deposit), {:controller => 'welcome', :action => 'index', :host => Setting.project_domain} @@ -1921,24 +2079,28 @@ module ApplicationHelper forum_link = link_to l(:label_forum_all), {:controller => "forums", :action => "index"} stores_link = link_to l(:label_stores_index), {:controller => 'stores', :action=> 'index'} school_all_school_link = link_to l(:label_school_all), {:controller => 'school', :action => 'index'} + project_new_link = link_to l(:label_project_new), {:controller => 'projects', :action => 'new', :host => Setting.project_domain} + # project_mine_link = link_to l(:label_my_project), {:controller => 'users', :action => 'user_projects', :host => Setting.project_domain} #@nav_dispaly_project_label nav_list = Array.new - nav_list.push(school_all_school_link) if @nav_dispaly_course_all_label && @show_course == 1 + nav_list.push(school_all_school_link) if @nav_dispaly_course_all_label && @show_course == 1 && visiable # nav_list.push(course_all_course_link) if @nav_dispaly_course_all_label && @show_course == 1 - nav_list.push(course_teacher_all_link) if @nav_dispaly_teacher_all_label && @show_course == 1 + nav_list.push(course_teacher_all_link) if @nav_dispaly_teacher_all_label && @show_course == 1 && visiable nav_list.push(main_project_link) if @nav_dispaly_main_project_label - nav_list.push(main_course_link) if @nav_dispaly_main_course_label && @show_course == 1 - nav_list.push(main_contest_link) if @nav_dispaly_main_contest_label && @show_contest == 1 + nav_list.push(main_course_link) if @nav_dispaly_main_course_label && @show_course == 1 && visiable + nav_list.push(main_contest_link) if @nav_dispaly_main_contest_label && @show_contest == 1 && visiable - nav_list.push(courses_link) if @nav_dispaly_course_label && @show_course == 1 + nav_list.push(courses_link) if @nav_dispaly_course_label && @show_course == 1 && visiable + nav_list.push(project_new_link) if @nav_dispaly_project_label + # nav_list.push(project_mine_link) if @nav_dispaly_main_project_label # nav_list.push(projects_link) if @nav_dispaly_project_label #nav_list.push(users_link) if @nav_dispaly_user_label # nav_list.push(contest_link) if @nav_dispaly_contest_label && @show_contest == 1 - nav_list.push(bids_link) if @nav_dispaly_bid_label - nav_list.push(forum_link) if @nav_dispaly_forum_label - nav_list.push(stores_link) if @nav_dispaly_store_all_label + nav_list.push(bids_link) if @nav_dispaly_bid_label && visiable + nav_list.push(forum_link) if @nav_dispaly_forum_label && visiable + nav_list.push(stores_link) if @nav_dispaly_store_all_label && visiable content_li = '' nav_list.collect do |nav_item| @@ -1952,12 +2114,12 @@ module ApplicationHelper end # def hadcommittedforcontest(curu) - # message = JournalsForMessage.find_by_sql("select * from journals_for_messages where jour_type = 'Softapplication' ") - # message.each do |createmessage| - # if createmessage.user_id == curu - # return true - # end - # end + # message = JournalsForMessage.find_by_sql("select * from journals_for_messages where jour_type = 'Softapplication' ") + # message.each do |createmessage| + # if createmessage.user_id == curu + # return true + # end + # end # end def footer_logo(ul_class=nil, li_class=nil) @@ -1977,28 +2139,28 @@ module ApplicationHelper def sort_homework_path(bid, sort, direction) case self.action_name - when 'show_courseEx' - get_not_batch_homework_homework_attach_index_path(bid_id: bid.id, sort: sort, direction: 'asc') - when 'get_not_batch_homework' - get_not_batch_homework_homework_attach_index_path(bid_id: bid.id, sort: sort, direction: direction) - when 'get_batch_homeworks' - get_batch_homeworks_homework_attach_index_path(bid_id: bid.id, sort: sort, direction: direction) - when 'get_homeworks' - get_homeworks_homework_attach_index_path(bid_id: bid.id, sort: sort, direction: direction) - else - '#' + when 'show_courseEx' + get_not_batch_homework_homework_attach_index_path(bid_id: bid.id, sort: sort, direction: 'asc') + when 'get_not_batch_homework' + get_not_batch_homework_homework_attach_index_path(bid_id: bid.id, sort: sort, direction: direction) + when 'get_batch_homeworks' + get_batch_homeworks_homework_attach_index_path(bid_id: bid.id, sort: sort, direction: direction) + when 'get_homeworks' + get_homeworks_homework_attach_index_path(bid_id: bid.id, sort: sort, direction: direction) + else + '#' end end def anonymous_comment_link(bid, course) link = case bid.comment_status when 0 - confirm_info = "开启匿评后学生将不能对作业进行提交、修改、删除等操作\n" + confirm_info = "开启匿评后学生将不能对作品进行提交、修改、删除等操作\n" confirm_info += anonymous_comment_notice(bid,course) confirm_info += '是否确定开启匿评?' link_to '启动匿评', start_anonymous_comment_bid_path(bid), id: "#{bid.id}_start_anonymous_comment", remote: true, :confirm => confirm_info, disable_with: '加载中...' when 1 - confirm_info = "关闭匿评后所有同学将不能继续进行匿评,且将公开已提交作业列表\n" + confirm_info = "关闭匿评后所有同学将不能继续进行匿评,且将公开已提交作品列表\n" confirm_info += anonymous_comment_notice(bid,course) confirm_info += '是否确定关闭匿评?' link_to '关闭匿评', stop_anonymous_comment_bid_path(bid), id: "#{bid.id}_stop_anonymous_comment", remote: true, :confirm => confirm_info @@ -2014,7 +2176,7 @@ module ApplicationHelper @student_size ||= searchStudent(course).size @homework_size = bid.homeworks.size percent = @homework_size.to_f / (@student_size == 0 ? 1 : @student_size) - confirm_info = "目前#{@student_size}个学生,总共提交了#{@homework_size}份作业,占#{number_to_percentage(percent * 100, precision: 1)}\n" + confirm_info = "目前#{@student_size}个学生,总共提交了#{@homework_size}份作品,占#{number_to_percentage(percent * 100, precision: 1)}\n" when 1 @homework_evaluations = 0 bid.homeworks.map { |homework| @homework_evaluations += homework.homework_evaluations.count} @@ -2031,7 +2193,7 @@ module ApplicationHelper bid.homeworks.map { |homework| @has_evaluations += homework.rates(:quality).where("seems_rateable_rates.rater_id not in #{teachers}").count} percent = @has_evaluations.to_f / (@homework_evaluations == 0 ? 1 : @homework_evaluations) - confirm_info = "目前总共分配了#{@homework_evaluations}份匿评作业,已评价#{@has_evaluations}份作业,占#{number_to_percentage(percent * 100, precision: 1)}\n" + confirm_info = "目前总共分配了#{@homework_evaluations}份匿评作品,已评价#{@has_evaluations}份作品,占#{number_to_percentage(percent * 100, precision: 1)}\n" end confirm_info end @@ -2054,4 +2216,23 @@ module ApplicationHelper request.env["HTTP_USER_AGENT"] =~ /MSIE 8.0/ end + + #获取指定资源列表的TAG的集合以及每个TAG的数量,降序排序 + def attachment_tag_list attachments + tag_list = Hash.new + attachments.each do |attachment| + attachment.tag_list.map{|tag| tag_list.has_key?(tag) ? tag_list[tag] = tag_list[tag] + 1 : tag_list[tag] = 1} + end + tag_list.sort {|a,b| b[1]<=>a[1]} + end + + #获取课程资源的TAG云 + def get_course_tag_list course + all_attachments = course.attachments.select{|attachment| attachment.is_public? || + (attachment.container_type == "Course" && User.current.member_of_course?(course))|| + attachment.author_id == User.current.id + } + tag_list = attachment_tag_list all_attachments + tag_list + end end diff --git a/app/helpers/attachments_helper.rb b/app/helpers/attachments_helper.rb index 2e07421f8..be24629af 100644 --- a/app/helpers/attachments_helper.rb +++ b/app/helpers/attachments_helper.rb @@ -35,6 +35,16 @@ module AttachmentsHelper end end + def link_to_attachment_project(container, options = {}) + options.assert_valid_keys(:author, :thumbnails) + + if container.attachments.any? + options = {:deletable => container.attachments_deletable?, :author => true}.merge(options) + render :partial => 'attachments/project_file_links', + :locals => {:attachments => container.attachments, :options => options, :thumbnails => (options[:thumbnails] && Setting.thumbnails_enabled?)} + end + end + def link_to_attachments_course(container, options = {}) options.assert_valid_keys(:author, :thumbnails) @@ -77,6 +87,7 @@ module AttachmentsHelper end end + #判断课程course中是否包含课件attachment,course中引用了attachment也算作包含 def course_contains_attachment? course,attachment course.attachments.each do |att| if att.id == attachment.id || (!att.copy_from.nil? && !attachment.copy_from.nil? && att.copy_from == attachment.copy_from) || att.copy_from == attachment.id || att.id == attachment.copy_from @@ -85,6 +96,15 @@ module AttachmentsHelper end false end + #判断项目project中是否包含课件attachment,project中引用了attachment也算作包含 + def project_contains_attachment? project,attachment + project.attachments.each do |att| + if att.id == attachment.id || (!att.copy_from.nil? && !attachment.copy_from.nil? && att.copy_from == attachment.copy_from) || att.copy_from == attachment.id || att.id == attachment.copy_from + return true + end + end + false + end def get_qute_number attachment if attachment.copy_from diff --git a/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb index 384fcaa9b..801d98b0b 100644 --- a/app/helpers/courses_helper.rb +++ b/app/helpers/courses_helper.rb @@ -20,7 +20,7 @@ module CoursesHelper # 返回教师数量,即roles表中定义的Manager def teacherCount project - project.members.count - studentCount(project).to_i + project ? project.members.count - studentCount(project).to_i : 0 # or # searchTeacherAndAssistant(project).count end @@ -52,7 +52,7 @@ module CoursesHelper end def course_poll_count - Poll.where("polls_type = 'Course' and polls_group_id = #{@course.id}").count + Poll.where("polls_type = 'Course' and polls_group_id = #{@course.id} and polls_status in (2,3)").count end def course_feedback_count @@ -152,7 +152,7 @@ module CoursesHelper # 学生人数计算 # add by nwb def studentCount course - course.student.count.to_s#course.student.count + course ? course.student.count.to_s : 0#course.student.count end #课程成员数计算 @@ -233,10 +233,10 @@ module CoursesHelper #searchPeopleByRoles(project, StudentRoles) mems = [] if name != "" + name = name.to_s.downcase members.each do |m| - - username = m.user[:lastname].to_s + m.user[:firstname].to_s - if(m.user[:login].to_s.include?(name) || m.user.user_extensions[:student_id].to_s.include?(name) || username.include?(name)) + username = m.user[:lastname].to_s.downcase + m.user[:firstname].to_s.downcase + if(m.user[:login].to_s.downcase.include?(name) || m.user.user_extensions[:student_id].to_s.downcase.include?(name) || username.include?(name)) mems << m end end @@ -632,27 +632,73 @@ module CoursesHelper #获取课程动态 def get_course_activity courses, activities @course_ids=activities.keys() - days = Setting.activity_days_default.to_i - date_to ||= Date.today + 1 - date_from = date_to - days-1.years + #原来课程动态计算当期时间前(一年+一月)的动态 + # date_to ||= Date.today + 1 + # #date_from = date_to - days-1.years + date_from = @course.created_at.to_date-days #file_count Attachment.where(container_id: @course_ids, container_type: Course).where("created_on>?", date_from).each do |attachment| + if attachment.is_public? || User.current.member_of_course?(@course) || User.current.admin? activities[attachment.container_id]+=1 + else + activities[attachment.container_id] + end end + #message_count + #Board.where(course_id: @course_ids).each do |board| + # activities[board.course_id]+=board.messages.where("updated_on>?", date_from).count + #end + #message_count Board.where(course_id: @course_ids).each do |board| -# activities[board.course_id]+=1 - activities[board.course_id]+=board.messages.where("updated_on>?", date_from).count + countmessage = 0 + # 课程人员退出课程后,之前在讨论区回帖不计入课程动态统计 + board.messages.where("updated_on>?", date_from).each do |message| + if message.author.member_of_course?(@course) + countmessage+=1 + end + end + activities[board.course_id]+=countmessage end #news News.where(course_id: @course_ids).where("created_on>?",date_from).each do |news| + if news.author.member_of_course?(@course) activities[news.course_id]+=1 + end + end + + #feedback_count 留言目前有问题留待下一步处理 + #JournalsForMessage.where(jour_id: @course_ids, jour_type: Course).each do |jourformess| + # activities[jourformess.jour_id]+=1 + #end + + #homework_count + HomeworkForCourse.where(course_id: @course_ids).each do |homework| + countbid=0 + # @bid_ids<?",date_from).each do |bid| + countbid+=1 + end + activities[homework.course_id]+=countbid end + #@bid_ids.each do |bid_id| + # activities[] +=Bid.where(id: bid_id ).where("created_on>?",date_from).count + #end + + #poll_count + # 动态目前只统计发布的问卷,关闭的问卷不在动态内显示 + # Poll.where(polls_group_id: @course_ids, polls_type: Course, polls_status: 2||3).where("published_at>?",date_from).each do |poll| + Poll.where(polls_group_id: @course_ids, polls_type: Course, polls_status: 2||3).where("published_at>?",date_from).each do |poll| + activities[poll.polls_group_id]+=1 + end + #end + + # 动态数 + 1 ( 某某创建了该课程 ) activities.each_pair { |key, value| activities[key] = value + 1 } return activities @@ -677,7 +723,7 @@ module CoursesHelper #加入课程、退出课程按钮 def join_in_course_header(course, user, options=[]) if user.logged? - joined = user.member_of_course? course + joined = course.members.map{|member| member.user_id}.include? user.id text = joined ? ("".html_safe + l(:label_course_exit_student)) : ("".html_safe + l(:label_course_join_student)) url = joined ? join_path(:object_id => course.id) : try_join_path(:object_id => course.id) method = joined ? 'delete' : 'post' @@ -718,7 +764,11 @@ module CoursesHelper if user_homework && user_homework.empty? link_to l(:label_commit_homework), new_exercise_book_path(bid),:class => 'fr mr10 work_edit' else - "作业已交".html_safe + if bid.comment_status == 1 && bid.open_anonymous_evaluation == 1 + "#{l(:label_edit_homework)}".html_safe + else + link_to l(:label_edit_homework), edit_homework_attach_path(user_homework.first.id),:class => 'fr mr10 work_edit' + end end end @@ -736,4 +786,15 @@ module CoursesHelper "未启用匿评".html_safe end end + + def visable_attachemnts_incourse course + return[] unless course + result = [] + course.attachments.each do |attachment| + if attachment.is_public? || User.current.member_of_course?(course) || User.current.admin? + result << attachment + end + end + result + end end diff --git a/app/helpers/files_helper.rb b/app/helpers/files_helper.rb index 884ebc2eb..9a1765ddc 100644 --- a/app/helpers/files_helper.rb +++ b/app/helpers/files_helper.rb @@ -1,157 +1,150 @@ -# encoding: utf-8 -module FilesHelper - include AttachmentsHelper - - def downloadAll containers - paths = [] - files = [] - tmpfile = "tmp.zip" - - containers.each do |container| - next if container.attachments.empty? - if container.is_a?(Version);end - container.attachments.each do |attachment| - paths << attachment.diskfile - file = attachment.diskfile - # logger.error "[FilesHelper] downloadAll: #{e}" - begin - File.new(file, "r") - rescue Exception => e - logger.error e - next - end - files << file - # zip.add(file.path.dup.sub(directory, ''), file.path) - end - end - - zipfile_name = "archive.zip" - if File.exists? File.open(zipfile_name, "w+") - ff = File.open(zipfile_name, "w+") - ff.close - File.delete ff - end - Zip::ZipFile.open(zipfile_name, Zip::ZipFile::CREATE) do |zipfile| - files.each do |filename| - directory = File.dirname filename - # Two arguments: - # - The name of the file as it will appear in the archive - # - The original file, including the path to find it - dir = filename.sub(directory+"/", '') - zipfile.add(dir, filename) - - end - end - File.new(zipfile_name,'w+') - end - - def courses_check_box_tags(name,courses,current_course,attachment) - s = '' - courses.each do |course| - if !course_contains_attachment?(course,attachment) && is_course_teacher(User.current,course) && course_in_current_or_next_term(course) - s << " [#{get_course_term course}]
" - end - end - s.html_safe - end - - #判断用户是否拥有不包含当前资源的课程,需用户在该课程中角色为教师且该课程属于当前学期或下一学期 - def has_course? user,file - result = false - user.courses.each do |course| - if !course_contains_attachment?(course,file) && is_course_teacher(User.current,course) && course_in_current_or_next_term(course) - return true - end - end - result - end - - # 判断指定的资源时候符合类型 - def isTypeOk(attachment, type, contentType) - result = false - if type != 0 - if attachment.attachtype == type - result = true - end - else - result = true - end - if result - if contentType != '0' && contentType != attachment.suffix_type - result = false - end - end - result - end - - def visable_attachemnts attachments - result = [] - attachments.each do |attachment| - if attachment.is_public? || - (attachment.container_type == "Project" && User.current.member_of?(attachment.project)) || - (attachment.container_type == "Course" && User.current.member_of_course?(Course.find(attachment.container_id)))|| - attachment.author_id == User.current.id - result << attachment - end - end - result - end - - def visable_attachemnts_incourse attachments - result = [] - attachments.each do |attachment| - if attachment.is_public? || (attachment.author.member_of_course?(Course.find(attachment.container_id)))|| attachment.author_id == User.current.id - result << attachment - end - end - result - end - - def visable_attachemnts_insite attachments,course - result = [] - attachments.each do |attachment| - if attachment.is_public? || (attachment.container_type == "Course" && attachment.container_id == course.id && User.current.member_of_course?(Course.find(attachment.container_id)))|| attachment.author_id == User.current.id - result << attachment - end - end - result - end - - def attachment_candown attachment - candown = false - if attachment.container - if attachment.container.class.to_s != "HomeworkAttach" && (attachment.container.has_attribute?(:project) || attachment.container.has_attribute?(:project_id)) && attachment.container.project - project = attachment.container.project - candown= User.current.member_of?(project) || (project.is_public && attachment.is_public == 1) - elsif attachment.container.is_a?(Project) - project = attachment.container - candown= User.current.member_of?(project) || (project.is_public && attachment.is_public == 1) - elsif (attachment.container.has_attribute?(:board) || attachment.container.has_attribute?(:board_id)) && attachment.container.board && - attachment.container.board.project - project = attachment.container.board.project - candown = User.current.member_of?(project) || (project.is_public && attachment.is_public == 1) - elsif (attachment.container.has_attribute?(:course) ||attachment.container.has_attribute?(:course_id) ) && attachment.container.course - course = attachment.container.course - candown = User.current.member_of_course?(course) || (course.is_public==1 && attachment.is_public == 1) - elsif attachment.container.is_a?(Course) - course = attachment.container - candown= User.current.member_of_course?(course) || (course.is_public==1 && attachment.is_public == 1) - elsif (attachment.container.has_attribute?(:board) || attachment.container.has_attribute?(:board_id)) && attachment.container.board && - attachment.container.board.course - course = attachment.container.board.course - candown= User.current.member_of_course?(course) || (course.is_public==1 && attachment.is_public == 1) - elsif attachment.container.class.to_s=="HomeworkAttach" && attachment.container.bid.reward_type == 3 - candown = true - elsif attachment.container_type == "Bid" && attachment.container && attachment.container.courses - course = attachment.container.courses.first - candown = User.current.member_of_course?(attachment.container.courses.first) || (course.is_public == 1 && attachment.is_public == 1) - else - candown = (attachment.is_public == 1 || attachment.is_public == true) - end - end - candown - end - - - +# encoding: utf-8 +module FilesHelper + include AttachmentsHelper + + def downloadAll containers + paths = [] + files = [] + tmpfile = "tmp.zip" + + containers.each do |container| + next if container.attachments.empty? + if container.is_a?(Version);end + container.attachments.each do |attachment| + paths << attachment.diskfile + file = attachment.diskfile + # logger.error "[FilesHelper] downloadAll: #{e}" + begin + File.new(file, "r") + rescue Exception => e + logger.error e + next + end + files << file + # zip.add(file.path.dup.sub(directory, ''), file.path) + end + end + + zipfile_name = "archive.zip" + if File.exists? File.open(zipfile_name, "w+") + ff = File.open(zipfile_name, "w+") + ff.close + File.delete ff + end + Zip::ZipFile.open(zipfile_name, Zip::ZipFile::CREATE) do |zipfile| + files.each do |filename| + directory = File.dirname filename + # Two arguments: + # - The name of the file as it will appear in the archive + # - The original file, including the path to find it + dir = filename.sub(directory+"/", '') + zipfile.add(dir, filename) + + end + end + File.new(zipfile_name,'w+') + end + + #带勾选框的课程列表 + def courses_check_box_tags(name,courses,current_course,attachment) + s = '' + courses.each do |course| + if !course_contains_attachment?(course,attachment) && is_course_teacher(User.current,course) && course_in_current_or_next_term(course) + s << " [#{get_course_term course}]
" + end + end + s.html_safe + end + + #带勾选框的项目列表 + def projects_check_box_tags(name,projects,current_project,attachment) + s = '' + projects.each do |project| + if !project_contains_attachment?(project,attachment) && User.current.allowed_to?(:manage_files, project) + s << "
" + end + end + s.html_safe + end + + #判断用户是否拥有不包含当前资源的课程,需用户在该课程中角色为教师且该课程属于当前学期或下一学期 + def has_course? user,file + result = false + user.courses.each do |course| + if !course_contains_attachment?(course,file) && is_course_teacher(User.current,course) && course_in_current_or_next_term(course) + return true + end + end + result + end + + #判断用户是否拥有不包含当前资源的项目,需用户在该项目中有资源管理相关资源 + def has_project? user,file + result = false + user.projects.each do |project| + if !project_contains_attachment?(project,file) && User.current.allowed_to?(:manage_files, project) + return true + end + end + result + end + + # 判断指定的资源时候符合类型 + def isTypeOk(attachment, type, contentType) + result = false + if type != 0 + if attachment.attachtype == type + result = true + end + else + result = true + end + if result + if contentType != '0' && contentType != attachment.suffix_type + result = false + end + end + result + end + + def visable_attachemnts attachments + result = [] + attachments.each do |attachment| + if attachment.is_public? || + (attachment.container_type == "Project" && User.current.member_of?(attachment.project)) || + (attachment.container_type == "Course" && User.current.member_of_course?(Course.find(attachment.container_id)))|| + attachment.author_id == User.current.id + result << attachment + end + end + result + end + + def get_attachments_by_tag attachments,tag + attachments.each do |attachment| + attachment.tag_list.include?(tag) + end + end + + def visable_attachemnts_insite attachments,obj + result = [] + if obj.is_a?(Course) + attachments.each do |attachment| + if attachment.is_public? || (attachment.container_type == "Course" && attachment.container_id == obj.id && User.current.member_of_course?(Course.find(attachment.container_id)))|| attachment.author_id == User.current.id + result << attachment + end + end + else if obj.is_a?(Project) + attachments.each do |attachment| + if attachment.is_public? || (attachment.container_type == "Project" && attachment.container_id == obj.id && User.current.member_of_course?(Project.find(attachment.container_id)))|| attachment.author_id == User.current.id + result << attachment + end + end + end + end + result + end + + + end \ No newline at end of file diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 6a708051a..b72a191db 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -61,12 +61,38 @@ module IssuesHelper #h("#{issue.tracker} ##{issue.id}") # h("#{issue.tracker} #{issue.source_from}") s = '' - s << link_to(@issue.project.name, project_issues_path(@issue.project)) - s << " > #" - s << @issue.project_index + s << link_to(@issue.project.name, project_issues_path(@issue.project), :class => "pro_page_top") + s << " > " + s << link_to("#" + @issue.project_index, project_issues_path(@issue.project), :class => "pro_page_top") s.html_safe end + #获取跟踪类型 + #REDO:时间紧需要优化,两个方法可以综合成一个 + def get_issue_type(value) + if value == "缺陷" || value == 1 + class_type = "red_btn_cir ml10" + elsif value == "功能" || value == 2 + class_type = "blue_btn_cir ml10" + elsif value == "支持" || value == 3 + class_type = "green_btn_cir ml10" + else + class_type = "orange_btn_cir ml10" + end + end + + def get_issue_typevalue(value) + if value == "缺陷" || value == 1 + assign = "缺陷" + elsif value == "功能" || value == 2 + assign = "功能" + elsif value == "支持" || value == 3 + assign = "支持" + else + assign = "任务" + end + end + def render_issue_subject_with_tree(issue) s = '' ancestors = issue.root? ? [] : issue.ancestors.visible.all @@ -314,17 +340,18 @@ module IssuesHelper if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key) # Link to the attachment if it has not been removed if options[:token].nil? - value = link_to_attachment(atta, :download => true, :only_path => options[:only_path]) + value = atta.filename else - value = link_to_attachment(atta, :download => true, :only_path => options[:only_path], :token => options[:token]) - end - if options[:only_path] != false && atta.is_text? - value += link_to( - image_tag('magnifier.png'), - :controller => 'attachments', :action => 'show', - :id => atta, :filename => atta.filename - ) + value = atta.filename end + # 放大镜搜索功能 + # if options[:only_path] != false && atta.is_text? + # value += link_to( + # image_tag('magnifier.png'), + # :controller => 'attachments', :action => 'show', + # :id => atta, :filename => atta.filename + # ) + # end else value = content_tag("i", h(value)) if value end diff --git a/app/helpers/journals_helper.rb b/app/helpers/journals_helper.rb index cfebb4d30..0eee08b6b 100644 --- a/app/helpers/journals_helper.rb +++ b/app/helpers/journals_helper.rb @@ -46,6 +46,26 @@ module JournalsHelper content.html_safe end + def render_links_easy(issue, journal, options={}) + content = '' + editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project))) + destroyable = User.current.logged? && ((journal.user == User.current) || (issue.author_id == User.current.id) || (User.current.admin == 1)) + links = [] + if !journal.notes.blank? + links << link_to(l(:button_quote), + {:controller => 'journals', :action => 'new', :id => issue.id, :journal_id => journal}, + :remote => true, + :method => 'post', + :title => l(:button_quote)) if options[:reply_links] + if destroyable + links << link_to(l(:button_delete), { :controller => 'journals', :action => 'destroy', :id => journal, :format => 'js' }, + :title => l(:button_delete)) + end + end + content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty? + content.html_safe + end + def render_notes (issue, journal, options={}) content = '' editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project))) @@ -73,6 +93,35 @@ module JournalsHelper content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes ,:style => "width:580px") end + # 缺陷回复内容、引用内容 + # Redo:后面需要统一扩展 + def render_notes_issue (issue, journal, options={}) + content = '' + editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project))) + destroyable = User.current.logged? && ((journal.user == User.current) || (issue.author_id == User.current.id) || (User.current.admin == 1)) + links = [] + if !journal.notes.blank? + links << link_to(l(:button_quote), + {:controller => 'journals', :action => 'new', :id => issue.id, :journal_id => journal}, + :remote => true, + :method => 'post', + :title => l(:button_quote)) if options[:reply_links] + links << link_to_in_place_notes_editor(l(:button_edit), "journal-#{journal.id}-notes", + { :controller => 'journals', :action => 'edit', :id => journal, :format => 'js' }, + :title => l(:button_edit)) if editable + #Added by young + if destroyable + links << link_to(l(:button_delete), { :controller => 'journals', :action => 'destroy', :id => journal, :format => 'js' }, + :title => l(:button_delete)) + end + end + #content << content_tag('div', links.join(' ').html_safe, :class => 'contextual', :style => 'margin-top:-25px;') unless links.empty? + content << textilizable(journal, :notes) + css_classes = "wiki" + css_classes << " editable" if editable + content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes ,:style => "width:510px") + end + def link_to_in_place_notes_editor(text, field_id, url, options={}) onclick = "$.ajax({url: '#{url_for(url)}', type: 'get'}); return false;" link_to text, '#', options.merge(:onclick => onclick) diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb index ed18b7ae4..06b154d36 100644 --- a/app/helpers/members_helper.rb +++ b/app/helpers/members_helper.rb @@ -1,95 +1,110 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module MembersHelper - def render_principals_for_new_members(project) - scope = Principal.active.sorted.not_member_of(project).like(params[:q]) - principal_count = scope.count - principal_pages = Redmine::Pagination::Paginator.new principal_count, 10, params['page'] #by young - principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all - s = content_tag('div', principals_check_box_tags_ex('membership[user_ids][]', principals), :id => 'principals') - links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options| - link_to text, autocomplete_project_memberships_path(project, parameters.merge(:q => params[:q], :format => 'js')), :remote => true - } - s + content_tag('div', content_tag('ul', links), :class => 'pagination_new') - end - - #获取项目可邀请的成员列表 - def render_project_members project - scope = Principal.active.sorted.not_member_of(project).like(params[:q]) - principals = paginateHelper scope,10 - s = content_tag('ul', project_member_check_box_tags_ex('membership[user_ids][]', principals), :class => 'mb5', :style => "margin-left: -40px;") - links = pagination_links_full(@obj_pages, @obj_count, :per_page_links => false, :remote => false, :flag => true){|text, parameters, options| - link_to text, autocomplete_project_memberships_path(project, parameters.merge(:q => params[:q],:flag => true, :format => 'js')), :remote => true - } - s + content_tag('ul', links,:class => 'wlist') - end - - # add by nwb - # 课程可添加的成员列表 - def render_principals_for_new_course_members(course) - scope = Principal.active.sorted.not_member_of_course(course).like(params[:q]) - principal_count = scope.count - principal_pages = Redmine::Pagination::Paginator.new principal_count, 10, params['page'] - principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all - - s = content_tag('div', principals_check_box_tags_ex('membership[user_ids][]', principals), :id => 'principals') - - links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options| - link_to text, autocomplete_course_memberships_path(course, parameters.merge(:q => params[:q], :format => 'js')), :remote => true - } - - s + content_tag('div', content_tag('ul', links), :class => 'pagination_new') - end - - - # 当前申请加入的成员名单 - def render_principals_for_applied_members(project) - scope = project.applied_projects.map(&:user) - principal_count = scope.count - principal_pages = Redmine::Pagination::Paginator.new principal_count, 10, params['page'] - offset ||= principal_pages.offset - principals = scope[offset, 10] - #principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all - #principals = ApplicationController.new.paginateHelper scope,10 - - s = content_tag('div', principals_check_box_tags_ex('membership[user_ids][]', principals), :id => 'principals') - - links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options| - link_to text, appliedproject_project_memberships_path(project, parameters.merge(:q => params[:q], :format => 'js')), :remote => true - } - - s + content_tag('div', content_tag('ul', links), :class => 'applied_new') - end - - private - def paginateHelper obj, pre_size=20 - @obj_count = obj.count - @obj_pages = Redmine::Pagination::Paginator.new @obj_count, pre_size, params['page'] - if obj.kind_of? ActiveRecord::Base or obj.kind_of? ActiveRecord::Relation - obj.limit(@obj_pages.per_page).offset(@obj_pages.offset) - elsif obj.kind_of? Array - obj[@obj_pages.offset, @obj_pages.per_page] - else - logger.error "[ApplicationController] Error : application_controller#paginateHelper ===> unknow category: #{obj.class}" - raise RuntimeError, 'unknow type, Please input you type into this helper.' - end - end - -end +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module MembersHelper + def render_principals_for_new_members(project) + scope = Principal.active.sorted.not_member_of(project).like(params[:q]) + principal_count = scope.count + principal_pages = Redmine::Pagination::Paginator.new principal_count, 10, params['page'] #by young + principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all + s = content_tag('div', principals_check_box_tags_ex('membership[user_ids][]', principals), :id => 'principals') + links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options| + link_to text, autocomplete_project_memberships_path(project, parameters.merge(:q => params[:q], :format => 'js')), :remote => true + } + s + content_tag('div', content_tag('ul', links), :class => 'pagination_new') + end + + #获取项目可邀请的成员列表 + def render_project_members project + if params[:q] && params[:q].lstrip.rstrip != "" + scope = Principal.active.sorted.not_member_of(project).like(params[:q]) + else + scope = [] + end + principals = paginateHelper scope,10 + s = content_tag('ul', project_member_check_box_tags_ex('membership[user_ids][]', principals), :class => 'mb5') + links = pagination_links_full(@obj_pages, @obj_count, :per_page_links => false, :remote => false, :flag => true){|text, parameters, options| + link_to text, autocomplete_project_memberships_path(project, parameters.merge(:q => params[:q],:flag => true, :format => 'js')), :remote => true + } + s + content_tag('ul', links,:class => 'wlist', :id => "course_member_pagination_links" ) + end + + # add by nwb + # 课程可添加的成员列表 + def render_principals_for_new_course_members(course) + if params[:q] && params[:q].lstrip.rstrip != "" + scope = Principal.active.sorted.not_member_of_course(course).like(params[:q]) + else + scope = [] + end + principals = paginateHelper scope,10 + s = content_tag('ul', project_member_check_box_tags_ex('membership[user_ids][]', principals), :class => 'mb5', :id => 'principals') + + links = pagination_links_full(@obj_pages, @obj_count, :per_page_links => false, :remote => false, :flag => true) {|text, parameters, options| + link_to text, autocomplete_course_memberships_path(course, parameters.merge(:q => params[:q], :format => 'js')), :remote => true + } + + s + content_tag('ul', links,:class => 'wlist',:id => "course_member_pagination_links") + end + + # 新申请加入项目成员列表 + def render_principals_for_applied_members_new project + scope = project.applied_projects.map(&:user) + principals = paginateHelper scope,10 + s = content_tag('ul', principals_check_box_tags_li('membership[user_ids][]', principals), :class => 'mb5') + links = pagination_links_full(@obj_pages, @obj_count, :per_page_links => false, :remote => false, :flag => true){|text, parameters, options| + link_to text, appliedproject_project_memberships_path(project, parameters.merge(:q => params[:q],:flag => true, :format => 'js')), :remote => true + } + s + content_tag('ul', links,:class => 'wlist', :id => "course_member_pagination_links" ) + end + + # 当前申请加入的成员名单 + def render_principals_for_applied_members(project) + scope = project.applied_projects.map(&:user) + principal_count = scope.count + principal_pages = Redmine::Pagination::Paginator.new principal_count, 10, params['page'] + offset ||= principal_pages.offset + principals = scope[offset, 10] + #principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all + #principals = ApplicationController.new.paginateHelper scope,10 + + s = content_tag('div', principals_check_box_tags_ex('membership[user_ids][]', principals), :id => 'principals') + + links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options| + link_to text, appliedproject_project_memberships_path(project, parameters.merge(:q => params[:q], :format => 'js')), :remote => true + } + + s + content_tag('div', content_tag('ul', links), :class => 'applied_new') + end + + private + def paginateHelper obj, pre_size=20 + @obj_count = obj.count + @obj_pages = Redmine::Pagination::Paginator.new @obj_count, pre_size, params['page'] + if obj.kind_of? ActiveRecord::Base or obj.kind_of? ActiveRecord::Relation + obj.limit(@obj_pages.per_page).offset(@obj_pages.offset) + elsif obj.kind_of? Array + obj[@obj_pages.offset, @obj_pages.per_page] + else + logger.error "[ApplicationController] Error : application_controller#paginateHelper ===> unknow category: #{obj.class}" + raise RuntimeError, 'unknow type, Please input you type into this helper.' + end + end + +end diff --git a/app/helpers/owner_type_helper.rb b/app/helpers/owner_type_helper.rb new file mode 100644 index 000000000..dd5dbbbac --- /dev/null +++ b/app/helpers/owner_type_helper.rb @@ -0,0 +1,9 @@ +module OwnerTypeHelper + MEMO = 1 + FORUM = 2 + MESSAGE = 3 + NEWS = 4 + COMMENT = 5 + BID = 6 + JOURNALSFORMESSAGE = 7 +end \ No newline at end of file diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 3c1663fcb..508e58ba3 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -21,7 +21,12 @@ include AvatarHelper module ProjectsHelper def link_to_version(version, options = {}) return '' unless version && version.is_a?(Version) - link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => version }, options + link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => version }, :class => "c_blue02" + end + + def link_to_version_show(version, options = {}) + return '' unless version && version.is_a?(Version) + link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => version }, :class => " f16 fb c_dblue " end def project_settings_tabs @@ -29,7 +34,7 @@ module ProjectsHelper {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural}, {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural}, {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural}, - {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural}, + # {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural}, # {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki}, {:name => 'repositories', :action => :manage_repository, :partial => 'projects/settings/repositories', :label => :label_repository_plural}, #{:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}, @@ -386,4 +391,20 @@ module ProjectsHelper end type end + + #显示项目配置菜单 + def show_project_memu user + if user.allowed_to?(:edit_project, @project) + result = "edit_project" + elsif user.allowed_to?(:select_project_modules, @project) + result = "select_project_modules" + elsif user.allowed_to?(:manage_members, @project) + result = "manage_members" + elsif user.allowed_to?(:manage_versions, @project) + result = "manage_versions" + elsif user.allowed_to?(:manage_repository, @project) + result = "manage_repository" + end + result + end end diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb index f36d2ea94..e1d8c3e12 100644 --- a/app/helpers/queries_helper.rb +++ b/app/helpers/queries_helper.rb @@ -255,8 +255,19 @@ module QueriesHelper # Give it a name, required to be valid @query = IssueQuery.new(:name => "_") @query.project = @project + params[:f] = %w(subject status_id priority_id author_id assigned_to_id) unless params[:status_id].nil? + params[:op] = {'subject' => "~" , + 'status_id' => ( params[:status_id] == '0' ? "!":"=" ), + 'priority_id' => ( params[:priority_id] == '0' ? "!":"=" ), + 'author_id' => ( params[:author_id] == '0' ? "!":"=" ), + 'assigned_to_id' => ( params[:assigned_to_id] == '0' ? "!":"=" )} unless params[:status_id].nil? + params[:v] = {'subject' => [params[:subject]], + 'status_id' => [params[:status_id]], + 'priority_id' => [params[:priority_id]], + 'author_id' => [params[:author_id]], + 'assigned_to_id' => [params[:assigned_to_id]]} unless params[:status_id].nil? @query.build_from_params(params) - session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} + #session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} else # retrieve from session @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id] diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index 3c7123435..773560932 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -18,7 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module RepositoriesHelper - ROOT_PATH="/home/pdl/redmine-2.3.2-0/apache2/" + if Rails.env.development? + ROOT_PATH="/tmp/" if Rails.env.development? + else + ROOT_PATH="/home/pdl/redmine-2.3.2-0/apache2/" + end PROJECT_PATH_CUT = 40 REPO_IP_ADDRESS = Setting.repository_domain diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 58935dd11..af2d5abc4 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -278,4 +278,29 @@ module UsersHelper end } end + + #获取用户留言相关的连接 + def user_jour_feed_back_url active + if active.act_type == "JournalsForMessage" + jour = JournalsForMessage.find active.act_id + if jour + case jour.jour_type + when "Principal" + link_to(l(:label_goto), user_newfeedback_user_path(jour.jour_id)) + when "Project" + link_to(l(:label_goto), project_feedback_path(jour.jour_id)) + when "Bid" + link_to(l(:label_goto), course_for_bid_path(jour.jour_id)) + when "Course" + link_to(l(:label_goto), course_feedback_path(jour.jour_id)) + when "Contest" + link_to(l(:label_goto), show_contest_contest_path(jour.jour_id)) + when "Softapplication" + link_to(l(:label_goto), softapplication_path(jour.jour_id)) + when "HomeworkAttach" + link_to(l(:label_goto), course_for_bid_path(jour.jour_id)) + end + end + end + end end diff --git a/app/helpers/watchers_helper.rb b/app/helpers/watchers_helper.rb index 8974a52d3..d3b2a49b1 100644 --- a/app/helpers/watchers_helper.rb +++ b/app/helpers/watchers_helper.rb @@ -42,7 +42,28 @@ module WatchersHelper :object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort) ) method = watched ? 'delete' : 'post' - + + link_to text, url, :remote => true, :method => method, :class => css + end + + def watcher_link_issue(objects, user, options=[]) + return '' unless user && user.logged? + objects = Array.wrap(objects) + + watched = objects.any? {|object| object.watched_by?(user)} + @watch_flag = (objects.first.instance_of?(User) or objects.first.instance_of?(Project) or objects.first.instance_of?(Contest) or (objects.first.instance_of?(Bid))) + css = @watch_flag ? ([watcher_css(objects), watched ? 'talk_edit ' : 'talk_edit '].join(' ') << options[0].to_s) : + ([watcher_css(objects), watched ? 'talk_edit fr ' : 'talk_edit fr '].join(' ') << options[0].to_s) + + text = @watch_flag ? + (watched ? l(:button_unfollow) : l(:button_follow)) : (watched ? l(:button_unwatch) : l(:button_watch)) + + url = watch_path( + :object_type => objects.first.class.to_s.underscore, + :object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort) + ) + method = watched ? 'delete' : 'post' + link_to text, url, :remote => true, :method => method, :class => css end @@ -155,7 +176,7 @@ module WatchersHelper else text = l(:label_new_join_group) form_tag({:controller => "courses", :action => "join_group", :object_id => "#{group.id}"}, :remote => true, :method => 'post') do - submit_tag text, class: "group_in", style: "width: 43px;height: 21px;" + submit_tag text, class: "group_in", style: "width: 90px;height: 21px;" end end end @@ -261,17 +282,36 @@ module WatchersHelper content.present? ? content_tag('ul', content, :class => 'watchers') : content end + + + + + + +# 缺陷跟踪者列表复选框生成 def watchers_checkboxes(object, users, checked=nil) if users.nil? else + # tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil + # content_tag 'label', "#{tag} #{h(user)}".html_safe, + # :id => "issue_watcher_user_ids_#{user.id}", + # :class => "floating" users.map do |user| c = checked.nil? ? object.watched_by?(user) : checked - tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil - content_tag 'label', "#{tag} #{h(user)}".html_safe, - :id => "issue_watcher_user_ids_#{user.id}", - :class => "floating" + s = content_tag(:ul, + content_tag(:li, "#{check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil } #{h link_to user.userInfo, user_path( user.id)}".html_safe, + :id=>"issue_watcher_user_ids_#{user.id}",:style=>"float: left;width: 175px;margin: 0px 20px 10px 0px; overflow: hidden; line-height:1.6em;" ), + :class => "mb10 ml80") end.join.html_safe + + # scope = users.sort + # watchers = paginateHelper scope,10 + # s = content_tag('ul', issue_watcher_check_box_tags_ex('issue[watcher_user_ids][]', watchers), :class => 'mb10 ml80') + # links = pagination_links_full(@obj_pages, @obj_count, :per_page_links => false, :remote => false, :flag => true){|text, parameters, options| + # link_to text, watchers_autocomplete_for_user_path(@users, parameters.merge(:q => params[:q],:format => 'js',:flag => 'ture')), :remote => true + # } + # s + content_tag('ul', links,:class => 'wlist', :style =>"float:left;margin-top:0px;") end end @@ -304,8 +344,8 @@ module WatchersHelper def exit_project_link(project) link_to(l(:label_exit_project),exit_cur_project_path(project.id), :remote => true, :confirm => l(:lable_sure_exit_project), - :style => "color: #fff; display:block;font-size:12px; padding: 0px 5px; margin-right: 10px; height: 20px; line-height: 22px; background: none repeat scroll 0% 0% #64BDD9; TES;padding-top:1px;" ) - end + :class => "pr_join_a_quit" ) + end #项目关注、取消关注 #REDO:项目样式确定后方法需要对CSS变量进行改进 @@ -321,7 +361,7 @@ module WatchersHelper :object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort)) method = watched ? 'delete' : 'post' link_to text, url, :remote => true, :method => method, - :class => "project_watch_new" ,:id=>id + :class => "pr_join_a" ,:id=>id end #申请加入项目 @@ -339,7 +379,21 @@ module WatchersHelper :user_id => user.id, :project_id => project.id) method = applied ? 'delete' : 'post' - link_to text, url, :remote => true, :method => method , :class => "project_watch_new",:id => id + link_to text, url, :remote => true, :method => method , :class => "pr_join_a",:id => id + end + + def paginateHelper obj, pre_size=20 + @obj_count = obj.count + @obj_pages = Redmine::Pagination::Paginator.new @obj_count, pre_size, params['page'] + if obj.kind_of? ActiveRecord::Base or obj.kind_of? ActiveRecord::Relation + obj.limit(@obj_pages.per_page).offset(@obj_pages.offset) + elsif obj.kind_of? Array + obj[@obj_pages.offset, @obj_pages.per_page] + else + logger.error "[ApplicationController] Error : application_controller#paginateHelper ===> unknow category: #{obj.class}" + raise RuntimeError, 'unknow type, Please input you type into this helper.' end + end + end diff --git a/app/helpers/welcome_helper.rb b/app/helpers/welcome_helper.rb index f6e205e82..4800a949a 100644 --- a/app/helpers/welcome_helper.rb +++ b/app/helpers/welcome_helper.rb @@ -443,6 +443,10 @@ module WelcomeHelper resultSet.take(limit) end + def find_my_projects + my_projects = User.current.memberships.all(conditions: "projects.project_type = 0") + end + def sort_project_by_hot_rails project_type=0, order_by='score DESC', limit=15 # Project.find_by_sql(" # SELECT p.id, p.name, p.description, p.identifier, t.project_id diff --git a/app/models/bid.rb b/app/models/bid.rb index 10476b4d3..ccd6198bb 100644 --- a/app/models/bid.rb +++ b/app/models/bid.rb @@ -17,7 +17,8 @@ class Bid < ActiveRecord::Base HomeworkProject = 2 attr_accessible :author_id, :budget, :deadline, :name, :description, :homework_type, :password include Redmine::SafeAttributes - + include ApplicationHelper + has_many_kindeditor_assets :assets, :dependent => :destroy belongs_to :author, :class_name => 'User', :foreign_key => :author_id belongs_to :course has_many :biding_projects, :dependent => :destroy @@ -47,7 +48,7 @@ class Bid < ActiveRecord::Base validate :validate_user validate :validate_reward_type after_create :act_as_activity - + after_destroy :delete_kindeditor_assets scope :visible, lambda {|*args| nil } @@ -157,5 +158,10 @@ class Bid < ActiveRecord::Base end end - + # Time 2015-04-01 14:19:06 + # Author lizanle + # Description 删除对应课程通知的图片资源 + def delete_kindeditor_assets + delete_kindeditor_assets_from_disk self.id,OwnerTypeHelper::BID + end end diff --git a/app/models/comment.rb b/app/models/comment.rb index 539c62e85..bdb642d3c 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -17,8 +17,25 @@ class Comment < ActiveRecord::Base include Redmine::SafeAttributes + include ApplicationHelper + has_many_kindeditor_assets :assets, :dependent => :destroy belongs_to :commented, :polymorphic => true, :counter_cache => true belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' validates_presence_of :commented, :author, :comments safe_attributes 'comments' + after_create :send_mail + + def send_mail + if self.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added') + Mailer.run.news_comment_added(self) + end + end + after_destroy :delete_kindeditor_assets + + # Time 2015-03-31 09:15:06 + # Author lizanle + # Description 删除对应评论的图片资源 + def delete_kindeditor_assets + delete_kindeditor_assets_from_disk self.id,OwnerTypeHelper::COMMENT + end end diff --git a/app/models/comment_observer.rb b/app/models/comment_observer.rb deleted file mode 100644 index a46e53ab9..000000000 --- a/app/models/comment_observer.rb +++ /dev/null @@ -1,27 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class CommentObserver < ActiveRecord::Observer - def after_create(comment) - if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added') - ##by senluo - thread3=Thread.new do - Mailer.news_comment_added(comment).deliver - end - end - end -end diff --git a/app/models/discuss_demo.rb b/app/models/discuss_demo.rb deleted file mode 100644 index 6ed8d15b6..000000000 --- a/app/models/discuss_demo.rb +++ /dev/null @@ -1,4 +0,0 @@ -class DiscussDemo < ActiveRecord::Base - attr_accessible :title, :body - has_many_kindeditor_assets :assets, :dependent => :destroy -end diff --git a/app/models/document.rb b/app/models/document.rb index 33ffdaa2f..6bfb4b8ff 100644 --- a/app/models/document.rb +++ b/app/models/document.rb @@ -1,90 +1,95 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class Document < ActiveRecord::Base - include Redmine::SafeAttributes - belongs_to :project - belongs_to :user - belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id" - include UserScoreHelper - after_save :be_user_score # user_score - after_destroy :down_user_score - acts_as_attachable :delete_permission => :delete_documents - # 被ForgeActivity虚拟关联 - has_many :forge_acts, :class_name => 'ForgeActivity',:as =>:forge_act ,:dependent => :destroy - # end - acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project - acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"}, - #:author => Proc.new {|o| o.attachments.reorder("#{Attachment.table_name}.created_on ASC").first.try(:author) }, - :author => Proc.new {|o| User.find(o.user_id)}, - :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}} - acts_as_activity_provider :find_options => {:include => :project}, - :is_public => 'documents.is_public' - - validates_presence_of :project, :title, :category - validates_length_of :title, :maximum => 60 - after_create :act_as_forge_activity - scope :visible, lambda {|*args| - includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_documents, *args)) - } - - safe_attributes 'category_id', 'title', 'description','is_public' - - def visible?(user=User.current) - !user.nil? && user.allowed_to?(:view_documents, project) - end - - def has_right?(project,user=User.current) - user.admin? || user.member_of?(project) || self.is_public==1 - end - - def initialize(attributes=nil, *args) - super - if new_record? - self.category ||= DocumentCategory.default - end - end - - def updated_on - unless @updated_on - a = attachments.last - @updated_on = (a && a.created_on) || created_on - end - @updated_on - end - - # update user score - def be_user_score - UserScore.project(:push_document, self.user,self,{ document_id: self.id }) - update_document(self.user,1) - update_document(self.user,2,self.project) - end - - def down_user_score - update_document(self.user,1) - update_document(self.user,2,self.project) - end - - # Time 2015-03-02 10:51:16 - # Author lizanle - # Description 新创建的document要在公共表ForgeActivity中记录 - def act_as_forge_activity - self.forge_acts << ForgeActivity.new(:user_id => self.user_id, - :project_id => self.project_id) - end - -end +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Document < ActiveRecord::Base + include Redmine::SafeAttributes + belongs_to :project + belongs_to :user + belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id" + include UserScoreHelper + after_save :be_user_score # user_score + after_destroy :down_user_score + acts_as_attachable :delete_permission => :delete_documents + after_create :send_mail + # 被ForgeActivity虚拟关联 + has_many :forge_acts, :class_name => 'ForgeActivity',:as =>:forge_act ,:dependent => :destroy + # end + acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project + acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"}, + #:author => Proc.new {|o| o.attachments.reorder("#{Attachment.table_name}.created_on ASC").first.try(:author) }, + :author => Proc.new {|o| User.find(o.user_id)}, + :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}} + acts_as_activity_provider :find_options => {:include => :project}, + :is_public => 'documents.is_public' + + validates_presence_of :project, :title, :category + validates_length_of :title, :maximum => 60 + after_create :act_as_forge_activity + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_documents, *args)) + } + + safe_attributes 'category_id', 'title', 'description','is_public' + + def visible?(user=User.current) + !user.nil? && user.allowed_to?(:view_documents, project) + end + + def has_right?(project,user=User.current) + user.admin? || user.member_of?(project) || self.is_public==1 + end + + def initialize(attributes=nil, *args) + super + if new_record? + self.category ||= DocumentCategory.default + end + end + + def updated_on + unless @updated_on + a = attachments.last + @updated_on = (a && a.created_on) || created_on + end + @updated_on + end + + # update user score + def be_user_score + UserScore.project(:push_document, self.user,self,{ document_id: self.id }) + update_document(self.user,1) + update_document(self.user,2,self.project) + end + + def down_user_score + update_document(self.user,1) + update_document(self.user,2,self.project) + end + + # Time 2015-03-02 10:51:16 + # Author lizanle + # Description 新创建的document要在公共表ForgeActivity中记录 + def act_as_forge_activity + self.forge_acts << ForgeActivity.new(:user_id => self.user_id, + :project_id => self.project_id) + end + + def send_mail + Mailer.run.document_added(self) if Setting.notified_events.include?('document_added') + end + +end diff --git a/app/models/document_observer.rb b/app/models/document_observer.rb deleted file mode 100644 index 447952c70..000000000 --- a/app/models/document_observer.rb +++ /dev/null @@ -1,25 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class DocumentObserver < ActiveRecord::Observer - def after_create(document) - ##by senluo - thread2=Thread.new do - Mailer.document_added(document).deliver if Setting.notified_events.include?('document_added') - end - end -end diff --git a/app/models/forum.rb b/app/models/forum.rb index 6843ab678..2af1abf9e 100644 --- a/app/models/forum.rb +++ b/app/models/forum.rb @@ -1,59 +1,58 @@ -class Forum < ActiveRecord::Base - include Redmine::SafeAttributes - include ApplicationHelper - has_many_kindeditor_assets :assets, :dependent => :destroy - has_many :topics, :class_name => 'Memo', :conditions => "#{Memo.table_name}.parent_id IS NULL", :order => "#{Memo.table_name}.created_at DESC", :dependent => :destroy - has_many :memos, :dependent => :destroy, conditions: "parent_id IS NULL" - belongs_to :creator, :class_name => "User", :foreign_key => 'creator_id' - safe_attributes 'name', - 'description', - 'topic_count', - 'memo_count', - 'last_memo_id', - 'creator_id', - 'sticky', - 'locked' - validates_presence_of :name, :creator_id, :description - validates_length_of :name, maximum: 50 - #validates_length_of :description, maximum: 255 - validates :name, :uniqueness => true - after_destroy :delete_kindeditor_assets - acts_as_taggable - scope :by_join_date, order("created_at DESC") - #after_create :send_email - def reset_counters! - self.class.reset_counters!(id) - end - - def editable_by? user - # user && user.logged? || (self.author == usr && usr.allowed_to?(:edit_own_messages, project)) - self.creator == user || user.admin? - end - - def destroyable_by? user - # user && user.logged? && Forum.find(self.forum_id).creator_id == user.id || user.admin? - self.creator == user || user.admin? - end - - def send_email - Thread.start do - Mailer.forum_add(self).deliver if Setting.notified_events.include?('forum_add') - end - end - # Updates topic_count, memo_count and last_memo_id attributes for +board_id+ - def self.reset_counters!(forum_id) - forum_id = forum_id.to_i - update_all("topic_count = (SELECT COUNT(*) FROM #{Memo.table_name} WHERE forum_id=#{forum_id} AND parent_id IS NULL)," + - " memo_count = (SELECT COUNT(*) FROM #{Memo.table_name} WHERE forum_id=#{forum_id} AND parent_id IS NOT NULL)," + - " last_memo_id = (SELECT MAX(id) FROM #{Memo.table_name} WHERE forum_id=#{forum_id})", - ["id = ?", forum_id]) - end - - # Time 2015-03-26 15:50:54 - # Author lizanle - # Description 删除论坛后删除对应的资源 - def delete_kindeditor_assets - delete_kindeditor_assets_from_disk self.id,2 - end - -end +class Forum < ActiveRecord::Base + include Redmine::SafeAttributes + include ApplicationHelper + has_many_kindeditor_assets :assets, :dependent => :destroy + has_many :topics, :class_name => 'Memo', :conditions => "#{Memo.table_name}.parent_id IS NULL", :order => "#{Memo.table_name}.created_at DESC", :dependent => :destroy + has_many :memos, :dependent => :destroy, conditions: "parent_id IS NULL" + belongs_to :creator, :class_name => "User", :foreign_key => 'creator_id' + safe_attributes 'name', + 'description', + 'topic_count', + 'memo_count', + 'last_memo_id', + 'creator_id', + 'sticky', + 'locked' + validates_presence_of :name, :creator_id, :description + validates_length_of :name, maximum: 50 + #validates_length_of :description, maximum: 255 + validates :name, :uniqueness => true + after_destroy :delete_kindeditor_assets + acts_as_taggable + scope :by_join_date, order("created_at DESC") + after_create :send_mail + def reset_counters! + self.class.reset_counters!(id) + end + + def editable_by? user + # user && user.logged? || (self.author == usr && usr.allowed_to?(:edit_own_messages, project)) + self.creator == user || user.admin? + end + + def destroyable_by? user + # user && user.logged? && Forum.find(self.forum_id).creator_id == user.id || user.admin? + self.creator == user || user.admin? + end + + def send_mail + logger.debug "send mail for forum add." + Mailer.run.forum_add(self) if Setting.notified_events.include?('forum_add') + end + # Updates topic_count, memo_count and last_memo_id attributes for +board_id+ + def self.reset_counters!(forum_id) + forum_id = forum_id.to_i + update_all("topic_count = (SELECT COUNT(*) FROM #{Memo.table_name} WHERE forum_id=#{forum_id} AND parent_id IS NULL)," + + " memo_count = (SELECT COUNT(*) FROM #{Memo.table_name} WHERE forum_id=#{forum_id} AND parent_id IS NOT NULL)," + + " last_memo_id = (SELECT MAX(id) FROM #{Memo.table_name} WHERE forum_id=#{forum_id})", + ["id = ?", forum_id]) + end + + # Time 2015-03-26 15:50:54 + # Author lizanle + # Description 删除论坛后删除对应的资源 + def delete_kindeditor_assets + delete_kindeditor_assets_from_disk self.id,OwnerTypeHelper::FORUM + end + +end diff --git a/app/models/forum_observer.rb b/app/models/forum_observer.rb deleted file mode 100644 index 6afcac824..000000000 --- a/app/models/forum_observer.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ForumObserver < ActiveRecord::Observer - # def after_create(forum) - # Thread.start do - # Mailer.forum_add(forum).deliver if Setting.notified_events.include?('forum_add') - # end - # - # end -end diff --git a/app/models/issue_observer.rb b/app/models/issue_observer.rb index e404a4a1c..ea78349af 100644 --- a/app/models/issue_observer.rb +++ b/app/models/issue_observer.rb @@ -1,30 +1,28 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class IssueObserver < ActiveRecord::Observer - - def after_create(issue) - Thread.start do - # 将跟踪者与本项目的其他成员都设为收件方,并去重,不在进行抄送, - recipients = issue.recipients - issue.watcher_recipients + issue.watcher_recipients - recipients.each do |rec| - Mailer.issue_add(issue,rec).deliver if Setting.notified_events.include?('issue_added') - end - end - - end -end +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class IssueObserver < ActiveRecord::Observer + + def after_create(issue) + # 将跟踪者与本项目的其他成员都设为收件方,并去重,不在进行抄送, + recipients = issue.recipients - issue.watcher_recipients + issue.watcher_recipients + recipients.each do |rec| + Mailer.run.issue_add(issue,rec) if Setting.notified_events.include?('issue_added') + end + + end +end diff --git a/app/models/issue_query.rb b/app/models/issue_query.rb index c55143ca8..dba45cb43 100644 --- a/app/models/issue_query.rb +++ b/app/models/issue_query.rb @@ -310,7 +310,7 @@ class IssueQuery < Query :order => options[:order], :limit => options[:limit], :offset => options[:offset] - ) + ).reverse rescue ::ActiveRecord::StatementInvalid => e raise StatementInvalid.new(e.message) end diff --git a/app/models/journal_observer.rb b/app/models/journal_observer.rb index b58464a9b..c5b0e496b 100644 --- a/app/models/journal_observer.rb +++ b/app/models/journal_observer.rb @@ -1,36 +1,34 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class JournalObserver < ActiveRecord::Observer - def after_create(journal) - if journal.notify? && - (Setting.notified_events.include?('issue_updated') || - (Setting.notified_events.include?('issue_note_added') && journal.notes.present?) || - (Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) || - (Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?) - ) - Thread.start do - # 将跟踪者与本项目的其他成员都设为收件方,并去重,不在进行抄送, - recipients = journal.recipients - journal.watcher_recipients + journal.watcher_recipients - recipients.each do |rec| - - Mailer.issue_edit(journal,rec).deliver - end - end - end - end -end +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class JournalObserver < ActiveRecord::Observer + def after_create(journal) + if journal.notify? && + (Setting.notified_events.include?('issue_updated') || + (Setting.notified_events.include?('issue_note_added') && journal.notes.present?) || + (Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) || + (Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?) + ) + # 将跟踪者与本项目的其他成员都设为收件方,并去重,不在进行抄送, + recipients = journal.recipients - journal.watcher_recipients + journal.watcher_recipients + recipients.each do |rec| + + Mailer.run.issue_edit(journal,rec) + end + end + end +end diff --git a/app/models/journals_for_message.rb b/app/models/journals_for_message.rb index 23d9c7753..239a15881 100644 --- a/app/models/journals_for_message.rb +++ b/app/models/journals_for_message.rb @@ -1,167 +1,179 @@ -# fq -# 数据库字段中带有m前缀和is_readed是二次开发添加,之前的字段基本复用 -# 注意reply_id 是提到人的id,不是留言id, Base中叫做 at_user -class JournalsForMessage < ActiveRecord::Base - include Redmine::SafeAttributes - include UserScoreHelper - safe_attributes "jour_type", # 留言所属类型 - "jour_id", # 留言所属类型的id - "notes", # 留言内容 - "reply_id", # 留言被回复留言者的用户id(用户a回复了用户b,这是b的id,用以查询谁给b留言了) - "status", # 留言是否被查看(弃用) - "user_id", # 留言者的id - "m_parent_id", # 留言信息的父留言id - "is_readed", # 留言是否已读 - "m_reply_count", # 留言的回复数量 - "m_reply_id" # 回复某留言的留言id(a留言回复了b留言,这是b留言的id) - "is_comprehensive_evaluation" # 1 教师评论、2 匿评、3 留言 - acts_as_tree :foreign_key => 'm_parent_id', :counter_cache => :m_reply_count, :order => "#{JournalsForMessage.table_name}.created_on ASC" - - belongs_to :project, - :foreign_key => 'jour_id', - :conditions => "#{self.table_name}.jour_type = 'Project' " - belongs_to :course, - :foreign_key => 'jour_id' - - - belongs_to :jour, :polymorphic => true - belongs_to :user - belongs_to :homework_attach - belongs_to :at_user, :class_name => "User", :foreign_key => 'reply_id' - - acts_as_event :title => Proc.new {|o| "#{l(:label_my_message)}"}, - :datetime => Proc.new {|o| o.updated_on }, - :author => Proc.new {|o| o.user }, - :description => Proc.new{|o| o.notes }, - :type => Proc.new {|o| o.jour_type }, - :url => Proc.new {|o| - if o.jour.kind_of? Project - {:controller => 'projects', :action => 'feedback', :id => o.jour, :r => o.id, :anchor => "word_li_#{o.id}"} - elsif o.jour.kind_of? Course - {:controller => 'courses', :action => 'feedback', :id => o.jour, :r => o.id, :anchor => "word_li_#{o.id}"} - end - } - acts_as_activity_provider :author_key => :user_id, - :timestamp => "#{self.table_name}.updated_on", - :find_options => {:include => :project } - - acts_as_activity_provider :type => 'course_journals_for_messages', - :author_key => :user_id, - :timestamp => "#{self.table_name}.updated_on", - :find_options => {:include => :course } - - - has_many :acts, :class_name => 'Activity', :as => :act, :dependent => :destroy - - validates :notes, presence: true - after_create :act_as_activity #huang - after_create :reset_counters! - after_destroy :reset_counters! - after_save :be_user_score - after_destroy :down_user_score - - # default_scope { where('m_parent_id IS NULL') } - - def self.create_by_user? user - if user.anonymous? - return false - else - return true - end - end - - - def self.remove_by_user? user - if( self.user == user || - ( self.jour.kind_of?(User) && self.jour== user ) - ) - true - else - false - end - end - - def self.delete_message(message_id) - self.find(message_id).destroy - # self.destroy_all "id = #{message_id}" - end - - def reference_user - User.find(reply_id) - end - - def delete_by_user?user - # 用户可删除自己的留言 - if self.user.id == user.id || user.admin? - return true - else - return false - end - end - - def self.reference_message(user_id) - @user = User.find(user_id) - message = JournalsForMessage.find_by_sql("select * from journals_for_messages where reply_id = #{@user.id} - or (jour_type = 'Bid' and jour_id in (select id from bids where author_id = #{@user.id}))") - message - end - - def act_as_activity - if self.jour_type == 'Principal' - unless self.user_id == self.jour.id && self.user_id != self.reply_id && self.reply_id != 0 - # self.acts << Activity.new(:user_id => self.user_id) - self.acts << Activity.new(:user_id => self.jour_id) - end - elsif self.jour_type == 'Project' - self.acts << Activity.new(:user_id => self.reply_id) - elsif self.jour_type == 'Course' - self.acts << Activity.new(:user_id => self.reply_id) - else - end - end - - def reset_counters! - self.class.reset_counters!(self) - end - def self.reset_counters! journals_for_messages - # jfm_id = journals_for_messages.id.to_i - count = find_all_by_m_parent_id(journals_for_messages.m_parent_id).count #(SELECT COUNT(*) FROM #{JournalsForMessage.table_name} WHERE m_parent_id = #{jfm_id} ) - update_all("m_reply_count = #{count.to_i}", ["id = ?", journals_for_messages.m_parent_id]) - end - - #如果是在项目中留言则返回该项目否则返回nil - zjc - def project - if self.jour_type == 'Project' - Project.find(self.jour_id) - else - nil - end - end - - # 更新用户分数 -by zjc - def be_user_score - #新建了留言回复 - if self.reply_id != 0 - #协同得分加分 - UserScore.joint(:reply_message, self.user,User.find(self.reply_id),self, { journals_for_messages_id: self.id }) - update_replay_for_message(self.user,1) - if self.jour_type == "Project" - update_replay_for_message(self.user,2,self.jour) - end - end - end - # 更新用户分数 -by zjc - def down_user_score - #删除了留言回复 - if self.reply_id != 0 - #协同得分减分 - UserScore.joint(:reply_message_delete, self.user,User.find(self.reply_id), { journals_for_messages_id: self.id }) - update_replay_for_message(self.user,1) - if self.jour_type == "Project" - update_replay_for_message(self.user,2,self.jour) - end - end - end - - -end +# fq +# 数据库字段中带有m前缀和is_readed是二次开发添加,之前的字段基本复用 +# 注意reply_id 是提到人的id,不是留言id, Base中叫做 at_user +class JournalsForMessage < ActiveRecord::Base + include Redmine::SafeAttributes + include UserScoreHelper + include ApplicationHelper + has_many_kindeditor_assets :assets, :dependent => :destroy + safe_attributes "jour_type", # 留言所属类型 + "jour_id", # 留言所属类型的id + "notes", # 留言内容 + "reply_id", # 留言被回复留言者的用户id(用户a回复了用户b,这是b的id,用以查询谁给b留言了) + "status", # 留言是否被查看(弃用) + "user_id", # 留言者的id + "m_parent_id", # 留言信息的父留言id + "is_readed", # 留言是否已读 + "m_reply_count", # 留言的回复数量 + "m_reply_id" # 回复某留言的留言id(a留言回复了b留言,这是b留言的id) + "is_comprehensive_evaluation" # 1 教师评论、2 匿评、3 留言 + acts_as_tree :foreign_key => 'm_parent_id', :counter_cache => :m_reply_count, :order => "#{JournalsForMessage.table_name}.created_on ASC" + after_destroy :delete_kindeditor_assets + belongs_to :project, + :foreign_key => 'jour_id', + :conditions => "#{self.table_name}.jour_type = 'Project' " + belongs_to :course, + :foreign_key => 'jour_id' + + + belongs_to :jour, :polymorphic => true + belongs_to :user + belongs_to :homework_attach + belongs_to :at_user, :class_name => "User", :foreign_key => 'reply_id' + + acts_as_event :title => Proc.new {|o| "#{l(:label_my_message)}"}, + :datetime => Proc.new {|o| o.updated_on }, + :author => Proc.new {|o| o.user }, + :description => Proc.new{|o| o.notes }, + :type => Proc.new {|o| o.jour_type }, + :url => Proc.new {|o| + if o.jour.kind_of? Project + {:controller => 'projects', :action => 'feedback', :id => o.jour, :r => o.id, :anchor => "word_li_#{o.id}"} + elsif o.jour.kind_of? Course + {:controller => 'courses', :action => 'feedback', :id => o.jour, :r => o.id, :anchor => "word_li_#{o.id}"} + end + } + acts_as_activity_provider :author_key => :user_id, + :timestamp => "#{self.table_name}.updated_on", + :find_options => {:include => :project } + + acts_as_activity_provider :type => 'course_journals_for_messages', + :author_key => :user_id, + :permission => :view_course_journals_for_messages, + :timestamp => "#{self.table_name}.updated_on", + :find_options => {:include => :course } + acts_as_attachable + + has_many :acts, :class_name => 'Activity', :as => :act, :dependent => :destroy + + validates :notes, presence: true, if: :is_homework_jour? + after_create :act_as_activity #huang + after_create :reset_counters! + after_destroy :reset_counters! + after_save :be_user_score + after_destroy :down_user_score + + # default_scope { where('m_parent_id IS NULL') } + + def self.create_by_user? user + if user.anonymous? + return false + else + return true + end + end + + + def self.remove_by_user? user + if( self.user == user || + ( self.jour.kind_of?(User) && self.jour== user ) + ) + true + else + false + end + end + + def self.delete_message(message_id) + self.find(message_id).destroy + # self.destroy_all "id = #{message_id}" + end + + def is_homework_jour? + self.jour_type != "HomeworkAttach" + end + + def reference_user + User.find(reply_id) + end + + def delete_by_user?user + # 用户可删除自己的留言 + if self.user.id == user.id || user.admin? + return true + else + return false + end + end + + def self.reference_message(user_id) + @user = User.find(user_id) + message = JournalsForMessage.find_by_sql("select * from journals_for_messages where reply_id = #{@user.id} + or (jour_type = 'Bid' and jour_id in (select id from bids where author_id = #{@user.id}))") + message + end + + def act_as_activity + if self.jour_type == 'Principal' + unless self.user_id == self.jour.id && self.user_id != self.reply_id && self.reply_id != 0 + # self.acts << Activity.new(:user_id => self.user_id) + self.acts << Activity.new(:user_id => self.jour_id) + end + elsif self.jour_type == 'Project' + self.acts << Activity.new(:user_id => self.reply_id) + elsif self.jour_type == 'Course' + self.acts << Activity.new(:user_id => self.reply_id) + else + end + end + + def reset_counters! + self.class.reset_counters!(self) + end + def self.reset_counters! journals_for_messages + # jfm_id = journals_for_messages.id.to_i + count = find_all_by_m_parent_id(journals_for_messages.m_parent_id).count #(SELECT COUNT(*) FROM #{JournalsForMessage.table_name} WHERE m_parent_id = #{jfm_id} ) + update_all("m_reply_count = #{count.to_i}", ["id = ?", journals_for_messages.m_parent_id]) + end + + #如果是在项目中留言则返回该项目否则返回nil - zjc + def project + if self.jour_type == 'Project' + Project.find(self.jour_id) + else + nil + end + end + + # 更新用户分数 -by zjc + def be_user_score + #新建了留言回复 + if self.reply_id != 0 + #协同得分加分 + UserScore.joint(:reply_message, self.user,User.find(self.reply_id),self, { journals_for_messages_id: self.id }) + update_replay_for_message(self.user,1) + if self.jour_type == "Project" + update_replay_for_message(self.user,2,self.jour) + end + end + end + # 更新用户分数 -by zjc + def down_user_score + #删除了留言回复 + if self.reply_id != 0 + #协同得分减分 + UserScore.joint(:reply_message_delete, self.user,User.find(self.reply_id), { journals_for_messages_id: self.id }) + update_replay_for_message(self.user,1) + if self.jour_type == "Project" + update_replay_for_message(self.user,2,self.jour) + end + end + end + + # Time 2015-04-01 14:15:06 + # Author lizanle + # Description 删除对应课程留言的图片资源 + def delete_kindeditor_assets + delete_kindeditor_assets_from_disk self.id,7 + end +end diff --git a/app/models/journals_for_message_observer.rb b/app/models/journals_for_message_observer.rb index 0e5f29d03..0db2e0043 100644 --- a/app/models/journals_for_message_observer.rb +++ b/app/models/journals_for_message_observer.rb @@ -1,9 +1,7 @@ -# Added by young -class JournalsForMessageObserver < ActiveRecord::Observer - def after_create(journals_for_message) - thread1 = Thread.start do - Mailer.journals_for_message_add(User.current, journals_for_message).deliver - end - end -end - +# Added by young +class JournalsForMessageObserver < ActiveRecord::Observer + def after_create(journals_for_message) + Mailer.run.journals_for_message_add(User.current, journals_for_message) + end +end + diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb index 8421fb67d..bf268ad66 100644 --- a/app/models/mail_handler.rb +++ b/app/models/mail_handler.rb @@ -1,490 +1,490 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class MailHandler < ActionMailer::Base - include ActionView::Helpers::SanitizeHelper - include Redmine::I18n - - class UnauthorizedAction < StandardError; end - class MissingInformation < StandardError; end - - attr_reader :email, :user - - def self.receive(email, options={}) - @@handler_options = options.dup - - @@handler_options[:issue] ||= {} - - if @@handler_options[:allow_override].is_a?(String) - @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) - end - @@handler_options[:allow_override] ||= [] - # Project needs to be overridable if not specified - @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project) - # Status overridable by default - @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status) - - @@handler_options[:no_account_notice] = (@@handler_options[:no_account_notice].to_s == '1') - @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1') - @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1') - - email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding) - super(email) - end - - def logger - Rails.logger - end - - cattr_accessor :ignored_emails_headers - @@ignored_emails_headers = { - 'X-Auto-Response-Suppress' => 'oof', - 'Auto-Submitted' => /^auto-/ - } - - # Processes incoming emails - # Returns the created object (eg. an issue, a message) or false - def receive(email) - @email = email - sender_email = email.from.to_a.first.to_s.strip - # Ignore emails received from the application emission address to avoid hell cycles - if sender_email.downcase == Setting.mail_from.to_s.strip.downcase - if logger && logger.info - logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" - end - return false - end - # Ignore auto generated emails - self.class.ignored_emails_headers.each do |key, ignored_value| - value = email.header[key] - if value - value = value.to_s.downcase - if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value - if logger && logger.info - logger.info "MailHandler: ignoring email with #{key}:#{value} header" - end - return false - end - end - end - @user = User.find_by_mail(sender_email) if sender_email.present? - if @user && !@user.active? - if logger && logger.info - logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" - end - return false - end - if @user.nil? - # Email was submitted by an unknown user - case @@handler_options[:unknown_user] - when 'accept' - @user = User.anonymous - when 'create' - @user = create_user_from_email - if @user - if logger && logger.info - logger.info "MailHandler: [#{@user.login}] account created" - end - add_user_to_group(@@handler_options[:default_group]) - unless @@handler_options[:no_account_notice] - Mailer.account_information(@user, @user.password).deliver - end - else - if logger && logger.error - logger.error "MailHandler: could not create account for [#{sender_email}]" - end - return false - end - else - # Default behaviour, emails from unknown users are ignored - if logger && logger.info - logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" - end - return false - end - end - User.current = @user - dispatch - end - - private - - MESSAGE_ID_RE = %r{^ e - # TODO: send a email to the user - logger.error e.message if logger - false - rescue MissingInformation => e - logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger - false - rescue UnauthorizedAction => e - logger.error "MailHandler: unauthorized attempt from #{user}" if logger - false - end - - def dispatch_to_default - receive_issue - end - - # Creates a new issue - def receive_issue - project = target_project - # check permission - unless @@handler_options[:no_permission_check] - raise UnauthorizedAction unless user.allowed_to?(:add_issues, project) - end - - issue = Issue.new(:author => user, :project => project) - issue.safe_attributes = issue_attributes_from_keywords(issue) - issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} - issue.subject = cleaned_up_subject - if issue.subject.blank? - issue.subject = '(no subject)' - end - issue.description = cleaned_up_text_body - - # add To and Cc as watchers before saving so the watchers can reply to Redmine - add_watchers(issue) - issue.save! - add_attachments(issue) - logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info - issue - end - - # Adds a note to an existing issue - def receive_issue_reply(issue_id, from_journal=nil) - issue = Issue.find_by_id(issue_id) - return unless issue - # check permission - unless @@handler_options[:no_permission_check] - unless user.allowed_to?(:add_issue_notes, issue.project) || - user.allowed_to?(:edit_issues, issue.project) - raise UnauthorizedAction - end - end - - # ignore CLI-supplied defaults for new issues - @@handler_options[:issue].clear - - journal = issue.init_journal(user) - if from_journal && from_journal.private_notes? - # If the received email was a reply to a private note, make the added note private - issue.private_notes = true - end - issue.safe_attributes = issue_attributes_from_keywords(issue) - issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} - journal.notes = cleaned_up_text_body - add_attachments(issue) - issue.save! - if logger && logger.info - logger.info "MailHandler: issue ##{issue.id} updated by #{user}" - end - journal - end - - # Reply will be added to the issue - def receive_journal_reply(journal_id) - journal = Journal.find_by_id(journal_id) - if journal && journal.journalized_type == 'Issue' - receive_issue_reply(journal.journalized_id, journal) - end - end - - # Receives a reply to a forum message - def receive_message_reply(message_id) - message = Message.find_by_id(message_id) - if message - message = message.root - - unless @@handler_options[:no_permission_check] - raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project) - end - - if !message.locked? - reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip, - :content => cleaned_up_text_body) - reply.author = user - reply.board = message.board - message.children << reply - add_attachments(reply) - reply - else - if logger && logger.info - logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" - end - end - end - end - - def add_attachments(obj) - if email.attachments && email.attachments.any? - email.attachments.each do |attachment| - obj.attachments << Attachment.create(:container => obj, - :file => attachment.decoded, - :filename => attachment.filename, - :author => user, - :content_type => attachment.mime_type) - end - end - end - - # Adds To and Cc as watchers of the given object if the sender has the - # appropriate permission - def add_watchers(obj) - if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project) - addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase} - unless addresses.empty? - watchers = User.active.where('LOWER(mail) IN (?)', addresses).all - watchers.each {|w| obj.add_watcher(w)} - end - end - end - - def get_keyword(attr, options={}) - @keywords ||= {} - if @keywords.has_key?(attr) - @keywords[attr] - else - @keywords[attr] = begin - if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && - (v = extract_keyword!(plain_text_body, attr, options[:format])) - v - elsif !@@handler_options[:issue][attr].blank? - @@handler_options[:issue][attr] - end - end - end - end - - # Destructively extracts the value for +attr+ in +text+ - # Returns nil if no matching keyword found - def extract_keyword!(text, attr, format=nil) - keys = [attr.to_s.humanize] - if attr.is_a?(Symbol) - if user && user.language.present? - keys << l("field_#{attr}", :default => '', :locale => user.language) - end - if Setting.default_language.present? - keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) - end - end - keys.reject! {|k| k.blank?} - keys.collect! {|k| Regexp.escape(k)} - format ||= '.+' - keyword = nil - regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i - if m = text.match(regexp) - keyword = m[2].strip - text.gsub!(regexp, '') - end - keyword - end - - def target_project - # TODO: other ways to specify project: - # * parse the email To field - # * specific project (eg. Setting.mail_handler_target_project) - target = Project.find_by_identifier(get_keyword(:project)) - raise MissingInformation.new('Unable to determine target project') if target.nil? - target - end - - # Returns a Hash of issue attributes extracted from keywords in the email body - def issue_attributes_from_keywords(issue) - assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue) - - attrs = { - 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id), - 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id), - 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id), - 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id), - 'assigned_to_id' => assigned_to.try(:id), - 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && - issue.project.shared_versions.named(k).first.try(:id), - 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), - 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), - 'estimated_hours' => get_keyword(:estimated_hours, :override => true), - 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0') - }.delete_if {|k, v| v.blank? } - - if issue.new_record? && attrs['tracker_id'].nil? - attrs['tracker_id'] = issue.project.trackers.first.try(:id) - end - - attrs - end - - # Returns a Hash of issue custom field values extracted from keywords in the email body - def custom_field_values_from_keywords(customized) - customized.custom_field_values.inject({}) do |h, v| - if keyword = get_keyword(v.custom_field.name, :override => true) - h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized) - end - h - end - end - - # Returns the text/plain part of the email - # If not found (eg. HTML-only email), returns the body with tags removed - def plain_text_body - return @plain_text_body unless @plain_text_body.nil? - - part = email.text_part || email.html_part || email - @plain_text_body = Redmine::CodesetUtil.to_utf8(part.body.decoded, part.charset) - - # strip html tags and remove doctype directive - @plain_text_body = strip_tags(@plain_text_body.strip) - @plain_text_body.sub! %r{^$/) - addr, name = m[2], m[1] - end - if addr.present? - user = self.class.new_user_from_attributes(addr, name) - if @@handler_options[:no_notification] - user.mail_notification = 'none' - end - if user.save - user - else - logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger - nil - end - else - logger.error "MailHandler: failed to create User: no FROM address found" if logger - nil - end - end - - # Adds the newly created user to default group - def add_user_to_group(default_group) - if default_group.present? - default_group.split(',').each do |group_name| - if group = Group.named(group_name).first - group.users << @user - elsif logger - logger.warn "MailHandler: could not add user to [#{group_name}], group not found" - end - end - end - end - - # Removes the email body of text after the truncation configurations. - def cleanup_body(body) - delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)} - unless delimiters.empty? - regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE) - body = body.gsub(regex, '') - end - body.strip - end - - def find_assignee_from_keyword(keyword, issue) - keyword = keyword.to_s.downcase - assignable = issue.assignable_users - assignee = nil - assignee ||= assignable.detect {|a| - a.mail.to_s.downcase == keyword || - a.login.to_s.downcase == keyword - } - if assignee.nil? && keyword.match(/ /) - firstname, lastname = *(keyword.split) # "First Last Throwaway" - assignee ||= assignable.detect {|a| - a.is_a?(User) && a.firstname.to_s.downcase == firstname && - a.lastname.to_s.downcase == lastname - } - end - if assignee.nil? - assignee ||= assignable.detect {|a| a.name.downcase == keyword} - end - assignee - end -end +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class MailHandler < ActionMailer::Base + include ActionView::Helpers::SanitizeHelper + include Redmine::I18n + + class UnauthorizedAction < StandardError; end + class MissingInformation < StandardError; end + + attr_reader :email, :user + + def self.receive(email, options={}) + @@handler_options = options.dup + + @@handler_options[:issue] ||= {} + + if @@handler_options[:allow_override].is_a?(String) + @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) + end + @@handler_options[:allow_override] ||= [] + # Project needs to be overridable if not specified + @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project) + # Status overridable by default + @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status) + + @@handler_options[:no_account_notice] = (@@handler_options[:no_account_notice].to_s == '1') + @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1') + @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1') + + email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding) + super(email) + end + + def logger + Rails.logger + end + + cattr_accessor :ignored_emails_headers + @@ignored_emails_headers = { + 'X-Auto-Response-Suppress' => 'oof', + 'Auto-Submitted' => /^auto-/ + } + + # Processes incoming emails + # Returns the created object (eg. an issue, a message) or false + def receive(email) + @email = email + sender_email = email.from.to_a.first.to_s.strip + # Ignore emails received from the application emission address to avoid hell cycles + if sender_email.downcase == Setting.mail_from.to_s.strip.downcase + if logger && logger.info + logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" + end + return false + end + # Ignore auto generated emails + self.class.ignored_emails_headers.each do |key, ignored_value| + value = email.header[key] + if value + value = value.to_s.downcase + if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value + if logger && logger.info + logger.info "MailHandler: ignoring email with #{key}:#{value} header" + end + return false + end + end + end + @user = User.find_by_mail(sender_email) if sender_email.present? + if @user && !@user.active? + if logger && logger.info + logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" + end + return false + end + if @user.nil? + # Email was submitted by an unknown user + case @@handler_options[:unknown_user] + when 'accept' + @user = User.anonymous + when 'create' + @user = create_user_from_email + if @user + if logger && logger.info + logger.info "MailHandler: [#{@user.login}] account created" + end + add_user_to_group(@@handler_options[:default_group]) + unless @@handler_options[:no_account_notice] + Mailer.run.account_information(@user, @user.password) + end + else + if logger && logger.error + logger.error "MailHandler: could not create account for [#{sender_email}]" + end + return false + end + else + # Default behaviour, emails from unknown users are ignored + if logger && logger.info + logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" + end + return false + end + end + User.current = @user + dispatch + end + + private + + MESSAGE_ID_RE = %r{^ e + # TODO: send a email to the user + logger.error e.message if logger + false + rescue MissingInformation => e + logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger + false + rescue UnauthorizedAction => e + logger.error "MailHandler: unauthorized attempt from #{user}" if logger + false + end + + def dispatch_to_default + receive_issue + end + + # Creates a new issue + def receive_issue + project = target_project + # check permission + unless @@handler_options[:no_permission_check] + raise UnauthorizedAction unless user.allowed_to?(:add_issues, project) + end + + issue = Issue.new(:author => user, :project => project) + issue.safe_attributes = issue_attributes_from_keywords(issue) + issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} + issue.subject = cleaned_up_subject + if issue.subject.blank? + issue.subject = '(no subject)' + end + issue.description = cleaned_up_text_body + + # add To and Cc as watchers before saving so the watchers can reply to Redmine + add_watchers(issue) + issue.save! + add_attachments(issue) + logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info + issue + end + + # Adds a note to an existing issue + def receive_issue_reply(issue_id, from_journal=nil) + issue = Issue.find_by_id(issue_id) + return unless issue + # check permission + unless @@handler_options[:no_permission_check] + unless user.allowed_to?(:add_issue_notes, issue.project) || + user.allowed_to?(:edit_issues, issue.project) + raise UnauthorizedAction + end + end + + # ignore CLI-supplied defaults for new issues + @@handler_options[:issue].clear + + journal = issue.init_journal(user) + if from_journal && from_journal.private_notes? + # If the received email was a reply to a private note, make the added note private + issue.private_notes = true + end + issue.safe_attributes = issue_attributes_from_keywords(issue) + issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} + journal.notes = cleaned_up_text_body + add_attachments(issue) + issue.save! + if logger && logger.info + logger.info "MailHandler: issue ##{issue.id} updated by #{user}" + end + journal + end + + # Reply will be added to the issue + def receive_journal_reply(journal_id) + journal = Journal.find_by_id(journal_id) + if journal && journal.journalized_type == 'Issue' + receive_issue_reply(journal.journalized_id, journal) + end + end + + # Receives a reply to a forum message + def receive_message_reply(message_id) + message = Message.find_by_id(message_id) + if message + message = message.root + + unless @@handler_options[:no_permission_check] + raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project) + end + + if !message.locked? + reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip, + :content => cleaned_up_text_body) + reply.author = user + reply.board = message.board + message.children << reply + add_attachments(reply) + reply + else + if logger && logger.info + logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" + end + end + end + end + + def add_attachments(obj) + if email.attachments && email.attachments.any? + email.attachments.each do |attachment| + obj.attachments << Attachment.create(:container => obj, + :file => attachment.decoded, + :filename => attachment.filename, + :author => user, + :content_type => attachment.mime_type) + end + end + end + + # Adds To and Cc as watchers of the given object if the sender has the + # appropriate permission + def add_watchers(obj) + if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project) + addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase} + unless addresses.empty? + watchers = User.active.where('LOWER(mail) IN (?)', addresses).all + watchers.each {|w| obj.add_watcher(w)} + end + end + end + + def get_keyword(attr, options={}) + @keywords ||= {} + if @keywords.has_key?(attr) + @keywords[attr] + else + @keywords[attr] = begin + if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && + (v = extract_keyword!(plain_text_body, attr, options[:format])) + v + elsif !@@handler_options[:issue][attr].blank? + @@handler_options[:issue][attr] + end + end + end + end + + # Destructively extracts the value for +attr+ in +text+ + # Returns nil if no matching keyword found + def extract_keyword!(text, attr, format=nil) + keys = [attr.to_s.humanize] + if attr.is_a?(Symbol) + if user && user.language.present? + keys << l("field_#{attr}", :default => '', :locale => user.language) + end + if Setting.default_language.present? + keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) + end + end + keys.reject! {|k| k.blank?} + keys.collect! {|k| Regexp.escape(k)} + format ||= '.+' + keyword = nil + regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i + if m = text.match(regexp) + keyword = m[2].strip + text.gsub!(regexp, '') + end + keyword + end + + def target_project + # TODO: other ways to specify project: + # * parse the email To field + # * specific project (eg. Setting.mail_handler_target_project) + target = Project.find_by_identifier(get_keyword(:project)) + raise MissingInformation.new('Unable to determine target project') if target.nil? + target + end + + # Returns a Hash of issue attributes extracted from keywords in the email body + def issue_attributes_from_keywords(issue) + assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue) + + attrs = { + 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id), + 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id), + 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id), + 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id), + 'assigned_to_id' => assigned_to.try(:id), + 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && + issue.project.shared_versions.named(k).first.try(:id), + 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), + 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), + 'estimated_hours' => get_keyword(:estimated_hours, :override => true), + 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0') + }.delete_if {|k, v| v.blank? } + + if issue.new_record? && attrs['tracker_id'].nil? + attrs['tracker_id'] = issue.project.trackers.first.try(:id) + end + + attrs + end + + # Returns a Hash of issue custom field values extracted from keywords in the email body + def custom_field_values_from_keywords(customized) + customized.custom_field_values.inject({}) do |h, v| + if keyword = get_keyword(v.custom_field.name, :override => true) + h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized) + end + h + end + end + + # Returns the text/plain part of the email + # If not found (eg. HTML-only email), returns the body with tags removed + def plain_text_body + return @plain_text_body unless @plain_text_body.nil? + + part = email.text_part || email.html_part || email + @plain_text_body = Redmine::CodesetUtil.to_utf8(part.body.decoded, part.charset) + + # strip html tags and remove doctype directive + @plain_text_body = strip_tags(@plain_text_body.strip) + @plain_text_body.sub! %r{^$/) + addr, name = m[2], m[1] + end + if addr.present? + user = self.class.new_user_from_attributes(addr, name) + if @@handler_options[:no_notification] + user.mail_notification = 'none' + end + if user.save + user + else + logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger + nil + end + else + logger.error "MailHandler: failed to create User: no FROM address found" if logger + nil + end + end + + # Adds the newly created user to default group + def add_user_to_group(default_group) + if default_group.present? + default_group.split(',').each do |group_name| + if group = Group.named(group_name).first + group.users << @user + elsif logger + logger.warn "MailHandler: could not add user to [#{group_name}], group not found" + end + end + end + end + + # Removes the email body of text after the truncation configurations. + def cleanup_body(body) + delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)} + unless delimiters.empty? + regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE) + body = body.gsub(regex, '') + end + body.strip + end + + def find_assignee_from_keyword(keyword, issue) + keyword = keyword.to_s.downcase + assignable = issue.assignable_users + assignee = nil + assignee ||= assignable.detect {|a| + a.mail.to_s.downcase == keyword || + a.login.to_s.downcase == keyword + } + if assignee.nil? && keyword.match(/ /) + firstname, lastname = *(keyword.split) # "First Last Throwaway" + assignee ||= assignable.detect {|a| + a.is_a?(User) && a.firstname.to_s.downcase == firstname && + a.lastname.to_s.downcase == lastname + } + end + if assignee.nil? + assignee ||= assignable.detect {|a| a.name.downcase == keyword} + end + assignee + end +end diff --git a/app/models/mailer.rb b/app/models/mailer.rb index e1c538fd0..7756b7e27 100644 --- a/app/models/mailer.rb +++ b/app/models/mailer.rb @@ -1,854 +1,931 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class Mailer < ActionMailer::Base - layout 'mailer' - helper :application - helper :issues - helper :custom_fields - - include Redmine::I18n - include CoursesHelper - def self.default_url_options - { :host => Setting.host_name, :protocol => Setting.protocol } - end - - # author: alan - # 发送邀请未注册用户加入项目邮件 - # 功能: 在加入项目的同时自动注册用户 - def send_invite_in_project(email, project, invitor) - @email = email - @subject = "#{invitor.name} #{l(:label_invite_project)} #{project.name} " - @password = newpass(6) - @project_url = url_for(:controller => 'projects', :action => 'show', :id => project.id, - :password => @password, :login => email) - mail :to => email, :subject => @subject - end - - # author: alan - # 根据用户选择发送个人日报或周报 - # 发送内容: 项目【缺陷,讨论区,新闻】,课程【通知,留言,新闻】, 贴吧, 个人留言 - def send_for_user_activities(user, date_to, days) - date_from = date_to - days.days - - # 生成token用于直接点击登录 - @user = user - token = Token.new(:user =>user , :action => 'autologin') - token.save - @token = token - - @user_url = url_for(my_account_url(user,:token => @token.value)) - # 查询user参加的项目及课程 - projects = user.projects - courses = user.courses - project_ids = projects.map{|project| project.id}.join(",") - course_ids = courses.map {|course| course.id}.join(",") - - # 查询user的缺陷,包括发布的,跟踪的以及被指派的缺陷 - @issues = Issue.find_by_sql("select DISTINCT i.* from issues i, watchers w - where (i.assigned_to_id = #{user.id} or i.author_id = #{user.id} - or (w.watchable_type = 'Issue' and w.watchable_id = i.id and w.user_id = #{user.id})) - and (i.created_on between '#{date_from}' and '#{date_to}') order by i.created_on desc") - - # @bids 查询课程作业,包括老师发布的作业,以及user提交作业 - # @attachments查询课程课件更新 - @attachments ||= [] - - @bids ||= [] # 老师发布的作业 - - unless courses.first.nil? - count = courses.count - count = count - 1 - for i in 0..count do - bids = courses[i].homeworks.where("bids.created_on between '#{date_from}' and '#{date_to}'").order("bids.created_on desc") - attachments = courses[i].attachments.where("attachments.created_on between '#{date_from}' and '#{date_to}'").order('attachments.created_on DESC') - @bids += bids if bids.count > 0 - @attachments += attachments if attachments.count > 0 - end - end - # user 提交的作业 - @homeworks = HomeworkAttach.where("user_id=#{user.id} and (created_at between '#{date_from}' and '#{date_to}')").order("created_at desc") - - # 查询user在课程。项目中发布的讨论帖子 - messages = Message.find_by_sql("select DISTINCT * from messages where author_id = #{user.id} and (created_on between '#{date_from}' and '#{date_to}') order by created_on desc") - @course_messages ||= [] - @project_messages ||= [] - unless messages.first.nil? - messages.each do |msg| - if msg.project - @project_messages << msg - elsif msg.course - @course_messages << msg - end - end - end - # 查询user在课程中发布的通知,项目中发的新闻 - @course_news = News.find_by_sql("select DISTINCT n.* from news n - where n.course_id in (#{course_ids}) - and (created_on between '#{date_from}' and '#{date_to}') order by created_on desc") - @project_news = News.find_by_sql("select DISTINCT n.* from news n where n.project_id in (#{project_ids}) - and (created_on between '#{date_from}' and '#{date_to}') order by created_on desc") - - # 查询user在课程及个人中留言 - @course_journal_messages = JournalsForMessage.find_by_sql("select DISTINCT * from journals_for_messages where - jour_type='Course' and user_id = #{user.id} - and (created_on between '#{date_from}' and '#{date_to}') order by created_on desc") - @user_journal_messages = user.journals_for_messages.where("m_parent_id IS NULL and (created_on between '#{date_from}' and '#{date_to}')").order('created_on DESC') - - - # 查询user新建贴吧或发布帖子 - @forums = Forum.find_by_sql("select DISTINCT * from forums where creator_id = #{user.id} and (created_at between '#{date_from}' and '#{date_to}') order by created_at desc") - @memos = Memo.find_by_sql("select DISTINCT m.* from memos m, forums f where (m.author_id = #{user.id} or (m.forum_id = f.id and f.creator_id = #{user.id})) - and (m.created_at between '#{date_from}' and '#{date_to}') order by m.created_at desc") - if days == 1 - subject = "[ #{user.show_name} : #{l(:label_day_mail)}]" - @subject = " #{user.show_name} : #{date_to - 1.days} #{l(:label_day_mail)}" - else - subject = "[ #{user.show_name} : #{l(:label_week_mail)}]" - @subject = "#{user.show_name} : #{l(:label_week_mail)}" - end - mail :to => user.mail,:subject => subject - - end - - # 公共讨论区发帖、回帖添加邮件发送信息 - def forum_message_added(memo) - @memo = memo - redmine_headers 'Memo' => memo.id - @forum = memo.forum - @author = memo.author - @forum_url = url_for(:controller => 'forums', :action => 'show', :id => @forum.id) - @issue_author_url = url_for(user_activities_url(@author)) - recipients ||= [] - #将帖子创建者邮箱地址加入数组 - if @forum.creator.mail_notification != 'day' && @forum.creator.mail_notification != 'week' - recipients << @forum.creator.mail - end - #回复人邮箱地址加入数组 - if @author.mail_notification != 'day' && @author.mail_notification != 'week' - recipients << @author.mail - end - # cc = wiki_content.page.wiki.watcher_recipients - recipients - - @memo_url = url_for(forum_memo_url(@forum, (@memo.parent_id.nil? ? @memo : @memo.parent_id))) - mail :to => recipients,:subject => "[ #{l(:label_message_plural)} : #{memo.subject} #{l(:label_memo_create_succ)}]" - end - # Builds a Mail::Message object used to email recipients of the added journals for message. - - # 留言分为直接留言,和对留言人留言的回复 - # 字段说明在JournalsForMessage.rb - # 直接留言后 reply_id,m_parent_id 为空,相对应的at_user取值为nil - - def journals_for_message_add(user, journals_for_message) - @user = journals_for_message.user # 留言人 - @mail = journals_for_message.jour if journals_for_message.at_user.nil? # 留言 - @mail = journals_for_message.at_user if journals_for_message.at_user - @message = journals_for_message.notes - @title = "#@user #{t(:label_leave_your_message, :locale => 'zh')}" - @issue_author_url = url_for(user_activities_url(@user)) - @url = case journals_for_message.jour.class.to_s.to_sym # 判断留言的对象所属类型 - when :Bid - course_for_bid_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") - when :Project - return -1 if journals_for_message.jour.project_type == Project::ProjectType_project - project_feedback_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") - when :Course - course_feedback_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") - when :Contest - show_contest_contest_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") - when :User - user_newfeedback_user_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") - else - Rails.logger.error "[Builds a Mail::Message ERROR] journalsForMessage's jour is unkown type, journalsForMessage.id = #{journals_for_message.id}" - return -1 - end - - # 验证用户的收取邮件的方式 - recipients ||= [] - recipients1 ||= [] - if @mail.mail_notification != 'week' && @mail.mail_notification != 'day' - recipients1 = @mail.mail - end - if journals_for_message.jour.author.mail_notification != 'week' && journals_for_message.jour.author.mail_notification != 'day' - recipients = journals_for_message.jour.author.mail - end - - # modify by nwb - #如果是直接留言并且留言对象是课程 - if !journals_for_message.at_user && journals_for_message.jour.class.to_s.to_sym == :Course - course = journals_for_message.jour - @author = journals_for_message.user - #课程的教师 - @members = course_all_member journals_for_message.jour - #收件人邮箱 - @recipients ||= [] - @members.each do |teacher| - if teacher.user.mail_notification != 'week' && teacher.user.mail_notification != 'day' - @recipients << teacher.user.mail - end - end - mail :to => @recipients, - :subject => "#{l(:label_your_course)}#{journals_for_message.jour.name}#{l(:label_have_message)} " - elsif journals_for_message.jour.class.to_s.to_sym == :Bid - if !journals_for_message.jour.author.notify_about? journals_for_message - return -1 - end - - mail :to => recipients, :subject => @title - elsif journals_for_message.jour.class.to_s.to_sym == :Contest - if !journals_for_message.jour.author.notify_about? journals_for_message - return -1 - end - mail :to => recipients, :subject => @title - else - mail :to => recipients1, :subject => @title - end - - - end - - # Builds a Mail::Message object used to email recipients of the added issue. - # - # Example: - # issue_add(issue) => Mail::Message object - # Mailer.issue_add(issue).deliver => sends an email to issue recipients - def issue_add(issue, recipients) - issue_id = issue.project_index - redmine_headers 'Project' => issue.project.identifier, - 'Issue-Id' => issue_id, - 'Issue-Author' => issue.author.login - redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to - message_id issue - - @author = issue.author - @issue = issue - user = User.find_by_mail(recipients) - token = Token.new(:user =>user , :action => 'autologin') - token.save - @token = token - @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue.id, :token => @token.value) - - # edit - @issue_author_url = url_for(user_activities_url(@author,:token => @token.value)) - @project_url = url_for(:controller => 'projects', :action => 'show', :id => issue.project_id, :token => @token.value) - - @user_url = url_for(my_account_url(user,:token => @token.value)) - - - subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue_id}] (#{issue.status.name}) #{issue.subject}" - mail(:to => recipients, - - :subject => subject) - end - # issue.attachments.each do |attach| - # attachments["#{attach.filename}"] = File.read("#{attach.disk_filename}") - # end - # cc = issue.watcher_recipients - recipients - #mail.attachments['test'] = File.read("#{RAILS.root}/files/2015/01/150114094010_libegl.dll") - - - - - # Builds a Mail::Message object used to email recipients of the edited issue. - # - # Example: - # issue_edit(journal) => Mail::Message object - # Mailer.issue_edit(journal).deliver => sends an email to issue recipients - def issue_edit(journal,recipients) - issue = journal.journalized.reload - issue_id = issue.project_index - redmine_headers 'Project' => issue.project.identifier, - 'Issue-Id' => issue_id.to_s, - 'Issue-Author' => issue.author.login - redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to - message_id journal - references issue - @author = journal.user - - user = User.find_by_mail(recipients) - - token = Token.new(:user =>user , :action => 'autologin') - token.save - @token = token - - - # edit - @issue_author_url = url_for(:controller => 'users', :action => 'show', :id => issue.author_id, :token => @token.value) - @project_url = url_for(:controller => 'projects', :action => 'show', :id => issue.project_id, :token => @token.value) - @user_url = url_for(my_account_url(user,:token => @token.value)) - - @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue.id, :anchor => "change-#{journal.id}", :token => @token.value) - s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue_id}] " - s << "(#{issue.status.name}) " if journal.new_value_for('status_id') - s << issue.subject - @issue = issue - @journal = journal - # @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}") - mail(:to => recipients, - - :subject => s) - end - - def self.deliver_mailer(to,cc, subject) - mail :to => to, - :cc => cc, - :subject => subject - end - - # 用户申请加入项目邮件通知 - def applied_project(applied) - @project =applied.project - redmine_headers 'Project' => @project, - 'User' => applied.user - @user = applied.user - recipients = @project.manager_recipients - s = l(:text_applied_project, :id => "##{@user.show_name}", :project => @project.name) - @applied_url = url_for(:controller => 'projects', :action => 'settings', :id => @project.id,:tab=>'members') - mail :to => recipients, - :subject => s - end - - def reminder(user, issues, days) - set_language_if_valid user.language - @issues = issues - @days = days - @issues_url = url_for(:controller => 'issues', :action => 'index', - :set_filter => 1, :assigned_to_id => user.id, - :sort => 'due_date:asc') - mail :to => user.mail, - :subject => l(:mail_subject_reminder, :count => issues.size, :days => days) - end - - #缺陷到期邮件通知 - def issue_expire issue - issue_id = issue.project_index - redmine_headers 'Project' => issue.project.identifier, - 'Issue-Id' => issue_id, - 'Issue-Author' => issue.author.login - redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to - message_id issue - @author = issue.author - @issue = issue - @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue.id) - recipients = issue.recipients - s = l(:text_issue_expire,:issue => "[#{issue.project.name} - #{issue.tracker.name} ##{issue_id}] (#{issue.status.name}) #{issue.subject}") - mail :to => recipients, - :subject => s - ######################################################################################################### - #@issues = issues - #s = l(:text_issue_expire,:issue => "[#{issue.project.name} - #{issue.tracker.name} ##{issue_id}] (#{issue.status.name}) #{issue.subject}") - #puts s + "////" + issue.assigned_to.mail - #@issues_url = url_for(:controller => 'issues', :action => 'show',:id => issue.id) - #mail :to => issue.assigned_to.mail, - # :subject => s - ######################################################################################################### - #issue_id = issue.project_index - #redmine_headers 'Project' => issue.project.identifier, - # 'Issue-Id' => issue_id, - # 'Issue-Author' => issue.author.login - #redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to - #message_id issue - #@author = issue.author - #@issue = issue - #@issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue) - #recipients = issue.recipients - #cc = issue.watcher_recipients - recipients - #mail :to => recipients, - # :cc => cc, - # :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue_id}] (#{issue.status.name}) #{issue.subject}" - ###################################################################################################### - end - - - # Builds a Mail::Message object used to email users belonging to the added document's project. - # - # Example: - # document_added(document) => Mail::Message object - # Mailer.document_added(document).deliver => sends an email to the document's project recipients - def document_added(document) - redmine_headers 'Project' => document.project.identifier - @author = User.current - @document = document - @issue_author_url = url_for(user_activities_url(@author)) - @document_url = url_for(:controller => 'documents', :action => 'show', :id => document) - mail :to => document.recipients, - :subject => "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}" - end - - # Builds a Mail::Message object used to email recipients of a project when an attachements are added. - # - # Example: - # attachments_added(attachments) => Mail::Message object - # Mailer.attachments_added(attachments).deliver => sends an email to the project's recipients - def attachments_added(attachments) - container = attachments.first.container - added_to = '' - added_to_url = '' - @author = attachments.first.author - @issue_author_url = url_for(user_activities_url(@author)) - case container.class.name - when 'Project' - added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container) - added_to = "#{l(:label_project)}: #{container}" - recipients = container.notified_users.select { |user| user.allowed_to?(:view_files, container) }.collect { |u| u.mail } - when 'Course' - added_to_url = url_for(:controller => 'files', :action => 'index', :course_id => container) - added_to = "#{l(:label_course)}: #{container.name}" - recipients = container.notified_users.select { |user| user.allowed_to?(:view_files, container) }.collect { |u| u.mail } - when 'Version' - added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project) - added_to = "#{l(:label_version)}: #{container.name}" - recipients = container.project.notified_users.select { |user| user.allowed_to?(:view_files, container.project) }.collect { |u| u.mail } - when 'Document' - added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id) - added_to = "#{l(:label_document)}: #{container.title}" - recipients = container.recipients - end - if container.class.name == 'Course' - redmine_headers 'Course' => container.id - @attachments = attachments - @added_to = added_to - @added_to_url = added_to_url - mail :to => recipients, - :subject => "[#{container.name}] #{l(:label_attachment_new)}" - elsif container.class.name == 'Project' - redmine_headers 'Project' => container.id - @attachments = attachments - @added_to = added_to - @added_to_url = added_to_url - mail :to => recipients, - :subject => "[#{container.name}] #{l(:label_attachment_new)}" - else - redmine_headers 'Project' => container.project.identifier - @attachments = attachments - @added_to = added_to - @added_to_url = added_to_url - mail :to => recipients, - :subject => "[#{container.project.name}] #{l(:label_attachment_new)}" - end - end - - # Builds a Mail::Message object used to email recipients of a news' project when a news item is added. - # - # Example: - # news_added(news) => Mail::Message object - # Mailer.news_added(news).deliver => sends an email to the news' project recipients - def news_added(news) - - if news.project - redmine_headers 'Project' => news.project.identifier - @author = news.author - @issue_author_url = url_for(user_activities_url(@author)) - message_id news - @news = news - @news_url = url_for(:controller => 'news', :action => 'show', :id => news) - mail :to => news.recipients, - :subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}" - elsif news.course - redmine_headers 'Course' => news.course.id - @author = news.author - @issue_author_url = url_for(user_activities_url(@author)) - message_id news - @news = news - recipients = news.course.notified_users.select { |user| user.allowed_to?(:view_files, news.course) }.collect { |u| u.mail } - @news_url = url_for(:controller => 'news', :action => 'show', :id => news) - mail :to => recipients, - :subject => "[#{news.course.name}] #{l(:label_news)}: #{news.title}" - end - end - - # Builds a Mail::Message object used to email recipients of a news' project when a news comment is added. - # - # Example: - # news_comment_added(comment) => Mail::Message object - # Mailer.news_comment_added(comment) => sends an email to the news' project recipients - def news_comment_added(comment) - news = comment.commented - if news.project - redmine_headers 'Project' => news.project.identifier - @author = comment.author - @issue_author_url = url_for(user_activities_url(@author)) - message_id comment - @news = news - @comment = comment - @news_url = url_for(:controller => 'news', :action => 'show', :id => news) - mail :to => news.recipients, - :cc => news.watcher_recipients, - :subject => "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}" - elsif news.course - redmine_headers 'Course' => news.course.id - @author = comment.author - @issue_author_url = url_for(user_activities_url(@author)) - message_id comment - @news = news - @comment = comment - @news_url = url_for(:controller => 'news', :action => 'show', :id => news) - recipients = news.course.notified_users.select { |user| user.allowed_to?(:view_files, news.course) }.collect { |u| u.mail } - - mail :to => recipients, - :subject => "[#{news.course.name}] #{l(:label_news)}: #{news.title}" - end - end - - # Builds a Mail::Message object used to email the recipients of the specified message that was posted. - # - # Example: - # message_posted(message) => Mail::Message object - # Mailer.message_posted(message).deliver => sends an email to the recipients - def message_posted(message) - if message.project - redmine_headers 'Project' => message.project.identifier, - 'Topic-Id' => (message.parent_id || message.id) - @author = message.author - @issue_author_url = url_for(user_activities_url(@author)) - message_id message - references message.parent unless message.parent.nil? - recipients = message.recipients - cc = ((message.root.watcher_recipients + message.board.watcher_recipients).uniq - recipients) - @message = message - @message_url = url_for(message.event_url) - mail :to => recipients, - :cc => cc, - :subject => "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}" - elsif message.course - redmine_headers 'Course' => message.course.id, - 'Topic-Id' => (message.parent_id || message.id) - @author = message.author - @issue_author_url = url_for(user_activities_url(@author)) - message_id message - references message.parent unless message.parent.nil? - recipients = message.course.notified_users.select { |user| user.allowed_to?(:view_files, message.course) }.collect { |u| u.mail } - cc = ((message.root.watcher_recipients + message.board.watcher_recipients).uniq - recipients) - @message = message - @message_url = url_for(message.event_url) - mail :to => recipients, - :cc => cc, - :subject => "[#{message.board.course.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}" - end - end - - # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was added. - # - # Example: - # wiki_content_added(wiki_content) => Mail::Message object - # Mailer.wiki_content_added(wiki_content).deliver => sends an email to the project's recipients - def wiki_content_added(wiki_content) - redmine_headers 'Project' => wiki_content.project.identifier, - 'Wiki-Page-Id' => wiki_content.page.id - @author = wiki_content.author - message_id wiki_content - recipients = wiki_content.recipients - cc = wiki_content.page.wiki.watcher_recipients - recipients - @wiki_content = wiki_content - @wiki_content_url = url_for(:controller => 'wiki', :action => 'show', - :project_id => wiki_content.project, - :id => wiki_content.page.title) - mail :to => recipients, - :cc => cc, - :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}" - end - - # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was updated. - # - # Example: - # wiki_content_updated(wiki_content) => Mail::Message object - # Mailer.wiki_content_updated(wiki_content).deliver => sends an email to the project's recipients - def wiki_content_updated(wiki_content) - redmine_headers 'Project' => wiki_content.project.identifier, - 'Wiki-Page-Id' => wiki_content.page.id - @author = wiki_content.author - message_id wiki_content - recipients = wiki_content.recipients - cc = wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients - @wiki_content = wiki_content - @wiki_content_url = url_for(:controller => 'wiki', :action => 'show', - :project_id => wiki_content.project, - :id => wiki_content.page.title) - @wiki_diff_url = url_for(:controller => 'wiki', :action => 'diff', - :project_id => wiki_content.project, :id => wiki_content.page.title, - :version => wiki_content.version) - mail :to => recipients, - :cc => cc, - :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}" - end - - # Builds a Mail::Message object used to email the specified user their account information. - # - # Example: - # account_information(user, password) => Mail::Message object - # Mailer.account_information(user, password).deliver => sends account information to the user - def account_information(user, password) - set_language_if_valid user.language - @user = user - @password = password - @login_url = url_for(:controller => 'account', :action => 'login') - mail :to => user.mail, - :subject => l(:mail_subject_register, Setting.app_title) - end - - # Builds a Mail::Message object used to email all active administrators of an account activation request. - # - # Example: - # account_activation_request(user) => Mail::Message object - # Mailer.account_activation_request(user).deliver => sends an email to all active administrators - def account_activation_request(user) - # Send the email to all active administrators - recipients = User.active.where(:admin => true).all.collect { |u| u.mail }.compact - @user = user - @url = url_for(:controller => 'users', :action => 'index', - :status => User::STATUS_REGISTERED, - :sort_key => 'created_on', :sort_order => 'desc') - mail :to => recipients, - :subject => l(:mail_subject_account_activation_request, Setting.app_title) - end - - # Builds a Mail::Message object used to email the specified user that their account was activated by an administrator. - # - # Example: - # account_activated(user) => Mail::Message object - # Mailer.account_activated(user).deliver => sends an email to the registered user - def account_activated(user) - set_language_if_valid user.language - @user = user - @login_url = url_for(:controller => 'account', :action => 'login') - mail :to => user.mail, - :subject => l(:mail_subject_register, Setting.app_title) - end - - def lost_password(token) - set_language_if_valid(token.user.language) - @token = token - @url = url_for(:controller => 'account', :action => 'lost_password', :token => token.value) - mail :to => token.user.mail, - :subject => l(:mail_subject_lost_password, Setting.app_title) - end - - def register(token) - set_language_if_valid(token.user.language) - @token = token - @url = url_for(:controller => 'account', :action => 'activate', :token => token.value) - mail :to => token.user.mail, - :subject => l(:mail_subject_register, Setting.app_title) - end - - def test_email(user) - set_language_if_valid(user.language) - @url = url_for(:controller => 'welcome') - mail :to => user.mail, - :subject => 'forge test' - end - - # Overrides default deliver! method to prevent from sending an email - # with no recipient, cc or bcc - def deliver!(mail = @mail) - set_language_if_valid @initial_language - return false if (recipients.nil? || recipients.empty?) && - (cc.nil? || cc.empty?) && - (bcc.nil? || bcc.empty?) - - - # Log errors when raise_delivery_errors is set to false, Rails does not - raise_errors = self.class.raise_delivery_errors - self.class.raise_delivery_errors = true - begin - return super(mail) - rescue Exception => e - if raise_errors - raise e - elsif mylogger - mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml." - end - ensure - self.class.raise_delivery_errors = raise_errors - end - end - - # Sends reminders to issue assignees - # Available options: - # * :days => how many days in the future to remind about (defaults to 7) - # * :tracker => id of tracker for filtering issues (defaults to all trackers) - # * :project => id or identifier of project to process (defaults to all projects) - # * :users => array of user/group ids who should be reminded - def self.reminders(options={}) - days = options[:days] || 7 - project = options[:project] ? Project.find(options[:project]) : nil - tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil - user_ids = options[:users] - - scope = Issue.open.where("#{Issue.table_name}.assigned_to_id IS NOT NULL" + - " AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}" + - " AND #{Issue.table_name}.due_date <= ?", days.day.from_now.to_date - ) - scope = scope.where(:assigned_to_id => user_ids) if user_ids.present? - scope = scope.where(:project_id => project.id) if project - scope = scope.where(:tracker_id => tracker.id) if tracker - - issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker).all.group_by(&:assigned_to) - issues_by_assignee.keys.each do |assignee| - if assignee.is_a?(Group) - assignee.users.each do |user| - issues_by_assignee[user] ||= [] - issues_by_assignee[user] += issues_by_assignee[assignee] - end - end - end - - issues_by_assignee.each do |assignee, issues| - reminder(assignee, issues, days).deliver if assignee.is_a?(User) && assignee.active? - end - end - - # Activates/desactivates email deliveries during +block+ - def self.with_deliveries(enabled = true, &block) - was_enabled = ActionMailer::Base.perform_deliveries - ActionMailer::Base.perform_deliveries = !!enabled - yield - ensure - ActionMailer::Base.perform_deliveries = was_enabled - end - - # Sends emails synchronously in the given block - def self.with_synched_deliveries(&block) - saved_method = ActionMailer::Base.delivery_method - if m = saved_method.to_s.match(%r{^async_(.+)$}) - synched_method = m[1] - ActionMailer::Base.delivery_method = synched_method.to_sym - ActionMailer::Base.send "#{synched_method}_settings=", ActionMailer::Base.send("async_#{synched_method}_settings") - end - yield - ensure - ActionMailer::Base.delivery_method = saved_method - end - - def mail(headers={}) - headers.merge! 'X-Mailer' => 'Redmine', - 'X-Redmine-Host' => Setting.host_name, - 'X-Redmine-Site' => Setting.app_title, - 'X-Auto-Response-Suppress' => 'OOF', - 'Auto-Submitted' => 'auto-generated', - 'From' => Setting.mail_from, - 'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>" - - # Removes the author from the recipients and cc - # if he doesn't want to receive notifications about what he does - if @author && @author.logged? && @author.pref[:no_self_notified] - headers[:to].delete(@author.mail) if headers[:to].is_a?(Array) - headers[:cc].delete(@author.mail) if headers[:cc].is_a?(Array) - end - - if @author && @author.logged? - redmine_headers 'Sender' => @author.login - end - - # Blind carbon copy recipients - if Setting.bcc_recipients? - headers[:bcc] = [headers[:to], headers[:cc]].flatten.uniq.reject(&:blank?) - headers[:to] = nil - headers[:cc] = nil - end - - if @message_id_object - headers[:message_id] = "<#{self.class.message_id_for(@message_id_object)}>" - end - if @references_objects - headers[:references] = @references_objects.collect {|o| "<#{self.class.message_id_for(o)}>"}.join(' ') - end - - super headers do |format| - format.text - format.html unless Setting.plain_text_mail? - end - - set_language_if_valid @initial_language - end - - def initialize(*args) - @initial_language = current_language - set_language_if_valid Setting.default_language - super - end - - def self.deliver_mail(mail) - return false if mail.to.blank? && mail.cc.blank? && mail.bcc.blank? - Thread.new do - super - end - end - - def self.method_missing(method, *args, &block) - if m = method.to_s.match(%r{^deliver_(.+)$}) - ActiveSupport::Deprecation.warn "Mailer.deliver_#{m[1]}(*args) is deprecated. Use Mailer.#{m[1]}(*args).deliver instead." - send(m[1], *args).deliver - else - super - end - end - - - - private - - # Appends a Redmine header field (name is prepended with 'X-Redmine-') - def redmine_headers(h) - h.each { |k,v| headers["X-Redmine-#{k}"] = v.to_s } - end - - # Returns a predictable Message-Id for the given object - def self.message_id_for(object) - # id + timestamp should reduce the odds of a collision - # as far as we don't send multiple emails for the same object - timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on) - hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.strftime("%Y%m%d%H%M%S")}" - host = Setting.mail_from.to_s.gsub(%r{^.*@}, '') - host = "#{::Socket.gethostname}.redmine" if host.empty? - "#{hash}@#{host}" - end - - def message_id(object) - @message_id_object = object - end - - def references(object) - @references_objects ||= [] - @references_objects << object - end - - def mylogger - Rails.logger - end - - def add_attachments(obj) - if email.attachments && email.attachments.any? - email.attachments.each do |attachment| - obj.attachments << Attachment.create(:container => obj, - :file => attachment.decoded, - :filename => attachment.filename, - :author => user, - :content_type => attachment.mime_type) - end - end - end - - # author: alan - # 功能: 生成len位随机字符串 - def newpass(len) - chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a - newpass = "" - 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] } - return newpass - end -end +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Mailer < ActionMailer::Base + layout 'mailer' + helper :application + helper :issues + helper :custom_fields + + include Redmine::I18n + include CoursesHelper + def self.default_url_options + { :host => Setting.host_name, :protocol => Setting.protocol } + end + + + class MailerProxy + def initialize(cls) + @target = cls + end + def method_missing(name, *args, &block) + if Setting.delayjob_enabled? && Object.const_defined?('Delayed') + @target.delay.send(name, *args, &block) + else + @target.send(name, *args, &block).deliver + end + end + end + + def self.run + MailerProxy.new(self) + end + + # author: alan + # 发送邀请未注册用户加入项目邮件 + # 功能: 在加入项目的同时自动注册用户 + def send_invite_in_project(email, project, invitor) + @email = email + @subject = "#{invitor.name} #{l(:label_invite_project)} #{project.name} " + @password = newpass(6) + + login = email + login = login.sub(/%40/,'@') + us = UsersService.new + # 自动激活用户 + user = us.register_auto(login, @email, @password) + + Member.create(:role_ids => [4], :user_id => user.id,:project_id => project.id) + UserGrade.create(:user_id => user.id, :project_id => project.id) + User.current = user unless User.current.nil? + @user = user + @token = Token.get_token_from_user(user, 'autologin') + @project_url = url_for(:controller => 'projects', :action => 'show', :id => project.id,:user => user, :token => @token.value + ) + mail :to => email, :subject => @subject + end + + # 邀请已注册的用户加入项目 + def request_member_to_project(email, project, invitor) + @subject = "#{invitor.name} #{l(:label_invite_project)}: #{project.name} " + user = User.find_by_mail(email.to_s) + @invitor_name = "#{invitor.name}" + @project_name = "#{project.name}" + @user = user + @token = Token.get_token_from_user(user, 'autologin') + @project_url = url_for(:controller => 'projects', :action => 'show', :id => project.id, :email => email, :token => @token.value) + mail :to => email, :subject => @subject + end + + # author: alan + # 根据用户选择发送个人日报或周报 + # 发送内容: 项目【缺陷,讨论区,新闻】,课程【通知,留言,新闻】, 贴吧, 个人留言 + def send_for_user_activities(user, date_to, days) + date_from = date_to - days.days + + subject = "[ #{user.show_name}#{l(:label_day_mail)}]" + @subject = " #{user.show_name}#{l(:label_day_mail)}" + + date_from = "#{date_from} 17:59:59" + date_to = "#{date_to} 17:59:59" + + # 生成token用于直接点击登录 + @user = user + @token = Token.get_token_from_user(user, 'autologin') + + # 查询user参加的项目及课程 + projects = user.projects + courses = user.courses + project_ids = projects.map{|project| project.id}.join(",") + course_ids = courses.map {|course| course.id}.join(",") + + # 查询user的缺陷,包括发布的,跟踪的以及被指派的缺陷 + sql = "select DISTINCT i.* from issues i, watchers w + where (i.assigned_to_id = #{user.id} or i.author_id = #{user.id} + or (w.watchable_type = 'Issue' and w.watchable_id = i.id and w.user_id = #{user.id})) + and (i.created_on between '#{date_from}' and '#{date_to}') order by i.created_on desc" + @issues = Issue.find_by_sql(sql) + + # @bids 查询课程作业,包括老师发布的作业,以及user提交作业 + # @attachments查询课程课件更新 + @attachments ||= [] + + @bids ||= [] # 老师发布的作业 + + unless courses.first.nil? + count = courses.count + count = count - 1 + for i in 0..count do + bids = courses[i].homeworks.where("bids.created_on between '#{date_from}' and '#{date_to}'").order("bids.created_on desc") + attachments = courses[i].attachments.where("attachments.created_on between '#{date_from}' and '#{date_to}'").order('attachments.created_on DESC') + @bids += bids if bids.count > 0 + @attachments += attachments if attachments.count > 0 + end + end + # user 提交的作业 + @homeworks = HomeworkAttach.where("user_id=#{user.id} and (created_at between '#{date_from}' and '#{date_to}')").order("created_at desc") + + # 查询user在课程。项目中发布的讨论帖子 + messages = Message.find_by_sql("select DISTINCT * from messages where author_id = #{user.id} and (created_on between '#{date_from}' and '#{date_to}') order by created_on desc") + @course_messages ||= [] + @project_messages ||= [] + unless messages.first.nil? + messages.each do |msg| + if msg.project + @project_messages << msg + elsif msg.course + @course_messages << msg + end + end + end + # 查询user在课程中发布的通知,项目中发的新闻 + @course_news = (course_ids && !course_ids.empty?) ? News.find_by_sql("select DISTINCT n.* from news n + where n.course_id in (#{course_ids}) + and (created_on between '#{date_from}' and '#{date_to}') order by created_on desc") : [] + @project_news = (project_ids && !project_ids.empty?) ? News.find_by_sql("select DISTINCT n.* from news n where n.project_id in (#{project_ids}) + and (created_on between '#{date_from}' and '#{date_to}') order by created_on desc") : [] + + # 查询user在课程及个人中留言 + @course_journal_messages = JournalsForMessage.find_by_sql("select DISTINCT * from journals_for_messages where + jour_type='Course' and user_id = #{user.id} + and (created_on between '#{date_from}' and '#{date_to}') order by created_on desc") + @user_journal_messages = user.journals_for_messages.where("m_parent_id IS NULL and (created_on between '#{date_from}' and '#{date_to}')").order('created_on DESC') + + + # 查询user新建贴吧或发布帖子 + @forums = Forum.find_by_sql("select DISTINCT * from forums where creator_id = #{user.id} and (created_at between '#{date_from}' and '#{date_to}') order by created_at desc") + @memos = Memo.find_by_sql("select DISTINCT m.* from memos m, forums f where (m.author_id = #{user.id} or (m.forum_id = f.id and f.creator_id = #{user.id})) + and (m.created_at between '#{date_from}' and '#{date_to}') order by m.created_at desc") + + + has_content = [@issues,@homeworks,@course_messages,@project_messages,@course_news,@project_news, + @course_journal_messages,@user_journal_messages,@forums,@memos,@attachments,@bids].any? {|o| + !o.empty? + } + mylogger.debug "Sent activity mail : #{user.mail} - #{has_content}" + #有内容才发,没有不发 + mail :to => user.mail,:subject => subject if has_content + end + + # 公共讨论区发帖、回帖添加邮件发送信息 + def forum_message_added(memo) + @memo = memo + redmine_headers 'Memo' => memo.id + @forum = memo.forum + @author = memo.author + @forum_url = url_for(:controller => 'forums', :action => 'show', :id => @forum.id) + @issue_author_url = url_for(user_activities_url(@author)) + recipients ||= [] + #将帖子创建者邮箱地址加入数组 + recipients << @forum.creator.mail + #回复人邮箱地址加入数组 + recipients << @author.mail + # cc = wiki_content.page.wiki.watcher_recipients - recipients + + @memo_url = url_for(forum_memo_url(@forum, (@memo.parent_id.nil? ? @memo : @memo.parent_id))) + mail :to => recipients, + :subject => "[ #{l(:label_message_plural)} : #{memo.subject} #{l(:label_memo_create_succ)}]", + :filter => true + end + # Builds a Mail::Message object used to email recipients of the added journals for message. + + # 留言分为直接留言,和对留言人留言的回复 + # 字段说明在JournalsForMessage.rb + # 直接留言后 reply_id,m_parent_id 为空,相对应的at_user取值为nil + + def journals_for_message_add(user, journals_for_message) + @user = journals_for_message.user # 留言人 + @mail = journals_for_message.jour if journals_for_message.at_user.nil? # 留言 + @mail = journals_for_message.at_user if journals_for_message.at_user + @message = journals_for_message.notes + @title = "#@user #{t(:label_leave_your_message, :locale => 'zh')}" + @issue_author_url = url_for(user_activities_url(@user)) + @url = case journals_for_message.jour.class.to_s.to_sym # 判断留言的对象所属类型 + when :Bid + course_for_bid_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") + when :Project + return -1 if journals_for_message.jour.project_type == Project::ProjectType_project + project_feedback_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") + when :Course + course_feedback_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") + when :Contest + show_contest_contest_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") + when :User + user_newfeedback_user_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") + else + Rails.logger.error "[Builds a Mail::Message ERROR] journalsForMessage's jour is unkown type, journalsForMessage.id = #{journals_for_message.id}" + return -1 + end + + # 验证用户的收取邮件的方式 + recipients ||= [] + recipients1 ||= [] + recipients1 = @mail.mail + recipients = journals_for_message.jour.author.mail + + # modify by nwb + #如果是直接留言并且留言对象是课程 + if !journals_for_message.at_user && journals_for_message.jour.class.to_s.to_sym == :Course + course = journals_for_message.jour + @author = journals_for_message.user + #课程的教师 + @members = course_all_member journals_for_message.jour + #收件人邮箱 + @recipients ||= [] + @members.each do |teacher| + @recipients << teacher.user.mail + end + mail :to => @recipients, + :subject => "#{l(:label_your_course)}#{journals_for_message.jour.name}#{l(:label_have_message)} ", + :filter => true + elsif journals_for_message.jour.class.to_s.to_sym == :Bid + if !journals_for_message.jour.author.notify_about? journals_for_message + return -1 + end + + mail :to => recipients, :subject => @title,:filter => true + elsif journals_for_message.jour.class.to_s.to_sym == :Contest + if !journals_for_message.jour.author.notify_about? journals_for_message + return -1 + end + mail :to => recipients, :subject => @title,:filter => true + else + mail :to => recipients1, :subject => @title,:filter => true + end + + + end + + # Builds a Mail::Message object used to email recipients of the added issue. + # + # Example: + # issue_add(issue) => Mail::Message object + # Mailer.issue_add(issue).deliver => sends an email to issue recipients + def issue_add(issue, recipients) + issue_id = issue.project_index + redmine_headers 'Project' => issue.project.identifier, + 'Issue-Id' => issue_id, + 'Issue-Author' => issue.author.login + redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to + message_id issue + + @author = issue.author + @issue = issue + user = User.find_by_mail(recipients) + @user = user + @token = Token.get_token_from_user(user, 'autologin') + @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue.id, :token => @token.value) + + # edit + @issue_author_url = url_for(user_activities_url(@author,:token => @token.value)) + @project_url = url_for(:controller => 'projects', :action => 'show', :id => issue.project_id, :token => @token.value) + + @user_url = url_for(my_account_url(user,:token => @token.value)) + + + subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue_id}] (#{issue.status.name}) #{issue.subject}" + mail :to => recipients, + :subject => subject, + :filter => true + end + # issue.attachments.each do |attach| + # attachments["#{attach.filename}"] = File.read("#{attach.disk_filename}") + # end + # cc = issue.watcher_recipients - recipients + #mail.attachments['test'] = File.read("#{RAILS.root}/files/2015/01/150114094010_libegl.dll") + + + + + # Builds a Mail::Message object used to email recipients of the edited issue. + # + # Example: + # issue_edit(journal) => Mail::Message object + # Mailer.issue_edit(journal).deliver => sends an email to issue recipients + def issue_edit(journal,recipients) + issue = journal.journalized.reload + issue_id = issue.project_index + redmine_headers 'Project' => issue.project.identifier, + 'Issue-Id' => issue_id.to_s, + 'Issue-Author' => issue.author.login + redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to + message_id journal + references issue + @author = journal.user + + user = User.find_by_mail(recipients) + @user = user + @token = Token.get_token_from_user(user, 'autologin') + + + # edit + @issue_author_url = url_for(:controller => 'users', :action => 'show', :id => issue.author_id, :token => @token.value) + @project_url = url_for(:controller => 'projects', :action => 'show', :id => issue.project_id, :token => @token.value) + @user_url = url_for(my_account_url(user,:token => @token.value)) + + @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue.id, :anchor => "change-#{journal.id}", :token => @token.value) + s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue_id}] " + s << "(#{issue.status.name}) " if journal.new_value_for('status_id') + s << issue.subject + @issue = issue + @journal = journal + # @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}") + mail :to => recipients, + :subject => s, + :filter => true + end + + def self.deliver_mailer(to,cc, subject) + mail :to => to, + :cc => cc, + :subject => subject + end + + # 用户申请加入项目邮件通知 + def applied_project(applied) + @project =applied.project + redmine_headers 'Project' => @project, + 'User' => applied.user + @user = applied.user + recipients = @project.manager_recipients + s = l(:text_applied_project, :id => "##{@user.show_name}", :project => @project.name) + @applied_url = url_for(:controller => 'projects', :action => 'settings', :id => @project.id,:tab=>'members') + mail :to => recipients, + :subject => s + end + + def reminder(user, issues, days) + set_language_if_valid user.language + @issues = issues + @days = days + @issues_url = url_for(:controller => 'issues', :action => 'index', + :set_filter => 1, :assigned_to_id => user.id, + :sort => 'due_date:asc') + mail :to => user.mail, + :subject => l(:mail_subject_reminder, :count => issues.size, :days => days) + end + + #缺陷到期邮件通知 + def issue_expire issue + issue_id = issue.project_index + redmine_headers 'Project' => issue.project.identifier, + 'Issue-Id' => issue_id, + 'Issue-Author' => issue.author.login + redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to + message_id issue + @author = issue.author + @issue = issue + @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue.id) + recipients = issue.recipients + s = l(:text_issue_expire,:issue => "[#{issue.project.name} - #{issue.tracker.name} ##{issue_id}] (#{issue.status.name}) #{issue.subject}") + mail :to => recipients, + :subject => s + ######################################################################################################### + #@issues = issues + #s = l(:text_issue_expire,:issue => "[#{issue.project.name} - #{issue.tracker.name} ##{issue_id}] (#{issue.status.name}) #{issue.subject}") + #puts s + "////" + issue.assigned_to.mail + #@issues_url = url_for(:controller => 'issues', :action => 'show',:id => issue.id) + #mail :to => issue.assigned_to.mail, + # :subject => s + ######################################################################################################### + #issue_id = issue.project_index + #redmine_headers 'Project' => issue.project.identifier, + # 'Issue-Id' => issue_id, + # 'Issue-Author' => issue.author.login + #redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to + #message_id issue + #@author = issue.author + #@issue = issue + #@issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue) + #recipients = issue.recipients + #cc = issue.watcher_recipients - recipients + #mail :to => recipients, + # :cc => cc, + # :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue_id}] (#{issue.status.name}) #{issue.subject}" + ###################################################################################################### + end + + + # Builds a Mail::Message object used to email users belonging to the added document's project. + # + # Example: + # document_added(document) => Mail::Message object + # Mailer.document_added(document).deliver => sends an email to the document's project recipients + def document_added(document) + redmine_headers 'Project' => document.project.identifier + @author = User.current + @document = document + @issue_author_url = url_for(user_activities_url(@author)) + @document_url = url_for(:controller => 'documents', :action => 'show', :id => document) + mail :to => document.recipients, + :subject => "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}", + :filter => true + end + + # Builds a Mail::Message object used to email recipients of a project when an attachements are added. + # + # Example: + # attachments_added(attachments) => Mail::Message object + # Mailer.attachments_added(attachments).deliver => sends an email to the project's recipients + def attachments_added(attachments) + container = attachments.first.container + added_to = '' + added_to_url = '' + @author = attachments.first.author + @issue_author_url = url_for(user_activities_url(@author)) + case container.class.name + when 'Project' + added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container) + added_to = "#{l(:label_project)}: #{container}" + recipients = container.notified_users.select { |user| user.allowed_to?(:view_files, container) }.collect { |u| u.mail } + when 'Course' + added_to_url = url_for(:controller => 'files', :action => 'index', :course_id => container) + added_to = "#{l(:label_course)}: #{container.name}" + recipients = container.notified_users.select { |user| user.allowed_to?(:view_files, container) }.collect { |u| u.mail } + when 'Version' + added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project) + added_to = "#{l(:label_version)}: #{container.name}" + recipients = container.project.notified_users.select { |user| user.allowed_to?(:view_files, container.project) }.collect { |u| u.mail } + when 'Document' + added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id) + added_to = "#{l(:label_document)}: #{container.title}" + recipients = container.recipients + end + if container.class.name == 'Course' + redmine_headers 'Course' => container.id + @attachments = attachments + @added_to = added_to + @added_to_url = added_to_url + mail :to => recipients, + :subject => "[#{container.name}] #{l(:label_attachment_new)}", + :filter => true + elsif container.class.name == 'Project' + redmine_headers 'Project' => container.id + @attachments = attachments + @added_to = added_to + @added_to_url = added_to_url + mail :to => recipients, + :subject => "[#{container.name}] #{l(:label_attachment_new)}", + :filter => true + else + redmine_headers 'Project' => container.project.identifier + @attachments = attachments + @added_to = added_to + @added_to_url = added_to_url + mail :to => recipients, + :subject => "[#{container.project.name}] #{l(:label_attachment_new)}", + :filter => true + end + end + + # Builds a Mail::Message object used to email recipients of a news' project when a news item is added. + # + # Example: + # news_added(news) => Mail::Message object + # Mailer.news_added(news).deliver => sends an email to the news' project recipients + def news_added(news) + + if news.project + redmine_headers 'Project' => news.project.identifier + @author = news.author + @issue_author_url = url_for(user_activities_url(@author)) + message_id news + @news = news + @news_url = url_for(:controller => 'news', :action => 'show', :id => news) + mail :to => news.recipients, + :subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}", + :filter => true + elsif news.course + redmine_headers 'Course' => news.course.id + @author = news.author + @issue_author_url = url_for(user_activities_url(@author)) + message_id news + @news = news + recipients = news.course.notified_users.select { |user| user.allowed_to?(:view_files, news.course) }.collect { |u| u.mail } + @news_url = url_for(:controller => 'news', :action => 'show', :id => news) + mail :to => recipients, + :subject => "[#{news.course.name}] #{l(:label_news)}: #{news.title}", + :filter => true + end + end + + # Builds a Mail::Message object used to email recipients of a news' project when a news comment is added. + # + # Example: + # news_comment_added(comment) => Mail::Message object + # Mailer.news_comment_added(comment) => sends an email to the news' project recipients + def news_comment_added(comment) + news = comment.commented + if news.project + redmine_headers 'Project' => news.project.identifier + @author = comment.author + @issue_author_url = url_for(user_activities_url(@author)) + message_id comment + @news = news + @comment = comment + @news_url = url_for(:controller => 'news', :action => 'show', :id => news) + mail :to => news.recipients, + :cc => news.watcher_recipients, + :subject => "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}", + :filter => true + elsif news.course + redmine_headers 'Course' => news.course.id + @author = comment.author + @issue_author_url = url_for(user_activities_url(@author)) + message_id comment + @news = news + @comment = comment + @news_url = url_for(:controller => 'news', :action => 'show', :id => news) + recipients = news.course.notified_users.select { |user| user.allowed_to?(:view_files, news.course) }.collect { |u| u.mail } + + mail :to => recipients, + :subject => "[#{news.course.name}] #{l(:label_news)}: #{news.title}", + :filter => true + end + end + + # Builds a Mail::Message object used to email the recipients of the specified message that was posted. + # + # Example: + # message_posted(message) => Mail::Message object + # Mailer.message_posted(message).deliver => sends an email to the recipients + def message_posted(message) + if message.project + redmine_headers 'Project' => message.project.identifier, + 'Topic-Id' => (message.parent_id || message.id) + @author = message.author + @issue_author_url = url_for(user_activities_url(@author)) + message_id message + references message.parent unless message.parent.nil? + recipients = message.recipients + cc = ((message.root.watcher_recipients + message.board.watcher_recipients).uniq - recipients) + @message = message + @message_url = url_for(message.event_url) + mail :to => recipients, + :cc => cc, + :subject => "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}", + :filter => true + elsif message.course + redmine_headers 'Course' => message.course.id, + 'Topic-Id' => (message.parent_id || message.id) + @author = message.author + @issue_author_url = url_for(user_activities_url(@author)) + message_id message + references message.parent unless message.parent.nil? + recipients = message.course.notified_users.select { |user| user.allowed_to?(:view_files, message.course) }.collect { |u| u.mail } + cc = ((message.root.watcher_recipients + message.board.watcher_recipients).uniq - recipients) + @message = message + @message_url = url_for(message.event_url) + mail :to => recipients, + :cc => cc, + :subject => "[#{message.board.course.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}", + :filter => true + end + end + + # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was added. + # + # Example: + # wiki_content_added(wiki_content) => Mail::Message object + # Mailer.wiki_content_added(wiki_content).deliver => sends an email to the project's recipients + def wiki_content_added(wiki_content) + redmine_headers 'Project' => wiki_content.project.identifier, + 'Wiki-Page-Id' => wiki_content.page.id + @author = wiki_content.author + message_id wiki_content + recipients = wiki_content.recipients + cc = wiki_content.page.wiki.watcher_recipients - recipients + @wiki_content = wiki_content + @wiki_content_url = url_for(:controller => 'wiki', :action => 'show', + :project_id => wiki_content.project, + :id => wiki_content.page.title) + mail :to => recipients, + :cc => cc, + :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}", + :filter => true + end + + # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was updated. + # + # Example: + # wiki_content_updated(wiki_content) => Mail::Message object + # Mailer.wiki_content_updated(wiki_content).deliver => sends an email to the project's recipients + def wiki_content_updated(wiki_content) + redmine_headers 'Project' => wiki_content.project.identifier, + 'Wiki-Page-Id' => wiki_content.page.id + @author = wiki_content.author + message_id wiki_content + recipients = wiki_content.recipients + cc = wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients + @wiki_content = wiki_content + @wiki_content_url = url_for(:controller => 'wiki', :action => 'show', + :project_id => wiki_content.project, + :id => wiki_content.page.title) + @wiki_diff_url = url_for(:controller => 'wiki', :action => 'diff', + :project_id => wiki_content.project, :id => wiki_content.page.title, + :version => wiki_content.version) + mail :to => recipients, + :cc => cc, + :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}", + :filter => true + end + + # Builds a Mail::Message object used to email the specified user their account information. + # + # Example: + # account_information(user, password) => Mail::Message object + # Mailer.account_information(user, password).deliver => sends account information to the user + def account_information(user, password) + set_language_if_valid user.language + @user = user + @password = password + @login_url = url_for(:controller => 'account', :action => 'login') + mail :to => user.mail, + :subject => l(:mail_subject_register, Setting.app_title) + end + + # Builds a Mail::Message object used to email all active administrators of an account activation request. + # + # Example: + # account_activation_request(user) => Mail::Message object + # Mailer.account_activation_request(user).deliver => sends an email to all active administrators + def account_activation_request(user) + # Send the email to all active administrators + recipients = User.active.where(:admin => true).all.collect { |u| u.mail }.compact + @user = user + @url = url_for(:controller => 'users', :action => 'index', + :status => User::STATUS_REGISTERED, + :sort_key => 'created_on', :sort_order => 'desc') + mail :to => recipients, + :subject => l(:mail_subject_account_activation_request, Setting.app_title) + end + + # Builds a Mail::Message object used to email the specified user that their account was activated by an administrator. + # + # Example: + # account_activated(user) => Mail::Message object + # Mailer.account_activated(user).deliver => sends an email to the registered user + def account_activated(user) + set_language_if_valid user.language + @user = user + @login_url = url_for(:controller => 'account', :action => 'login') + mail :to => user.mail, + :subject => l(:mail_subject_register, Setting.app_title) + end + + def lost_password(token) + set_language_if_valid(token.user.language) + @token = token + @url = url_for(:controller => 'account', :action => 'lost_password', :token => token.value) + mail :to => token.user.mail, + :subject => l(:mail_subject_lost_password, Setting.app_title) + end + + def register(token) + set_language_if_valid(token.user.language) + @token = token + @url = url_for(:controller => 'account', :action => 'activate', :token => token.value) + mail :to => token.user.mail, + :subject => l(:mail_subject_register, Setting.app_title) + end + + def test_email(user) + set_language_if_valid(user.language) + @url = url_for(:controller => 'welcome') + mail :to => user.mail, + :subject => 'forge test' + end + + # Overrides default deliver! method to prevent from sending an email + # with no recipient, cc or bcc + def deliver!(mail = @mail) + set_language_if_valid @initial_language + return false if (recipients.nil? || recipients.empty?) && + (cc.nil? || cc.empty?) && + (bcc.nil? || bcc.empty?) + + + # Log errors when raise_delivery_errors is set to false, Rails does not + raise_errors = self.class.raise_delivery_errors + self.class.raise_delivery_errors = true + begin + return super(mail) + rescue Exception => e + if raise_errors + raise e + elsif mylogger + mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml." + end + ensure + self.class.raise_delivery_errors = raise_errors + end + end + + # Sends reminders to issue assignees + # Available options: + # * :days => how many days in the future to remind about (defaults to 7) + # * :tracker => id of tracker for filtering issues (defaults to all trackers) + # * :project => id or identifier of project to process (defaults to all projects) + # * :users => array of user/group ids who should be reminded + def self.reminders(options={}) + days = options[:days] || 7 + project = options[:project] ? Project.find(options[:project]) : nil + tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil + user_ids = options[:users] + + scope = Issue.open.where("#{Issue.table_name}.assigned_to_id IS NOT NULL" + + " AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}" + + " AND #{Issue.table_name}.due_date <= ?", days.day.from_now.to_date + ) + scope = scope.where(:assigned_to_id => user_ids) if user_ids.present? + scope = scope.where(:project_id => project.id) if project + scope = scope.where(:tracker_id => tracker.id) if tracker + + issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker).all.group_by(&:assigned_to) + issues_by_assignee.keys.each do |assignee| + if assignee.is_a?(Group) + assignee.users.each do |user| + issues_by_assignee[user] ||= [] + issues_by_assignee[user] += issues_by_assignee[assignee] + end + end + end + + issues_by_assignee.each do |assignee, issues| + reminder(assignee, issues, days).deliver if assignee.is_a?(User) && assignee.active? + end + end + + # Activates/desactivates email deliveries during +block+ + def self.with_deliveries(enabled = true, &block) + was_enabled = ActionMailer::Base.perform_deliveries + ActionMailer::Base.perform_deliveries = !!enabled + yield + ensure + ActionMailer::Base.perform_deliveries = was_enabled + end + + # Sends emails synchronously in the given block + def self.with_synched_deliveries(&block) + saved_method = ActionMailer::Base.delivery_method + if m = saved_method.to_s.match(%r{^async_(.+)$}) + synched_method = m[1] + ActionMailer::Base.delivery_method = synched_method.to_sym + ActionMailer::Base.send "#{synched_method}_settings=", ActionMailer::Base.send("async_#{synched_method}_settings") + end + yield + ensure + ActionMailer::Base.delivery_method = saved_method + end + + #过滤掉不是不合规则的收件人 + def filter(reps) + r_reps = [] + if reps.is_a? Array + reps.each do |r| + u = User.find_by_mail(r) + if u && u.mail_notification == 'all' + r_reps << r + end + end + elsif reps.is_a? String + u = User.find_by_mail(reps) + if u && u.mail_notification == 'all' + r_reps << reps + end + end + r_reps + end + + def mail(headers={}) + headers.merge! 'X-Mailer' => 'Redmine', + 'X-Redmine-Host' => Setting.host_name, + 'X-Redmine-Site' => Setting.app_title, + 'X-Auto-Response-Suppress' => 'OOF', + 'Auto-Submitted' => 'auto-generated', + 'From' => Setting.mail_from, + 'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>" + + # Removes the author from the recipients and cc + # if he doesn't want to receive notifications about what he does + if @author && @author.logged? && @author.pref[:no_self_notified] + headers[:to].delete(@author.mail) if headers[:to].is_a?(Array) + headers[:cc].delete(@author.mail) if headers[:cc].is_a?(Array) + end + + if headers[:filter] + headers[:to] = filter(headers[:to]) + headers[:cc] = filter(headers[:cc]) + end + + if @author && @author.logged? + redmine_headers 'Sender' => @author.login + end + + # Blind carbon copy recipients + if Setting.bcc_recipients? + headers[:bcc] = [headers[:to], headers[:cc]].flatten.uniq.reject(&:blank?) + headers[:to] = nil + headers[:cc] = nil + end + + if @message_id_object + headers[:message_id] = "<#{self.class.message_id_for(@message_id_object)}>" + end + if @references_objects + headers[:references] = @references_objects.collect {|o| "<#{self.class.message_id_for(o)}>"}.join(' ') + end + + set_language_if_valid @initial_language + m = super headers do |format| + format.text + format.html unless Setting.plain_text_mail? + end + mylogger.debug "Sent a mail from #{m.from} to [#{m.to},#{m.cc}, #{m.bcc if Setting.bcc_recipients?}] subject: #{m.subject}" + m + end + + def initialize(*args) + @initial_language = current_language + set_language_if_valid Setting.default_language + super + end + + def self.deliver_mail(mail) + return false if mail.to.blank? && mail.cc.blank? && mail.bcc.blank? + Thread.new do + super + end + end + + def self.method_missing(method, *args, &block) + if m = method.to_s.match(%r{^deliver_(.+)$}) + ActiveSupport::Deprecation.warn "Mailer.deliver_#{m[1]}(*args) is deprecated. Use Mailer.#{m[1]}(*args).deliver instead." + send(m[1], *args).deliver + else + super + end + end + + + + private + + # Appends a Redmine header field (name is prepended with 'X-Redmine-') + def redmine_headers(h) + h.each { |k,v| headers["X-Redmine-#{k}"] = v.to_s } + end + + # Returns a predictable Message-Id for the given object + def self.message_id_for(object) + # id + timestamp should reduce the odds of a collision + # as far as we don't send multiple emails for the same object + timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on) + hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.strftime("%Y%m%d%H%M%S")}" + host = Setting.mail_from.to_s.gsub(%r{^.*@}, '') + host = "#{::Socket.gethostname}.redmine" if host.empty? + "#{hash}@#{host}" + end + + def message_id(object) + @message_id_object = object + end + + def references(object) + @references_objects ||= [] + @references_objects << object + end + + def mylogger + if Setting.delayjob_enabled? + Delayed::Worker.logger + else + Rails.logger + end + end + + def add_attachments(obj) + if email.attachments && email.attachments.any? + email.attachments.each do |attachment| + obj.attachments << Attachment.create(:container => obj, + :file => attachment.decoded, + :filename => attachment.filename, + :author => user, + :content_type => attachment.mime_type) + end + end + end + + # author: alan + # 功能: 生成len位随机字符串 + def newpass(len) + chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + newpass = "" + 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] } + return newpass + end +end diff --git a/app/models/memo.rb b/app/models/memo.rb index eb0c86855..2831effe3 100644 --- a/app/models/memo.rb +++ b/app/models/memo.rb @@ -1,181 +1,179 @@ -class Memo < ActiveRecord::Base - include Redmine::SafeAttributes - include UserScoreHelper - include ApplicationHelper - belongs_to :forum - has_many_kindeditor_assets :assets, :dependent => :destroy - belongs_to :author, :class_name => "User", :foreign_key => 'author_id' - validates_presence_of :author_id, :forum_id, :subject,:content - # 若是主题帖,则内容可以是空 - #validates :content, presence: true, if: Proc.new{|o| !o.parent_id.nil? } - validates_length_of :subject, maximum: 50 - #validates_length_of :content, maximum: 3072 - validate :cannot_reply_to_locked_topic, :on => :create - - acts_as_tree :counter_cache => :replies_count, :order => "#{Memo.table_name}.created_at ASC" - acts_as_attachable - has_many :user_score_details, :class_name => 'UserScoreDetails',:as => :score_changeable_obj - has_many :praise_tread, as: :praise_tread_object, dependent: :destroy - belongs_to :last_reply, :class_name => 'Memo', :foreign_key => 'last_reply_id' - # acts_as_searchable :column => ['subject', 'content'], - # #:include => { :forum => :p} - # #:project_key => "#{Forum.table_name}.project_id" - # :date_column => "#{table_name}.created_at" - acts_as_event :title => Proc.new {|o| "#{o.forum.name}: #{o.subject}"}, - :datetime => :updated_at, - # :datetime => :created_at, - :description => :content, - :author => :author, - :type => Proc.new {|o| o.parent_id.nil? ? 'Memo' : 'Reply'}, - :url => Proc.new {|o| {:controller => 'memos', :action => 'show', :forum_id => o.forum_id}.merge(o.parent_id.nil? ? {:id => o.id} : {:id => o.parent_id, :r => o.id, :anchor => "reply-#{o.id}"})} - acts_as_activity_provider :author_key => :author_id, - :func => 'memos', - :timestamp => 'created_at' - # :find_options => {:type => 'memos'} - # acts_as_watchable - - safe_attributes "author_id", - "subject", - "content", - "forum_id", - "last_memo_id", - "lock", - "sticky", - "parent_id", - "replies_count" - - after_create :add_author_as_watcher, :reset_counters!, :sendmail - # after_update :update_memos_forum - after_destroy :reset_counters!,:delete_kindeditor_assets#,:down_user_score -- 公共区发帖暂不计入得分 - # after_create :send_notification - # after_save :plusParentAndForum - # after_destroy :minusParentAndForum - #before_save :be_user_score - # scope :visible, lambda { |*args| - # includes(:forum => ).where() - # } - - def sendmail - thread1=Thread.new do - Mailer.forum_message_added(self).deliver if Setting.notified_events.include?('forum_message_added') - end - end - - def cannot_reply_to_locked_topic - errors.add :base, l(:label_memo_locked) if root.locked? && self != root - end - - # def update_memos_forum - # if forum_id_changed? - # Message.update_all({:board_id => board_id}, ["id = ? OR parent_id = ?", root.id, root.id ]) - # Forum.reset_counters!(forum_id_was) - # Forum.reset_counters!(forum_id) - # end - # end - - def reset_counters! - if parent && parent.id - Memo.update_all({:last_reply_id => parent.children.maximum(:id)}, {:id => parent.id}) - parent.update_attribute(:updated_at, Time.now) - end - forum.reset_counters! - end - - def sticky? - sticky == 1 - end - - def replies - Memo.where("parent_id = ?", id) - end - - def locked? - self.lock - end - - def editable_by? user - # user && user.logged? || (self.author == usr && usr.allowed_to?(:edit_own_messages, project)) - user.admin? || self.author == user - end - - def destroyable_by? user - (user && self.author == user) || user.admin? - #self.author == user || user.admin? - end - - def deleted_attach_able_by? user - (user && user.logged? && (self.author == user) ) || user.admin? - end - - private - - def add_author_as_watcher - Watcher.create(:watchable => self.root, :user => author) - end - - def send_notification - if Setting.notified_events.include?('message_posted') - Mailer.message_posted(self).deliver - end - end - - def plusParentAndForum - @forum = Forum.find(self.forum_id) - @forum.memo_count = @forum.memo_count.to_int + 1 - @forum.last_memo_id = self.id - if self.parent_id - @parent_memo = Memo.find_by_id(self.parent_id) - @parent_memo.last_reply_id = self - @parent_memo.replies_count = @parent_memo.replies_count.to_int + 1 - @parent_memo.save - else - @forum.topic_count = @forum.topic_count.to_int + 1 - end - @forum.save - end - - def minusParentAndForum - @forum = Forum.find(self.forum_id) - @forum.memo_count = @forum.memo_count.to_int - 1 - @forum.memo_count = 0 if @forum.memo_count.to_int < 0 - # @forum.last_memo_id = Memo.reorder('created_at ASC').find_all_by_forum_id(self.forum_id).last.id - if self.parent_id - @parent_memo = Memo.find_by_id(self.parent_id) - # @parent_memo.last_reply_id = Memo.reorder('created_at ASC').find_all_by_parent_id(self.parent_id).last.id - @parent_memo.replies_count = @parent_memo.replies_count.to_int - 1 - @parent_memo.replies_count = 0 if @parent_memo.replies_count.to_int < 0 - @parent_memo.save - else - @forum.topic_count = @forum.topic_count.to_int - 1 - @forum.topic_count = 0 if @forum.topic_count.to_int < 0 - end - @forum.save - end - - #更新用户分数 -by zjc - def be_user_score - #新建memo且无parent的为发帖 - if self.parent_id.nil? - UserScore.joint(:post_message, User.current,nil,self ,{ memo_id: self.id }) - update_memo_number(User.current,1) - - #新建memo且有parent的为回帖 - elsif !self.parent_id.nil? - UserScore.joint(:reply_posting, User.current,self.parent.author,self, { memo_id: self.id }) - update_replay_for_memo(User.current,1) - end - end - - #被删除时更新用户分数 - def down_user_score - update_memo_number(User.current,1) - update_replay_for_memo(User.current,1) - end - - # Time 2015-03-26 15:20:24 - # Author lizanle - # Description 从硬盘上删除资源 - def delete_kindeditor_assets - delete_kindeditor_assets_from_disk self.id,1 - end -end +class Memo < ActiveRecord::Base + include Redmine::SafeAttributes + include UserScoreHelper + include ApplicationHelper + belongs_to :forum + has_many_kindeditor_assets :assets, :dependent => :destroy + belongs_to :author, :class_name => "User", :foreign_key => 'author_id' + validates_presence_of :author_id, :forum_id, :subject,:content + # 若是主题帖,则内容可以是空 + #validates :content, presence: true, if: Proc.new{|o| !o.parent_id.nil? } + validates_length_of :subject, maximum: 50 + #validates_length_of :content, maximum: 3072 + validate :cannot_reply_to_locked_topic, :on => :create + + acts_as_tree :counter_cache => :replies_count, :order => "#{Memo.table_name}.created_at ASC" + acts_as_attachable + has_many :user_score_details, :class_name => 'UserScoreDetails',:as => :score_changeable_obj + has_many :praise_tread, as: :praise_tread_object, dependent: :destroy + belongs_to :last_reply, :class_name => 'Memo', :foreign_key => 'last_reply_id' + # acts_as_searchable :column => ['subject', 'content'], + # #:include => { :forum => :p} + # #:project_key => "#{Forum.table_name}.project_id" + # :date_column => "#{table_name}.created_at" + acts_as_event :title => Proc.new {|o| "#{o.forum.name}: #{o.subject}"}, + :datetime => :updated_at, + # :datetime => :created_at, + :description => :content, + :author => :author, + :type => Proc.new {|o| o.parent_id.nil? ? 'Memo' : 'Reply'}, + :url => Proc.new {|o| {:controller => 'memos', :action => 'show', :forum_id => o.forum_id}.merge(o.parent_id.nil? ? {:id => o.id} : {:id => o.parent_id, :r => o.id, :anchor => "reply-#{o.id}"})} + acts_as_activity_provider :author_key => :author_id, + :func => 'memos', + :timestamp => 'created_at' + # :find_options => {:type => 'memos'} + # acts_as_watchable + + safe_attributes "author_id", + "subject", + "content", + "forum_id", + "last_memo_id", + "lock", + "sticky", + "parent_id", + "replies_count" + + after_create :add_author_as_watcher, :reset_counters!, :send_mail + # after_update :update_memos_forum + after_destroy :reset_counters!,:delete_kindeditor_assets#,:down_user_score -- 公共区发帖暂不计入得分 + # after_create :send_notification + # after_save :plusParentAndForum + # after_destroy :minusParentAndForum + #before_save :be_user_score + # scope :visible, lambda { |*args| + # includes(:forum => ).where() + # } + + def send_mail + Mailer.run.forum_message_added(self) if Setting.notified_events.include?('forum_message_added') + end + + def cannot_reply_to_locked_topic + errors.add :base, l(:label_memo_locked) if root.locked? && self != root + end + + # def update_memos_forum + # if forum_id_changed? + # Message.update_all({:board_id => board_id}, ["id = ? OR parent_id = ?", root.id, root.id ]) + # Forum.reset_counters!(forum_id_was) + # Forum.reset_counters!(forum_id) + # end + # end + + def reset_counters! + if parent && parent.id + Memo.update_all({:last_reply_id => parent.children.maximum(:id)}, {:id => parent.id}) + parent.update_attribute(:updated_at, Time.now) + end + forum.reset_counters! + end + + def sticky? + sticky == 1 + end + + def replies + Memo.where("parent_id = ?", id) + end + + def locked? + self.lock + end + + def editable_by? user + # user && user.logged? || (self.author == usr && usr.allowed_to?(:edit_own_messages, project)) + user.admin? || self.author == user + end + + def destroyable_by? user + (user && self.author == user) || user.admin? + #self.author == user || user.admin? + end + + def deleted_attach_able_by? user + (user && user.logged? && (self.author == user) ) || user.admin? + end + + private + + def add_author_as_watcher + Watcher.create(:watchable => self.root, :user => author) + end + + def send_notification + if Setting.notified_events.include?('message_posted') + Mailer.run.message_posted(self) + end + end + + def plusParentAndForum + @forum = Forum.find(self.forum_id) + @forum.memo_count = @forum.memo_count.to_int + 1 + @forum.last_memo_id = self.id + if self.parent_id + @parent_memo = Memo.find_by_id(self.parent_id) + @parent_memo.last_reply_id = self + @parent_memo.replies_count = @parent_memo.replies_count.to_int + 1 + @parent_memo.save + else + @forum.topic_count = @forum.topic_count.to_int + 1 + end + @forum.save + end + + def minusParentAndForum + @forum = Forum.find(self.forum_id) + @forum.memo_count = @forum.memo_count.to_int - 1 + @forum.memo_count = 0 if @forum.memo_count.to_int < 0 + # @forum.last_memo_id = Memo.reorder('created_at ASC').find_all_by_forum_id(self.forum_id).last.id + if self.parent_id + @parent_memo = Memo.find_by_id(self.parent_id) + # @parent_memo.last_reply_id = Memo.reorder('created_at ASC').find_all_by_parent_id(self.parent_id).last.id + @parent_memo.replies_count = @parent_memo.replies_count.to_int - 1 + @parent_memo.replies_count = 0 if @parent_memo.replies_count.to_int < 0 + @parent_memo.save + else + @forum.topic_count = @forum.topic_count.to_int - 1 + @forum.topic_count = 0 if @forum.topic_count.to_int < 0 + end + @forum.save + end + + #更新用户分数 -by zjc + def be_user_score + #新建memo且无parent的为发帖 + if self.parent_id.nil? + UserScore.joint(:post_message, User.current,nil,self ,{ memo_id: self.id }) + update_memo_number(User.current,1) + + #新建memo且有parent的为回帖 + elsif !self.parent_id.nil? + UserScore.joint(:reply_posting, User.current,self.parent.author,self, { memo_id: self.id }) + update_replay_for_memo(User.current,1) + end + end + + #被删除时更新用户分数 + def down_user_score + update_memo_number(User.current,1) + update_replay_for_memo(User.current,1) + end + + # Time 2015-03-26 15:20:24 + # Author lizanle + # Description 从硬盘上删除资源 + def delete_kindeditor_assets + delete_kindeditor_assets_from_disk self.id,OwnerTypeHelper::MEMO + end +end diff --git a/app/models/memo_observer.rb b/app/models/memo_observer.rb deleted file mode 100644 index 66cabe923..000000000 --- a/app/models/memo_observer.rb +++ /dev/null @@ -1,8 +0,0 @@ -class MemoObserver < ActiveRecord::Observer - def after_create(memo) - - thread1=Thread.new do - Mailer.forum_message_added(memo).deliver if Setting.notified_events.include?('forum_message_added') - end - end -end diff --git a/app/models/message.rb b/app/models/message.rb index 85a87132d..bbf62b5dc 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -1,212 +1,221 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class Message < ActiveRecord::Base - include Redmine::SafeAttributes - include UserScoreHelper - belongs_to :board - belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' - has_many :praise_tread, as: :praise_tread_object, dependent: :destroy - - acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC" - acts_as_attachable - belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id' - - # added by fq - has_many :acts, :class_name => 'Activity', :as => :act, :dependent => :destroy - # 被ForgeActivity虚拟关联 - has_many :forge_acts, :class_name => 'ForgeActivity',:as =>:forge_act ,:dependent => :destroy - # end - - acts_as_searchable :columns => ['subject', 'content'], - :include => {:board => :project}, - :project_key => "#{Board.table_name}.project_id", - :date_column => "#{table_name}.created_on" - acts_as_searchable :columns => ['subject', 'content'], - :include => {:board => :course}, - :course_key => "#{Board.table_name}.course_id", - :date_column => "#{table_name}.created_at" - acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, - :description => :content, - :datetime => :updated_on, - # :datetime => "#{Message.table_name}.created_on", - :group => :parent, - :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'}, - :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} : - {:id => o.parent_id, :r => o.id, :anchor => "message-#{o.id}"})} - - acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]}, - :author_key => :author_id - acts_as_activity_provider :find_options => {:include => [{:board => :course}, :author]}, - :type => 'course_messages', - :author_key => :author_id - acts_as_watchable - - validates_presence_of :board, :subject, :content - validates_length_of :subject, :maximum => 255 - validate :cannot_reply_to_locked_topic, :on => :create - - after_create :add_author_as_watcher, :reset_counters! - after_update :update_messages_board - after_destroy :reset_counters!,:down_user_score - - # fq - after_create :act_as_activity,:be_user_score,:act_as_forge_activity - #before_save :be_user_score - # end - - scope :visible, lambda {|*args| - includes(:board => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) - } - - scope :course_visible, lambda {|*args| - includes(:board => :course).where(Course.allowed_to_condition(args.shift || User.current, :view_course_messages, *args)) - } - - - safe_attributes 'subject', 'content' - safe_attributes 'board_id','locked', 'sticky', - :if => lambda {|message, user| - if message.project - user.allowed_to?(:edit_messages, message.project) - else - user.allowed_to?(:edit_messages, message.course) - end - } - - def visible?(user=User.current) - if project - !user.nil? && user.allowed_to?(:view_messages, project) - elsif course - !user.nil? && user.allowed_to?(:view_messages, course) - end - end - - def cannot_reply_to_locked_topic - # Can not reply to a locked topic - errors.add :base, 'Topic is locked' if root.locked? && self != root - end - - def update_messages_board - if board_id_changed? - Message.update_all({:board_id => board_id}, ["id = ? OR parent_id = ?", root.id, root.id]) - Board.reset_counters!(board_id_was) - Board.reset_counters!(board_id) - end - end - - def reset_counters! - if parent && parent.id - Message.update_all({:last_reply_id => parent.children.maximum(:id)}, {:id => parent.id}) - end - board.reset_counters! - end - - def sticky=(arg) - write_attribute :sticky, (arg == true || arg.to_s == '1' ? 1 : 0) - end - - def sticky? - sticky == 1 - end - - def project - board.project - end - - def course - board.course - end - - def course_editable_by?(usr) - usr && usr.logged? && (usr.allowed_to?(:edit_messages, course) || (self.author == usr && usr.allowed_to?(:edit_own_messages, course))) - end - - def course_destroyable_by?(usr) - usr && usr.logged? && (usr.allowed_to?(:delete_messages, course) || (self.author == usr && usr.allowed_to?(:delete_own_messages, course))) - end - - def editable_by?(usr) - usr && usr.logged? && (usr.allowed_to?(:edit_messages, project) || (self.author == usr && usr.allowed_to?(:edit_own_messages, project))) - end - - def destroyable_by?(usr) - usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project))) - end - - private - - def add_author_as_watcher - Watcher.create(:watchable => self.root, :user => author) - end - - # fq - def act_as_activity - self.acts << Activity.new(:user_id => self.author_id) - end - # end - - # Time 2015-02-27 14:32:25 - # Author lizanle - # Description - def act_as_forge_activity - # 如果project为空,那么就是课程相关的消息 - if !self.board.project.nil? - self.forge_acts << ForgeActivity.new(:user_id => self.author_id, - :project_id => self.board.project.id) - end - end - - #更新用户分数 -by zjc - def be_user_score - #新建message且无parent的为发帖 - - if self.parent_id.nil? && !self.board.project.nil? - UserScore.joint(:post_message, self.author,nil,self, { message_id: self.id }) - update_memo_number(self.author,1) - if self.board.project_id != -1 - update_memo_number(self.author,2,self.board.project) - end - #新建message且有parent的为回帖 - elsif !self.parent_id.nil? && !self.board.project.nil? - UserScore.joint(:reply_posting, self.author,self.parent.author,self, { message_id: self.id }) - update_replay_for_memo(self.author,1) - if self.board.project_id != -1 - update_replay_for_memo(self.author,2,self.board.project) - end - end - end - - #减少用户分数 - def down_user_score - if self.parent_id.nil? && !self.board.project.nil? - UserScore.joint(:delete_message, self.author,nil,self, { message_id: self.id }) - update_memo_number(User.current,1) - if self.board.project_id != -1 - update_memo_number(self.author,2,self.board.project) - end - elsif !self.parent_id.nil? && !self.board.project.nil? - UserScore.joint(:reply_deleting, self.author,self.parent.author,self, { message_id: self.id }) - update_replay_for_memo(User.current,1) - if self.board.project_id != -1 - update_replay_for_memo(self.author,2,self.board.project) - end - end - end - - -end +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Message < ActiveRecord::Base + include Redmine::SafeAttributes + include UserScoreHelper + include ApplicationHelper + has_many_kindeditor_assets :assets, :dependent => :destroy + belongs_to :board + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + has_many :praise_tread, as: :praise_tread_object, dependent: :destroy + + acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC" + acts_as_attachable + belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id' + + # added by fq + has_many :acts, :class_name => 'Activity', :as => :act, :dependent => :destroy + # 被ForgeActivity虚拟关联 + has_many :forge_acts, :class_name => 'ForgeActivity',:as =>:forge_act ,:dependent => :destroy + # end + + acts_as_searchable :columns => ['subject', 'content'], + :include => {:board => :project}, + :project_key => "#{Board.table_name}.project_id", + :date_column => "#{table_name}.created_on" + acts_as_searchable :columns => ['subject', 'content'], + :include => {:board => :course}, + :course_key => "#{Board.table_name}.course_id", + :date_column => "#{table_name}.created_at" + acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, + :description => :content, + :datetime => :updated_on, + # :datetime => "#{Message.table_name}.created_on", + :group => :parent, + :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'}, + :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} : + {:id => o.parent_id, :r => o.id, :anchor => "message-#{o.id}"})} + + acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]}, + :author_key => :author_id + acts_as_activity_provider :find_options => {:include => [{:board => :course}, :author]}, + :type => 'course_messages', + :author_key => :author_id + acts_as_watchable + + validates_presence_of :board, :subject, :content + validates_length_of :subject, :maximum => 255 + validate :cannot_reply_to_locked_topic, :on => :create + + after_create :add_author_as_watcher, :reset_counters! + after_update :update_messages_board + after_destroy :reset_counters!,:down_user_score,:delete_kindeditor_assets + + after_create :act_as_activity,:be_user_score,:act_as_forge_activity, :send_mail + #before_save :be_user_score + + scope :visible, lambda {|*args| + includes(:board => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) + } + + scope :course_visible, lambda {|*args| + includes(:board => :course).where(Course.allowed_to_condition(args.shift || User.current, :view_course_messages, *args)) + } + + + safe_attributes 'subject', 'content' + safe_attributes 'board_id','locked', 'sticky', + :if => lambda {|message, user| + if message.project + user.allowed_to?(:edit_messages, message.project) + else + user.allowed_to?(:edit_messages, message.course) + end + } + + def visible?(user=User.current) + if project + !user.nil? && user.allowed_to?(:view_messages, project) + elsif course + !user.nil? && user.allowed_to?(:view_messages, course) + end + end + + def cannot_reply_to_locked_topic + # Can not reply to a locked topic + errors.add :base, 'Topic is locked' if root.locked? && self != root + end + + def update_messages_board + if board_id_changed? + Message.update_all({:board_id => board_id}, ["id = ? OR parent_id = ?", root.id, root.id]) + Board.reset_counters!(board_id_was) + Board.reset_counters!(board_id) + end + end + + def reset_counters! + if parent && parent.id + Message.update_all({:last_reply_id => parent.children.maximum(:id)}, {:id => parent.id}) + end + board.reset_counters! + end + + def sticky=(arg) + write_attribute :sticky, (arg == true || arg.to_s == '1' ? 1 : 0) + end + + def sticky? + sticky == 1 + end + + def project + board.project + end + + def course + board.course + end + + def course_editable_by?(usr) + usr && usr.logged? && (usr.allowed_to?(:edit_messages, course) || (self.author == usr && usr.allowed_to?(:edit_own_messages, course))) + end + + def course_destroyable_by?(usr) + usr && usr.logged? && (usr.allowed_to?(:delete_messages, course) || (self.author == usr && usr.allowed_to?(:delete_own_messages, course))) + end + + def editable_by?(usr) + usr && usr.logged? && (usr.allowed_to?(:edit_messages, project) || (self.author == usr && usr.allowed_to?(:edit_own_messages, project))) + end + + def destroyable_by?(usr) + usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project))) + end + + private + + def add_author_as_watcher + Watcher.create(:watchable => self.root, :user => author) + end + + # fq + def act_as_activity + self.acts << Activity.new(:user_id => self.author_id) + end + # end + + # Time 2015-02-27 14:32:25 + # Author lizanle + # Description + def act_as_forge_activity + # 如果project为空,那么就是课程相关的消息 + if !self.board.project.nil? + self.forge_acts << ForgeActivity.new(:user_id => self.author_id, + :project_id => self.board.project.id) + end + end + + #更新用户分数 -by zjc + def be_user_score + #新建message且无parent的为发帖 + + if self.parent_id.nil? && !self.board.project.nil? + UserScore.joint(:post_message, self.author,nil,self, { message_id: self.id }) + update_memo_number(self.author,1) + if self.board.project_id != -1 + update_memo_number(self.author,2,self.board.project) + end + #新建message且有parent的为回帖 + elsif !self.parent_id.nil? && !self.board.project.nil? + UserScore.joint(:reply_posting, self.author,self.parent.author,self, { message_id: self.id }) + update_replay_for_memo(self.author,1) + if self.board.project_id != -1 + update_replay_for_memo(self.author,2,self.board.project) + end + end + end + + #减少用户分数 + def down_user_score + if self.parent_id.nil? && !self.board.project.nil? + UserScore.joint(:delete_message, self.author,nil,self, { message_id: self.id }) + update_memo_number(User.current,1) + if self.board.project_id != -1 + update_memo_number(self.author,2,self.board.project) + end + elsif !self.parent_id.nil? && !self.board.project.nil? + UserScore.joint(:reply_deleting, self.author,self.parent.author,self, { message_id: self.id }) + update_replay_for_memo(User.current,1) + if self.board.project_id != -1 + update_replay_for_memo(self.author,2,self.board.project) + end + end + end + + def send_mail + Mailer.run.message_posted(self) if Setting.notified_events.include?('message_posted') + end + + # Time 2015-03-31 09:15:06 + # Author lizanle + # Description 删除对应消息的图片资源 + def delete_kindeditor_assets + delete_kindeditor_assets_from_disk self.id,OwnerTypeHelper::MESSAGE + end +end diff --git a/app/models/message_observer.rb b/app/models/message_observer.rb deleted file mode 100644 index 383301664..000000000 --- a/app/models/message_observer.rb +++ /dev/null @@ -1,25 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class MessageObserver < ActiveRecord::Observer - def after_create(message) - ##by senluo - thread5=Thread.new do - Mailer.message_posted(message).deliver if Setting.notified_events.include?('message_posted') - end - end -end diff --git a/app/models/news.rb b/app/models/news.rb index d2547fc02..7e809cbfd 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -1,108 +1,120 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class News < ActiveRecord::Base - include Redmine::SafeAttributes - belongs_to :project - #added by nwb - belongs_to :course - belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' - has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on" - # fq - has_many :acts, :class_name => 'Activity', :as => :act, :dependent => :destroy - # 被ForgeActivity虚拟关联 - has_many :forge_acts, :class_name => 'ForgeActivity',:as =>:forge_act ,:dependent => :destroy - # end - - validates_presence_of :title, :description - validates_length_of :title, :maximum => 60 - validates_length_of :summary, :maximum => 255 - validates_length_of :description, :maximum => 10000 - - acts_as_attachable :delete_permission => :manage_news - acts_as_searchable :columns => ['title', 'summary', "#{table_name}.description"], :include => :project - acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}} - acts_as_activity_provider :find_options => {:include => [:project, :author]}, - :author_key => :author_id - #added by nwb - #课程新闻独立于项目 - acts_as_activity_provider :type => 'course_news', - :find_options => {:include => [:course, :author]}, - :author_key => :author_id - acts_as_watchable - - after_create :add_author_as_watcher - # fq - after_create :act_as_activity,:act_as_forge_activity - # end - - scope :visible, lambda {|*args| - includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_news, *args)) - } - - scope :course_visible, lambda {|*args| - includes(:course).where(Course.allowed_to_condition(args.shift || User.current, :view_course_news, *args)) - } - safe_attributes 'title', 'summary', 'description' - - def visible?(user=User.current) - !user.nil? && user.allowed_to?(:view_news, project) - end - - # Returns true if the news can be commented by user - def commentable?(user=User.current) - user.allowed_to?(:comment_news, project) - end - - def recipients - project.users.select {|user| user.notify_about?(self)}.map(&:mail) - end - - # returns latest news for projects visible by user - def self.latest(user = User.current, count = 5) - visible(user).includes([:author, :project]).order("#{News.table_name}.created_on DESC").limit(count).all - end - - # 新闻的短描述信息 - def short_description(length = 255) - description.gsub(/<\/?.*?>/,"").html_safe if description - #description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description - #description - end - - private - - def add_author_as_watcher - Watcher.create(:watchable => self, :user => author) - end - ## fq - def act_as_activity - self.acts << Activity.new(:user_id => self.author_id) - end - - # Time 2015-02-27 15:48:17 - # Author lizanle - # Description 公用表中也要记录 - def act_as_forge_activity - # 如果是project为空,那么是课程相关的,不需要保存 - if !self.project.nil? - self.forge_acts << ForgeActivity.new(:user_id => self.author_id, - :project_id => self.project.id) - end - end - -end +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class News < ActiveRecord::Base + include Redmine::SafeAttributes + belongs_to :project + include ApplicationHelper + has_many_kindeditor_assets :assets, :dependent => :destroy + #added by nwb + belongs_to :course + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on" + # fq + has_many :acts, :class_name => 'Activity', :as => :act, :dependent => :destroy + # 被ForgeActivity虚拟关联 + has_many :forge_acts, :class_name => 'ForgeActivity',:as =>:forge_act ,:dependent => :destroy + # end + + validates_presence_of :title, :description + validates_length_of :title, :maximum => 60 + validates_length_of :summary, :maximum => 255 + validates_length_of :description, :maximum => 10000 + + acts_as_attachable :delete_permission => :manage_news + acts_as_searchable :columns => ['title', 'summary', "#{table_name}.description"], :include => :project + acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}} + acts_as_activity_provider :find_options => {:include => [:project, :author]}, + :author_key => :author_id + #added by nwb + #课程新闻独立于项目 + acts_as_activity_provider :type => 'course_news', + :find_options => {:include => [:course, :author]}, + :author_key => :author_id + acts_as_watchable + + after_create :act_as_activity,:act_as_forge_activity,:add_author_as_watcher, :send_mail + + after_destroy :delete_kindeditor_assets + + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_news, *args)) + } + + scope :course_visible, lambda {|*args| + includes(:course).where(Course.allowed_to_condition(args.shift || User.current, :view_course_news, *args)) + } + safe_attributes 'title', 'summary', 'description' + + def visible?(user=User.current) + !user.nil? && user.allowed_to?(:view_news, project) + end + + # Returns true if the news can be commented by user + def commentable?(user=User.current) + user.allowed_to?(:comment_news, project) + end + + def recipients + project.users.select {|user| user.notify_about?(self)}.map(&:mail) + end + + # returns latest news for projects visible by user + def self.latest(user = User.current, count = 5) + visible(user).includes([:author, :project]).order("#{News.table_name}.created_on DESC").limit(count).all + end + + # 新闻的短描述信息 + def short_description(length = 255) + description.gsub(/<\/?.*?>/,"").html_safe if description + #description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description + #description + end + + private + + def add_author_as_watcher + Watcher.create(:watchable => self, :user => author) + end + ## fq + def act_as_activity + self.acts << Activity.new(:user_id => self.author_id) + end + + # Time 2015-02-27 15:48:17 + # Author lizanle + # Description 公用表中也要记录 + def act_as_forge_activity + # 如果是project为空,那么是课程相关的,不需要保存 + if !self.project.nil? + self.forge_acts << ForgeActivity.new(:user_id => self.author_id, + :project_id => self.project.id) + end + end + + # Time 2015-03-31 13:50:54 + # Author lizanle + # Description 删除news后删除对应的资源 + def delete_kindeditor_assets + delete_kindeditor_assets_from_disk self.id,OwnerTypeHelper::NEWS + end + + def send_mail + Mailer.run.news_added(self) if Setting.notified_events.include?('news_added') + end + +end diff --git a/app/models/news_observer.rb b/app/models/news_observer.rb deleted file mode 100644 index 8b9bc7b4b..000000000 --- a/app/models/news_observer.rb +++ /dev/null @@ -1,25 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class NewsObserver < ActiveRecord::Observer - def after_create(news) - ##by senluo - thread6=Thread.new do - Mailer.news_added(news).deliver if Setting.notified_events.include?('news_added') - end - end -end diff --git a/app/models/poll.rb b/app/models/poll.rb index 06f1369c1..64e9df79a 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -6,4 +6,25 @@ class Poll < ActiveRecord::Base has_many :poll_questions, :dependent => :destroy,:order => "#{PollQuestion.table_name}.question_number" has_many :poll_users, :dependent => :destroy has_many :users, :through => :poll_users #该文件被哪些用户提交答案过 + # 添加课程的poll动态 + has_many :acts, :class_name => 'Activity', :as => :act, :dependent => :destroy + after_create :act_as_activity + + acts_as_event :title => Proc.new {|o| "#{l(:label_course_poll)}: #{o.polls_name}" }, + :description => :polls_description, + :datetime => :published_at, + :author => :user, + :url => Proc.new {|o| {:controller => 'poll', :action => 'show', :id => o.id}} + + acts_as_activity_provider :type => 'polls', + #:permission => :view_course_polls, + :find_options => {:select => "#{Poll.table_name}.*", + :joins => "LEFT JOIN #{Course.table_name} ON ( #{Poll.table_name}.polls_type='Course' AND #{Poll.table_name}.polls_status= 2 AND #{Poll.table_name}.polls_group_id = #{Course.table_name}.id )"}, + :timestamp => "#{self.table_name}.published_at", + :author_key => :user_id + + def act_as_activity + self.acts << Activity.new(:user_id => self.user_id) + end + end diff --git a/app/models/principal.rb b/app/models/principal.rb index 5aa21768f..0f0746f78 100644 --- a/app/models/principal.rb +++ b/app/models/principal.rb @@ -19,6 +19,7 @@ class Principal < ActiveRecord::Base self.table_name = "#{table_name_prefix}users#{table_name_suffix}" # Account statuses + # 0 全部;1 活动的; 2 已注册; 3 锁定 STATUS_ANONYMOUS = 0 STATUS_ACTIVE = 1 STATUS_REGISTERED = 2 @@ -42,7 +43,8 @@ class Principal < ActiveRecord::Base where({}) else pattern = "%#{q}%" - sql = %w(login firstname lastname mail).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ") + # sql = %w(login firstname lastname mail).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ") + sql= "LOWER(concat(lastname,firstname)) LIKE LOWER(:p) or LOWER(login) LIKE LOWER(:p) or LOWER(mail) LIKE LOWER(:p)" params = {:p => pattern} if q =~ /^(.+)\s+(.+)$/ a, b = "#{$1}%", "#{$2}%" diff --git a/app/models/project.rb b/app/models/project.rb index 07854822e..f257058ea 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -51,7 +51,7 @@ class Project < ActiveRecord::Base has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" has_many :boards, :dependent => :destroy, :order => "position ASC" has_one :repository, :conditions => ["is_default = ?", true] - has_many :repositories, :dependent => :destroy + has_many :repositories, :dependent => :destroy, conditions: "hidden=false" has_many :changesets, :through => :repository #added by xianbo for delete biding_project has_many :biding_projects, :dependent => :destroy diff --git a/app/models/relative_memo.rb b/app/models/relative_memo.rb index f087fce2b..07767c7d6 100644 --- a/app/models/relative_memo.rb +++ b/app/models/relative_memo.rb @@ -1,199 +1,199 @@ -class RelativeMemo < ActiveRecord::Base - # attr_accessible :title, :body - include Redmine::SafeAttributes - belongs_to :open_source_project, :class_name => "OpenSourceProject", :foreign_key => 'osp_id' - belongs_to :author, :class_name => "User", :foreign_key => 'author_id' - - has_many :tags, :through => :project_tags, :class_name => 'Tag' - has_many :project_tags, :class_name => 'ProjectTags' - - has_many :relation_topics, :class_name => 'RelativeMemoToOpenSourceProject' - - has_many :no_uses, :as => :no_use, :dependent => :delete_all - - has_many :bugs_to_osp, :class_name => 'BugToOsp', :foreign_key => 'relative_memo_id', :dependent => :destroy - - - acts_as_taggable - - validates_presence_of :subject - #validates :content, presence: true - # validates_length_of :subject, maximum: 50 - #validates_length_of :content, maximum: 3072 - validate :cannot_reply_to_locked_topic, :on => :create - validates_uniqueness_of :osp_id, :scope => [:subject, :content] - - acts_as_tree :counter_cache => :replies_count, :order => "#{RelativeMemo.table_name}.created_at ASC" - acts_as_attachable - belongs_to :last_reply, :class_name => 'RelativeMemo', :foreign_key => 'last_reply_id' - # acts_as_searchable :column => ['subject', 'content'], - # #:include => { :forum => :p} - # #:project_key => "#{Forum.table_name}.project_id" - # :date_column => "#{table_name}.created_at" - - # acts_as_event :title => Proc.new {|o| "#{o.forum.name}: #{o.subject}"}, - # :datetime => :updated_at, - # # :datetime => :created_at, - # :description => :content, - # :author => :author, - # :type => Proc.new {|o| o.parent_id.nil? ? 'Memo' : 'Reply'}, - # :url => Proc.new {|o| {:controller => 'memos', :action => 'show', :forum_id => o.forum_id}.merge(o.parent_id.nil? ? {:id => o.id} : {:id => o.parent_id, :r => o.id, :anchor => "reply-#{o.id}"})} - # acts_as_activity_provider :author_key => :author_id, - # :func => 'memos', - # :timestamp => 'created_at' - - # :find_options => {:type => 'memos'} - # acts_as_watchable - - safe_attributes "author_id", - "subject", - "content", - "osp_id", - "last_memo_id", - "lock", - "sticky", - "parent_id", - "replies_count", - "is_quote" - - after_create :add_author_as_watcher, :reset_counters! - # after_update :update_memos_forum - after_destroy :reset_counters! - # after_create :send_notification - # after_save :plusParentAndForum - # after_destroy :minusParentAndForum - - # scope :visible, lambda { |*args| - # includes(:forum => ).where() - # } - - def cannot_reply_to_locked_topic - errors.add :base, l(:label_memo_locked) if root.locked? && self != root - end - - def short_content(length = 25) - str = "^(.{,#{length}})[^\n\r]*.*$" - content.gsub(Regexp.new(str), '\1...').strip if content - end - - # def update_memos_forum - # if forum_id_changed? - # Message.update_all({:board_id => board_id}, ["id = ? OR parent_id = ?", root.id, root.id ]) - # Forum.reset_counters!(forum_id_was) - # Forum.reset_counters!(forum_id) - # end - # end - - - scope :no_use_for, lambda { |user_id| - { :include => :no_uses, - :conditions => ["#{NoUse.table_name}.user_id = ?", user_id] } - } - - # 获取帖子的回复 - def replies - memos = RelativeMemo.where("parent_id = ?", id) - end - - def no_use_for?(user) - self.no_uses.each do |no_use| - if no_use.user_id == user.id - return true - end - end - false - end - - def set_no_use(user, flag=true) - flag ? set_filter(user) : remove_filter(user) - end - - def set_filter(user) - self.no_uses << NoUse.new(:user => user) - end - - def remove_filter(user) - return nil unless user && user.is_a?(User) - NoUse.delete_all "no_use_type = '#{self.class}' AND no_use_id = #{self.id} AND user_id = #{user.id}" - end - - def reset_counters! - if parent && parent.id - RelativeMemo.update_all({:last_reply_id => parent.children.maximum(:id)}, {:id => parent.id}) - parent.update_attribute(:updated_at, Time.now) - end - # forum.reset_counters! - end - - def sticky? - sticky == 1 - end - - def replies - RelativeMemo.where("parent_id = ?", id) - end - - def locked? - self.lock - end - - def editable_by? user - # user && user.logged? || (self.author == usr && usr.allowed_to?(:edit_own_messages, project)) - user.admin? - end - - # def destroyable_by? user - # (user && user.logged? && (Forum.find(self.forum_id).creator_id == user.id) ) || user.admin? - # #self.author == user || user.admin? - # end - - def deleted_attach_able_by? user - (user && user.logged? && (self.author == user) ) || user.admin? - end - - private - - def add_author_as_watcher - Watcher.create(:watchable => self.root, :user => author) - end - - def send_notification - if Setting.notified_events.include?('message_posted') - Mailer.message_posted(self).deliver - end - end - - # def plusParentAndForum - # @forum = Forum.find(self.forum_id) - # @forum.memo_count = @forum.memo_count.to_int + 1 - # @forum.last_memo_id = self.id - # if self.parent_id - # @parent_memo = Memo.find_by_id(self.parent_id) - # @parent_memo.last_reply_id = self - # @parent_memo.replies_count = @parent_memo.replies_count.to_int + 1 - # @parent_memo.save - # else - # @forum.topic_count = @forum.topic_count.to_int + 1 - # end - # @forum.save - # end - - # def minusParentAndForum - # @forum = Forum.find(self.forum_id) - # @forum.memo_count = @forum.memo_count.to_int - 1 - # @forum.memo_count = 0 if @forum.memo_count.to_int < 0 - # # @forum.last_memo_id = Memo.reorder('created_at ASC').find_all_by_forum_id(self.forum_id).last.id - # if self.parent_id - # @parent_memo = Memo.find_by_id(self.parent_id) - # # @parent_memo.last_reply_id = Memo.reorder('created_at ASC').find_all_by_parent_id(self.parent_id).last.id - # @parent_memo.replies_count = @parent_memo.replies_count.to_int - 1 - # @parent_memo.replies_count = 0 if @parent_memo.replies_count.to_int < 0 - # @parent_memo.save - # else - # @forum.topic_count = @forum.topic_count.to_int - 1 - # @forum.topic_count = 0 if @forum.topic_count.to_int < 0 - # end - # @forum.save - # end -end - +class RelativeMemo < ActiveRecord::Base + # attr_accessible :title, :body + include Redmine::SafeAttributes + belongs_to :open_source_project, :class_name => "OpenSourceProject", :foreign_key => 'osp_id' + belongs_to :author, :class_name => "User", :foreign_key => 'author_id' + + has_many :tags, :through => :project_tags, :class_name => 'Tag' + has_many :project_tags, :class_name => 'ProjectTags' + + has_many :relation_topics, :class_name => 'RelativeMemoToOpenSourceProject' + + has_many :no_uses, :as => :no_use, :dependent => :delete_all + + has_many :bugs_to_osp, :class_name => 'BugToOsp', :foreign_key => 'relative_memo_id', :dependent => :destroy + + + acts_as_taggable + + validates_presence_of :subject + #validates :content, presence: true + # validates_length_of :subject, maximum: 50 + #validates_length_of :content, maximum: 3072 + validate :cannot_reply_to_locked_topic, :on => :create + validates_uniqueness_of :osp_id, :scope => [:subject, :content] + + acts_as_tree :counter_cache => :replies_count, :order => "#{RelativeMemo.table_name}.created_at ASC" + acts_as_attachable + belongs_to :last_reply, :class_name => 'RelativeMemo', :foreign_key => 'last_reply_id' + # acts_as_searchable :column => ['subject', 'content'], + # #:include => { :forum => :p} + # #:project_key => "#{Forum.table_name}.project_id" + # :date_column => "#{table_name}.created_at" + + # acts_as_event :title => Proc.new {|o| "#{o.forum.name}: #{o.subject}"}, + # :datetime => :updated_at, + # # :datetime => :created_at, + # :description => :content, + # :author => :author, + # :type => Proc.new {|o| o.parent_id.nil? ? 'Memo' : 'Reply'}, + # :url => Proc.new {|o| {:controller => 'memos', :action => 'show', :forum_id => o.forum_id}.merge(o.parent_id.nil? ? {:id => o.id} : {:id => o.parent_id, :r => o.id, :anchor => "reply-#{o.id}"})} + # acts_as_activity_provider :author_key => :author_id, + # :func => 'memos', + # :timestamp => 'created_at' + + # :find_options => {:type => 'memos'} + # acts_as_watchable + + safe_attributes "author_id", + "subject", + "content", + "osp_id", + "last_memo_id", + "lock", + "sticky", + "parent_id", + "replies_count", + "is_quote" + + after_create :add_author_as_watcher, :reset_counters! + # after_update :update_memos_forum + after_destroy :reset_counters! + # after_create :send_notification + # after_save :plusParentAndForum + # after_destroy :minusParentAndForum + + # scope :visible, lambda { |*args| + # includes(:forum => ).where() + # } + + def cannot_reply_to_locked_topic + errors.add :base, l(:label_memo_locked) if root.locked? && self != root + end + + def short_content(length = 25) + str = "^(.{,#{length}})[^\n\r]*.*$" + content.gsub(Regexp.new(str), '\1...').strip if content + end + + # def update_memos_forum + # if forum_id_changed? + # Message.update_all({:board_id => board_id}, ["id = ? OR parent_id = ?", root.id, root.id ]) + # Forum.reset_counters!(forum_id_was) + # Forum.reset_counters!(forum_id) + # end + # end + + + scope :no_use_for, lambda { |user_id| + { :include => :no_uses, + :conditions => ["#{NoUse.table_name}.user_id = ?", user_id] } + } + + # 获取帖子的回复 + def replies + memos = RelativeMemo.where("parent_id = ?", id) + end + + def no_use_for?(user) + self.no_uses.each do |no_use| + if no_use.user_id == user.id + return true + end + end + false + end + + def set_no_use(user, flag=true) + flag ? set_filter(user) : remove_filter(user) + end + + def set_filter(user) + self.no_uses << NoUse.new(:user => user) + end + + def remove_filter(user) + return nil unless user && user.is_a?(User) + NoUse.delete_all "no_use_type = '#{self.class}' AND no_use_id = #{self.id} AND user_id = #{user.id}" + end + + def reset_counters! + if parent && parent.id + RelativeMemo.update_all({:last_reply_id => parent.children.maximum(:id)}, {:id => parent.id}) + parent.update_attribute(:updated_at, Time.now) + end + # forum.reset_counters! + end + + def sticky? + sticky == 1 + end + + def replies + RelativeMemo.where("parent_id = ?", id) + end + + def locked? + self.lock + end + + def editable_by? user + # user && user.logged? || (self.author == usr && usr.allowed_to?(:edit_own_messages, project)) + user.admin? + end + + # def destroyable_by? user + # (user && user.logged? && (Forum.find(self.forum_id).creator_id == user.id) ) || user.admin? + # #self.author == user || user.admin? + # end + + def deleted_attach_able_by? user + (user && user.logged? && (self.author == user) ) || user.admin? + end + + private + + def add_author_as_watcher + Watcher.create(:watchable => self.root, :user => author) + end + + def send_notification + if Setting.notified_events.include?('message_posted') + Mailer.run.message_posted(self) + end + end + + # def plusParentAndForum + # @forum = Forum.find(self.forum_id) + # @forum.memo_count = @forum.memo_count.to_int + 1 + # @forum.last_memo_id = self.id + # if self.parent_id + # @parent_memo = Memo.find_by_id(self.parent_id) + # @parent_memo.last_reply_id = self + # @parent_memo.replies_count = @parent_memo.replies_count.to_int + 1 + # @parent_memo.save + # else + # @forum.topic_count = @forum.topic_count.to_int + 1 + # end + # @forum.save + # end + + # def minusParentAndForum + # @forum = Forum.find(self.forum_id) + # @forum.memo_count = @forum.memo_count.to_int - 1 + # @forum.memo_count = 0 if @forum.memo_count.to_int < 0 + # # @forum.last_memo_id = Memo.reorder('created_at ASC').find_all_by_forum_id(self.forum_id).last.id + # if self.parent_id + # @parent_memo = Memo.find_by_id(self.parent_id) + # # @parent_memo.last_reply_id = Memo.reorder('created_at ASC').find_all_by_parent_id(self.parent_id).last.id + # @parent_memo.replies_count = @parent_memo.replies_count.to_int - 1 + # @parent_memo.replies_count = 0 if @parent_memo.replies_count.to_int < 0 + # @parent_memo.save + # else + # @forum.topic_count = @forum.topic_count.to_int - 1 + # @forum.topic_count = 0 if @forum.topic_count.to_int < 0 + # end + # @forum.save + # end +end + diff --git a/app/models/token.rb b/app/models/token.rb index 3131bce8d..c89ff30bc 100644 --- a/app/models/token.rb +++ b/app/models/token.rb @@ -27,6 +27,14 @@ class Token < ActiveRecord::Base self.value = Token.generate_token_value end + def self.get_token_from_user(user, action) + token = Token.where(:action => action, :user_id => user).first + unless token + token = Token.create(user: user, action: action) + end + token + end + # Return true if token has expired def expired? return Time.now > self.created_on + @@validity_time diff --git a/app/models/user.rb b/app/models/user.rb index 9abe80779..7b232bf13 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,1029 +1,1030 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require "digest/sha1" - -class User < Principal - TEACHER = 0 - STUDENT = 1 - ENTERPRISE = 2 - DEVELOPER = 3 - - include Redmine::SafeAttributes - seems_rateable_rater - # Different ways of displaying/sorting users - USER_FORMATS = { - :firstname_lastname => { - :string => '#{firstname} #{lastname}', - :order => %w(firstname lastname id), - :setting_order => 1 - }, - :firstname_lastinitial => { - :string => '#{firstname} #{lastname.to_s.chars.first}.', - :order => %w(firstname lastname id), - :setting_order => 2 - }, - :firstname => { - :string => '#{firstname}', - :order => %w(firstname id), - :setting_order => 3 - }, - :lastname_firstname => { - :string => '#{lastname} #{firstname}', - :order => %w(lastname firstname id), - :setting_order => 4 - }, - :lastname_coma_firstname => { - :string => '#{lastname}, #{firstname}', - :order => %w(lastname firstname id), - :setting_order => 5 - }, - :lastname => { - :string => '#{lastname}', - :order => %w(lastname id), - :setting_order => 6 - }, - :username => { - :string => '#{login}', - :order => %w(login id), - :setting_order => 7 - }, - } - - MAIL_NOTIFICATION_OPTIONS = [ - ['all', :label_user_mail_option_all], - ['week', :label_user_mail_option_week], - ['day', :label_user_mail_option_day], - ['none', :label_user_mail_option_none] - ] - - has_many :homework_users - has_many :homework_attaches, :through => :homework_users - has_many :homework_evaluations - - #问卷相关关关系 - has_many :poll_users, :dependent => :destroy - has_many :poll_votes, :dependent => :destroy - has_many :poll, :dependent => :destroy #用户创建的问卷 - has_many :answers, :source => :poll, :through => :poll_users, :dependent => :destroy #用户已经完成问答的问卷 - # end - - has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)}, - :after_remove => Proc.new {|user, group| group.user_removed(user)} - has_many :changesets, :dependent => :nullify - has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' - has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'" - has_one :api_token, :class_name => 'Token', :conditions => "action='api'" - belongs_to :auth_source - belongs_to :ucourse, :class_name => 'Course', :foreign_key => :id #huang -## added by xianbo for delete - has_many :biding_projects, :dependent => :destroy - has_many :contesting_projects, :dependent => :destroy - belongs_to :softapplication, :foreign_key => 'id', :dependent => :destroy -##ended by xianbo - -#####fq - has_many :jours, :class_name => 'JournalsForMessage', :dependent => :destroy - has_many :journals_messages, :class_name => 'JournalsForMessage', :foreign_key => "user_id", :dependent => :destroy - has_many :bids, :foreign_key => 'author_id', :dependent => :destroy - has_many :contests, :foreign_key => 'author_id', :dependent => :destroy - has_many :softapplications, :foreign_key => 'user_id', :dependent => :destroy - has_many :journals_for_messages, :as => :jour, :dependent => :destroy - has_many :new_jours, :as => :jour, :class_name => 'JournalsForMessage', :conditions => "status=1" - has_many :journal_replies, :dependent => :destroy - has_many :activities, :dependent => :destroy - has_many :students_for_courses - #has_many :courses, :through => :students_for_courses, :source => :project - has_many :acts, :class_name => 'Activity', :as => :act, :dependent => :destroy - has_many :file_commit, :class_name => 'Attachment', :foreign_key => 'author_id', :conditions => "container_type = 'Project' or container_type = 'Version'" -#### -# added by bai - has_many :join_in_contests, :dependent => :destroy - has_many :news, :foreign_key => 'author_id' - has_many :contestnotification, :foreign_key => 'author_id' - has_many :comments, :foreign_key => 'author_id' - has_many :notificationcomments, :foreign_key => 'author_id' - has_many :wiki_contents, :foreign_key => 'author_id' - has_many :journals - has_many :messages, :foreign_key => 'author_id' - has_one :user_score, :dependent => :destroy - has_many :documents # 项目中关联的文档再次与人关联 -# end - -######added by nie - has_many :project_infos, :dependent => :destroy - has_one :user_status, :dependent => :destroy - ##### - has_many :shares ,:dependent => :destroy - - # add by zjc - has_one :level, :class_name => 'UserLevels', :dependent => :destroy - has_many :memos , :foreign_key => 'author_id' - ##### - scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") } - scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) } - scope :visible, lambda {|*args| - nil - } - - - acts_as_customizable - ############################added by william - acts_as_taggable - scope :by_join_date, order("created_on DESC") - ############################# added by liuping 关注 - acts_as_watchable - - has_one :user_extensions,:dependent => :destroy - ## end - - # default_scope -> { includes(:user_extensions, :user_score) } - scope :teacher, -> { - joins(:user_extensions).where('user_extensions.identity = ?', UserExtensions::TEACHER) - } - scope :student, -> { - joins(:user_extensions).where('user_extensions.identity = ?', UserExtensions::STUDENT) - } - scope :developer, -> { - joins(:user_extensions).where('user_extensions.identity = ?', UserExtensions::DEVELOPER) - } - scope :enterprise, -> { - joins(:user_extensions).where('user_extensions.identity = ?', UserExtensions::ENTERPRISE) - } - - attr_accessor :password, :password_confirmation - attr_accessor :last_before_login_on - # Prevents unauthorized assignments - attr_protected :login, :admin, :password, :password_confirmation, :hashed_password - - LOGIN_LENGTH_LIMIT = 25 - MAIL_LENGTH_LIMIT = 60 - - validates_presence_of :login, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) } - validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false - validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false - # Login must contain letters, numbers, underscores only - validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i - validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT - validates_length_of :firstname, :maximum => 30 - validates_length_of :lastname, :maximum => 30 - validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true - validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true - validates_confirmation_of :password, :allow_nil => true - validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true - validate :validate_password_length - # validates_email_realness_of :mail - before_create :set_mail_notification - before_save :update_hashed_password - before_destroy :remove_references_before_destroy - # added by fq - after_create :act_as_activity - # end - - scope :in_group, lambda {|group| - group_id = group.is_a?(Group) ? group.id : group.to_i - where("#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) - } - scope :not_in_group, lambda {|group| - group_id = group.is_a?(Group) ? group.id : group.to_i - where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) - } - scope :sorted, lambda { order(*User.fields_for_order_statement)} - - scope :like, lambda {|arg, type| - if arg.blank? - where(nil) - else - pattern = "%#{arg.to_s.strip.downcase}%" - #where(" LOWER(concat(lastname, firstname)) LIKE :p ", :p => pattern) - if type == "0" - where(" LOWER(login) LIKE '#{pattern}' ") - elsif type == "1" - where(" LOWER(concat(lastname, firstname)) LIKE '#{pattern}' ") - elsif type == "3" - where(" LOWER(concat(lastname, firstname,login)) LIKE '#{pattern}' ") - else - where(" LOWER(mail) LIKE '#{pattern}' ") - end - end - } - - - # ====================================================================== - - def extensions - self.user_extensions ||= UserExtensions.new - end - - def user_score_attr - self.user_score ||= UserScore.new - end - - # ====================================================================== - - #选择项目成员时显示的用户信息文字 - def userInfo - if self.realname.gsub(' ','') == "" || self.realname.nil? - info = self.nickname; - else - info=self.nickname + ' (' + self.realname + ')'; - end - info - end - - ###添加留言 fq - def add_jour(user, notes, reference_user_id = 0, options = {}) - if options.count == 0 - self.journals_for_messages << JournalsForMessage.new(:user_id => user.id, :notes => notes, :reply_id => reference_user_id, :status => true) - else - jfm = self.journals_for_messages.build(options) - jfm.save - jfm - end - end - - # 判断用户是否加入了竞赛中 fq - def join_in_contest?(bid) - joined = JoinInContest.where('user_id = ? and bid_id =?', self.id, bid.id) - if joined.size > 0 - true - else - false - end - end - - ### fq - def join_in?(course) - joined = StudentsForCourse.where('student_id = ? and course_id = ?', self.id, course.id) - if joined.size > 0 - true - else - false - end - end - - def show_name - unless self.user_extensions.nil? - if self.user_extensions.identity == 2 - firstname - else - lastname+firstname - end - else - lastname+firstname - end - end - ## end - - def count_new_jour - count = self.new_jours.count - end - - #added by nie - def count_new_journal_reply - count = self.journal_reply.count - end - - def set_mail_notification - ##add byxianbo - thread=Thread.new do - self.mail_notification = Setting.default_notification_option if self.mail_notification.blank? - true - end - end - - def update_hashed_password - # update hashed_password if password was set - if self.password && self.auth_source_id.blank? - salt_password(password) - end - end - - alias :base_reload :reload - def reload(*args) - @name = nil - @projects_by_role = nil - @courses_by_role = nil - @membership_by_project_id = nil - base_reload(*args) - end - - def mail=(arg) - write_attribute(:mail, arg.to_s.strip) - end - - def identity_url=(url) - if url.blank? - write_attribute(:identity_url, '') - else - begin - write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url)) - rescue OpenIdAuthentication::InvalidOpenId - # Invalid url, don't save - end - end - self.read_attribute(:identity_url) - end - - VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i - # VALID_EMAIL_REGEX = /^[0-9a-zA-Z_-]+@[0-9a-zA-Z_-]+(\.[0-9a-zA-Z_-]+)+$/ - # Returns the user that matches provided login and password, or nil - #登录,返回用户名与密码匹配的用户 - def self.try_to_login(login, password) - login = login.to_s.lstrip.rstrip - password = password.to_s - - # Make sure no one can sign in with an empty login or password - return nil if login.empty? || password.empty? - if (login =~ VALID_EMAIL_REGEX) - user = find_by_mail(login) - else - user = find_by_login(login) - end - if user - # user is already in local database - #return nil unless user.active? - return nil unless user.check_password?(password) - else - # user is not yet registered, try to authenticate with available sources - attrs = AuthSource.authenticate(login, password) - if attrs - user = new(attrs) - user.login = login - user.language = Setting.default_language - if user.save - user.reload - logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source - end - end - end - if user && !user.new_record? - last_login_on = user.last_login_on.nil? ? '' : user.last_login_on.to_s - user.update_column(:last_login_on, Time.now) - end - [user, last_login_on] - rescue => text - raise text - end - - - def self.try_to_autologin(key) - user = Token.find_active_user('autologin', key, Setting.autologin.to_i) - if user - user.update_column(:last_login_on, Time.now) - user - end - end - - def self.name_formatter(formatter = nil) - USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname] - end - - # Returns an array of fields names than can be used to make an order statement for users - # according to how user names are displayed - # Examples: - # - # User.fields_for_order_statement => ['users.login', 'users.id'] - # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id'] - def self.fields_for_order_statement(table=nil) - table ||= table_name - name_formatter[:order].map {|field| "#{table}.#{field}"} - end - - # Return user's full name for display - def realname(formatter = nil) - f = self.class.name_formatter(formatter) - if formatter - eval('"' + f[:string] + '"') - else - @name ||= eval('"' + f[:string] + '"') - end - end - - def nickname(formatter = nil) - login - end - - def name(formatter = nil) - login - end - - def active? - self.status == STATUS_ACTIVE - end - - def registered? - self.status == STATUS_REGISTERED - end - - def locked? - self.status == STATUS_LOCKED - end - - def activate - self.status = STATUS_ACTIVE - end - - def register - self.status = STATUS_REGISTERED - end - - def lock - self.status = STATUS_LOCKED - end - - def activate! - update_attribute(:status, STATUS_ACTIVE) - end - - def register! - update_attribute(:status, STATUS_REGISTERED) - end - - def lock! - update_attribute(:status, STATUS_LOCKED) - end - - # Returns true if +clear_password+ is the correct user's password, otherwise false - def check_password?(clear_password) - if auth_source_id.present? - auth_source.authenticate(self.login, clear_password) - else - User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password - end - end - def check_password1?(clear_password) - - clear_password == hashed_password - - end - # Generates a random salt and computes hashed_password for +clear_password+ - # The hashed password is stored in the following form: SHA1(salt + SHA1(password)) - def salt_password(clear_password) - self.salt = User.generate_salt - self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}") - end - - # Does the backend storage allow this user to change their password? - def change_password_allowed? - return true if auth_source.nil? - return auth_source.allow_password_changes? - end - - # Generate and set a random password. Useful for automated user creation - # Based on Token#generate_token_value - # - def random_password - chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a - password = '' - 40.times { |i| password << chars[rand(chars.size-1)] } - self.password = password - self.password_confirmation = password - self - end - - def pref - self.preference ||= UserPreference.new(:user => self) - end - - def time_zone - @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone]) - end - - def wants_comments_in_reverse_order? - self.pref[:comments_sorting] == 'desc' - end - - def wants_notificationcomments_in_reverse_order? - self.pref[:notificationcomments_sorting] == 'desc' - end - # Return user's RSS key (a 40 chars long string), used to access feeds - def rss_key - if rss_token.nil? - create_rss_token(:action => 'feeds') - end - rss_token.value - end - - # Return user's API key (a 40 chars long string), used to access the API - def api_key - if api_token.nil? - create_api_token(:action => 'api') - end - api_token.value - end - - # Return an array of project ids for which the user has explicitly turned mail notifications on - def notified_projects_ids - @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id) - end - - def notified_project_ids=(ids) - Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id]) - Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty? - @notified_projects_ids = nil - notified_projects_ids - end - - def valid_notification_options - self.class.valid_notification_options(self) - end - - # Only users that belong to more than 1 project can select projects for which they are notified - def self.valid_notification_options(user=nil) - # Note that @user.membership.size would fail since AR ignores - # :include association option when doing a count - if user.nil? || user.memberships.length < 1 - MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'} - else - MAIL_NOTIFICATION_OPTIONS - end - end - - # Find a user account by matching the exact login and then a case-insensitive - # version. Exact matches will be given priority. - #通过用户名查找相应的用户,若没有匹配到,则不区分大小写进行查询 - #修改:不再匹配不区分大小写情况 -zjc - def self.find_by_login(login) - if login.present? - login = login.to_s - # First look for an exact match - user = where(:login => login).all.detect {|u| u.login == login} - #unless user - # # Fail over to case-insensitive if none was found - # user = where("LOWER(login) = ?", login.downcase).first - #end - user - end - end - - def self.find_by_rss_key(key) - Token.find_active_user('feeds', key) - end - - def self.find_by_api_key(key) - Token.find_active_user('api', key) - end - - # Makes find_by_mail case-insensitive - def self.find_by_mail(mail) - where("LOWER(mail) = ?", mail.to_s.downcase).first - end - - # Returns true if the default admin account can no longer be used - def self.default_admin_account_changed? - !User.active.find_by_login("admin").try(:check_password?, "admin") - end - - def to_s - name - end - - CSS_CLASS_BY_STATUS = { - STATUS_ANONYMOUS => 'anon', - STATUS_ACTIVE => 'active', - STATUS_REGISTERED => 'registered', - STATUS_LOCKED => 'locked' - } - - def css_classes - "user #{CSS_CLASS_BY_STATUS[status]}" - end - - # Returns the current day according to user's time zone - def today - if time_zone.nil? - Date.today - else - Time.now.in_time_zone(time_zone).to_date - end - end - - # Returns the day of +time+ according to user's time zone - def time_to_date(time) - if time_zone.nil? - time.to_date - else - time.in_time_zone(time_zone).to_date - end - end - - def logged? - true - end - - def anonymous? - !logged? - end - - # Returns user's membership for the given project - # or nil if the user is not a member of project - def membership(project) - project_id = project.is_a?(Project) ? project.id : project - - @membership_by_project_id ||= Hash.new {|h, project_id| - h[project_id] = memberships.where(:project_id => project_id).first - } - @membership_by_project_id[project_id] - end - - def coursemembership(course) - course_id = course.is_a?(Course) ? course.id : course - - @membership_by_course_id ||= Hash.new {|h, course_id| - h[course_id] = coursememberships.where(:course_id => course_id).first - } - @membership_by_course_id[course_id] - end - - # Return user's roles for project - def roles_for_project(project) - roles = [] - # No role on archived projects - return roles if project.nil? || project.archived? - if logged? - # Find project membership - membership = membership(project) - if membership - roles = membership.roles - else - @role_non_member ||= Role.non_member - roles << @role_non_member - end - else - @role_anonymous ||= Role.anonymous - roles << @role_anonymous - end - roles - end - - # 用户课程权限判断 - def roles_for_course(course) - roles = [] - # No role on archived courses - return roles if course.nil? || course.archived? - if logged? - # Find course membership - membership = coursemembership(course) - if membership - roles = membership.roles - else - @role_non_member ||= Role.non_member - roles << @role_non_member - end - else - @role_anonymous ||= Role.anonymous - roles << @role_anonymous - end - roles - end - - # Return true if the user is a member of project - def member_of?(project) - projects.to_a.include?(project) - end - - def member_of_course?(course) - courses.to_a.include?(course) - end - - def member_of_course_group?(course_group) - course_groups.to_a.include?(course_group) - end - # Returns a hash of user's projects grouped by roles - def projects_by_role - return @projects_by_role if @projects_by_role - - @projects_by_role = Hash.new([]) - memberships.each do |membership| - if membership.project - membership.roles.each do |role| - @projects_by_role[role] = [] unless @projects_by_role.key?(role) - @projects_by_role[role] << membership.project - end - end - end - @projects_by_role.each do |role, projects| - projects.uniq! - end - - @projects_by_role - end - - # 课程的角色权限 - def courses_by_role - return @courses_by_role if @courses_by_role - - @courses_by_role = Hash.new([]) - coursememberships.each do |membership| - if membership.course - membership.roles.each do |role| - @courses_by_role[role] = [] unless @courses_by_role.key?(role) - @courses_by_role[role] << membership.course - end - end - end - @courses_by_role.each do |role, courses| - courses.uniq! - end - - @courses_by_role - end - # Returns true if user is arg or belongs to arg - def is_or_belongs_to?(arg) - if arg.is_a?(User) - self == arg - elsif arg.is_a?(Group) - arg.users.include?(self) - else - false - end - end - - - # Return true if the user is allowed to do the specified action on a specific context - # Action can be: - # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') - # * a permission Symbol (eg. :edit_project) - # Context can be: - # * a project : returns true if user is allowed to do the specified action on this project - # * an array of projects : returns true if user is allowed on every project - # * nil with options[:global] set : check if user has at least one role allowed for this action, - # or falls back to Non Member / Anonymous permissions depending if the user is logged - def allowed_to?(action, context, options={}, &block) - if context && context.is_a?(Project) - return false unless context.allows_to?(action) - # Admin users are authorized for anything else - return true if admin? - - roles = roles_for_project(context) - return false unless roles - roles.any? {|role| - (context.is_public? || role.member?) && - role.allowed_to?(action) && - (block_given? ? yield(role, self) : true) - } - #添加课程相关的权限判断 - elsif context && context.is_a?(Course) - return false unless context.allows_to?(action) - # Admin users are authorized for anything else - return true if admin? - - roles = roles_for_course(context) - return false unless roles - roles.any? {|role| - (context.is_public? || role.member?) && - role.allowed_to?(action) && - (block_given? ? yield(role, self) : true) - } - elsif context && context.is_a?(Array) - if context.empty? - false - else - # Authorize if user is authorized on every element of the array - context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&) - end - elsif options[:global] - # Admin users are always authorized - return true if admin? - - # authorize if user has at least one role that has this permission - roles = memberships.collect {|m| m.roles}.flatten.uniq - if roles.count == 0 - roles = coursememberships.collect {|m| m.roles}.flatten.uniq - end - roles << (self.logged? ? Role.non_member : Role.anonymous) - roles.any? {|role| - role.allowed_to?(action) && - (block_given? ? yield(role, self) : true) - } - else - if admin? - return true - end - #无项目时 查看Non member(id为1)角色是否有权限执行action - Role.find('1').allowed_to?(action) - # false - end - end - - # Is the user allowed to do the specified action on any project? - # See allowed_to? for the actions and valid options. - def allowed_to_globally?(action, options, &block) - allowed_to?(action, nil, options.reverse_merge(:global => true), &block) - end - - # Returns true if the user is allowed to delete his own account - def own_account_deletable? - Setting.unsubscribe? && - (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?) - end - - safe_attributes 'login', - 'firstname', - 'lastname', - 'mail', - 'mail_notification', - 'language', - 'custom_field_values', - 'custom_fields', - 'identity_url' - - safe_attributes 'status', - 'auth_source_id', - :if => lambda {|user, current_user| current_user.admin?} - - safe_attributes 'group_ids', - :if => lambda {|user, current_user| current_user.admin? && !user.new_record?} - - # Utility method to help check if a user should be notified about an - # event. - # - # TODO: only supports Issue events currently - def notify_about?(object) - if mail_notification == 'all' - true - elsif mail_notification.blank? || mail_notification == 'none' - false - else - case object - when Issue - case mail_notification - when 'selected', 'only_my_events' - # user receives notifications for created/assigned issues on unselected projects - object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) - when 'only_assigned' - is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) - when 'only_owner' - object.author == self - end - when News - # always send to project members except when mail_notification is set to 'none' - true - #判定用户是否接受留言提醒邮件 - when JournalsForMessage - ##如果是直接留言并且留言对象是Project并且Project类型是课程(课程留言) - if !object.at_user && object.jour.class.to_s.to_sym == :Project && object.jour.project_type == 1 - #根据用户设置邮件接收模式判定当前用户是否接受邮件提醒 - is_notified_project object.jour - end - - end - end - end - - #用户是否接收project的消息提醒 - def is_notified_project arg - if arg.is_a?(Project) - case mail_notification - when 'selected' - notified_projects_ids.include?(arg.id) - when 'only_my_events' - projects.include?(arg) - when 'only_assigned' - false - when 'only_owner' - course = Course.find_by_extra(arg.identifier) - course.teacher == self - end - #勾选的项目或用户的项目 TODO:需改 - #notified_projects_ids.include?(arg) || projects.include?(arg) - else - false - end - end - - def self.current=(user) - Thread.current[:current_user] = user - end - - def self.current - Thread.current[:current_user] ||= User.anonymous - end - - # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only - # one anonymous user per database. - def self.anonymous - anonymous_user = AnonymousUser.first - if anonymous_user.nil? - anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0) - raise 'Unable to create the anonymous user.' if anonymous_user.new_record? - end - anonymous_user - end - - # Salts all existing unsalted passwords - # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password)) - # This method is used in the SaltPasswords migration and is to be kept as is - def self.salt_unsalted_passwords! - transaction do - User.where("salt IS NULL OR salt = ''").find_each do |user| - next if user.hashed_password.blank? - salt = User.generate_salt - hashed_password = User.hash_password("#{salt}#{user.hashed_password}") - User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password) - end - end - end - - protected - - def validate_password_length - # Password length validation based on setting - if !password.nil? && password.size < Setting.password_min_length.to_i - errors.add(:password, :too_short, :count => Setting.password_min_length.to_i) - end - end - private - - def act_as_activity - self.acts << Activity.new(:user_id => self.id) - end - - # Removes references that are not handled by associations - # Things that are not deleted are reassociated with the anonymous user - def remove_references_before_destroy - return if self.id.nil? - - substitute = User.anonymous - Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - Notificationcomment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id] - Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] - JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s] - JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s] - Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - # Remove private queries and keep public ones - ::Query.delete_all ['user_id = ? AND is_public = ?', id, false] - ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] - TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] - Token.delete_all ['user_id = ?', id] - Watcher.delete_all ['user_id = ?', id] - WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - end - - # Return password digest - def self.hash_password(clear_password) - Digest::SHA1.hexdigest(clear_password || "") - end - - # Returns a 128bits random salt as a hex string (32 chars long) - def self.generate_salt - Redmine::Utils.random_hex(16) - end - - - -end - -class AnonymousUser < User - validate :validate_anonymous_uniqueness, :on => :create - - def validate_anonymous_uniqueness - # There should be only one AnonymousUser in the database - errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists? - end - - def available_custom_fields - [] - end - - # Overrides a few properties - def logged?; false end - def admin; false end - def name(*args); I18n.t(:label_user_anonymous) end - def mail; nil end - def time_zone; nil end - def rss_key; nil end - - def pref - UserPreference.new(:user => self) - end - - # def member_of?(project) - # false - # end - - # Anonymous user can not be destroyed - def destroy - false - end -end +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require "digest/sha1" + +class User < Principal + TEACHER = 0 + STUDENT = 1 + ENTERPRISE = 2 + DEVELOPER = 3 + + include Redmine::SafeAttributes + seems_rateable_rater + # Different ways of displaying/sorting users + USER_FORMATS = { + :firstname_lastname => { + :string => '#{firstname} #{lastname}', + :order => %w(firstname lastname id), + :setting_order => 1 + }, + :firstname_lastinitial => { + :string => '#{firstname} #{lastname.to_s.chars.first}.', + :order => %w(firstname lastname id), + :setting_order => 2 + }, + :firstname => { + :string => '#{firstname}', + :order => %w(firstname id), + :setting_order => 3 + }, + :lastname_firstname => { + :string => '#{lastname} #{firstname}', + :order => %w(lastname firstname id), + :setting_order => 4 + }, + :lastname_coma_firstname => { + :string => '#{lastname}, #{firstname}', + :order => %w(lastname firstname id), + :setting_order => 5 + }, + :lastname => { + :string => '#{lastname}', + :order => %w(lastname id), + :setting_order => 6 + }, + :username => { + :string => '#{login}', + :order => %w(login id), + :setting_order => 7 + }, + } + + #每日一报、一事一报、不报 + MAIL_NOTIFICATION_OPTIONS = [ + ['all', :label_user_mail_option_all], + #['week', :label_user_mail_option_week], + ['day', :label_user_mail_option_day], + ['none', :label_user_mail_option_none] + ] + + has_many :homework_users + has_many :homework_attaches, :through => :homework_users + has_many :homework_evaluations + + #问卷相关关关系 + has_many :poll_users, :dependent => :destroy + has_many :poll_votes, :dependent => :destroy + has_many :poll, :dependent => :destroy #用户创建的问卷 + has_many :answers, :source => :poll, :through => :poll_users, :dependent => :destroy #用户已经完成问答的问卷 + # end + + has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)}, + :after_remove => Proc.new {|user, group| group.user_removed(user)} + has_many :changesets, :dependent => :nullify + has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' + has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'" + has_one :api_token, :class_name => 'Token', :conditions => "action='api'" + belongs_to :auth_source + belongs_to :ucourse, :class_name => 'Course', :foreign_key => :id #huang +## added by xianbo for delete + has_many :biding_projects, :dependent => :destroy + has_many :contesting_projects, :dependent => :destroy + belongs_to :softapplication, :foreign_key => 'id', :dependent => :destroy +##ended by xianbo + +#####fq + has_many :jours, :class_name => 'JournalsForMessage', :dependent => :destroy + has_many :journals_messages, :class_name => 'JournalsForMessage', :foreign_key => "user_id", :dependent => :destroy + has_many :bids, :foreign_key => 'author_id', :dependent => :destroy + has_many :contests, :foreign_key => 'author_id', :dependent => :destroy + has_many :softapplications, :foreign_key => 'user_id', :dependent => :destroy + has_many :journals_for_messages, :as => :jour, :dependent => :destroy + has_many :new_jours, :as => :jour, :class_name => 'JournalsForMessage', :conditions => "status=1" + has_many :journal_replies, :dependent => :destroy + has_many :activities, :dependent => :destroy + has_many :students_for_courses + #has_many :courses, :through => :students_for_courses, :source => :project + has_many :acts, :class_name => 'Activity', :as => :act, :dependent => :destroy + has_many :file_commit, :class_name => 'Attachment', :foreign_key => 'author_id', :conditions => "container_type = 'Project' or container_type = 'Version'" +#### +# added by bai + has_many :join_in_contests, :dependent => :destroy + has_many :news, :foreign_key => 'author_id' + has_many :contestnotification, :foreign_key => 'author_id' + has_many :comments, :foreign_key => 'author_id' + has_many :notificationcomments, :foreign_key => 'author_id' + has_many :wiki_contents, :foreign_key => 'author_id' + has_many :journals + has_many :messages, :foreign_key => 'author_id' + has_one :user_score, :dependent => :destroy + has_many :documents # 项目中关联的文档再次与人关联 +# end + +######added by nie + has_many :project_infos, :dependent => :destroy + has_one :user_status, :dependent => :destroy + ##### + has_many :shares ,:dependent => :destroy + + # add by zjc + has_one :level, :class_name => 'UserLevels', :dependent => :destroy + has_many :memos , :foreign_key => 'author_id' + ##### + scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") } + scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) } + scope :visible, lambda {|*args| + nil + } + + + acts_as_customizable + ############################added by william + acts_as_taggable + scope :by_join_date, order("created_on DESC") + ############################# added by liuping 关注 + acts_as_watchable + + has_one :user_extensions,:dependent => :destroy + ## end + + # default_scope -> { includes(:user_extensions, :user_score) } + scope :teacher, -> { + joins(:user_extensions).where('user_extensions.identity = ?', UserExtensions::TEACHER) + } + scope :student, -> { + joins(:user_extensions).where('user_extensions.identity = ?', UserExtensions::STUDENT) + } + scope :developer, -> { + joins(:user_extensions).where('user_extensions.identity = ?', UserExtensions::DEVELOPER) + } + scope :enterprise, -> { + joins(:user_extensions).where('user_extensions.identity = ?', UserExtensions::ENTERPRISE) + } + + attr_accessor :password, :password_confirmation + attr_accessor :last_before_login_on + # Prevents unauthorized assignments + attr_protected :login, :admin, :password, :password_confirmation, :hashed_password + + LOGIN_LENGTH_LIMIT = 25 + MAIL_LENGTH_LIMIT = 60 + + validates_presence_of :login, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) } + validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false + validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false + # Login must contain letters, numbers, underscores only + validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i + validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT + validates_length_of :firstname, :maximum => 30 + validates_length_of :lastname, :maximum => 30 + validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true + validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true + validates_confirmation_of :password, :allow_nil => true + validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true + validate :validate_password_length + # validates_email_realness_of :mail + before_create :set_mail_notification + before_save :update_hashed_password + before_destroy :remove_references_before_destroy + # added by fq + after_create :act_as_activity + # end + + scope :in_group, lambda {|group| + group_id = group.is_a?(Group) ? group.id : group.to_i + where("#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) + } + scope :not_in_group, lambda {|group| + group_id = group.is_a?(Group) ? group.id : group.to_i + where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) + } + scope :sorted, lambda { order(*User.fields_for_order_statement)} + + scope :like, lambda {|arg, type| + if arg.blank? + where(nil) + else + pattern = "%#{arg.to_s.strip.downcase}%" + #where(" LOWER(concat(lastname, firstname)) LIKE :p ", :p => pattern) + if type == "0" + where(" LOWER(login) LIKE '#{pattern}' ") + elsif type == "1" + where(" LOWER(concat(lastname, firstname)) LIKE '#{pattern}' ") + elsif type == "3" + where(" LOWER(concat(lastname, firstname,login)) LIKE '#{pattern}' ") + else + where(" LOWER(mail) LIKE '#{pattern}' ") + end + end + } + + + # ====================================================================== + + def extensions + self.user_extensions ||= UserExtensions.new + end + + def user_score_attr + self.user_score ||= UserScore.new + end + + # ====================================================================== + + #选择项目成员时显示的用户信息文字 + def userInfo + if self.realname.gsub(' ','') == "" || self.realname.nil? + info = self.nickname; + else + info=self.nickname + ' (' + self.realname + ')'; + end + info + end + + ###添加留言 fq + def add_jour(user, notes, reference_user_id = 0, options = {}) + if options.count == 0 + self.journals_for_messages << JournalsForMessage.new(:user_id => user.id, :notes => notes, :reply_id => reference_user_id, :status => true) + else + jfm = self.journals_for_messages.build(options) + jfm.save + jfm + end + end + + # 判断用户是否加入了竞赛中 fq + def join_in_contest?(bid) + joined = JoinInContest.where('user_id = ? and bid_id =?', self.id, bid.id) + if joined.size > 0 + true + else + false + end + end + + ### fq + def join_in?(course) + joined = StudentsForCourse.where('student_id = ? and course_id = ?', self.id, course.id) + if joined.size > 0 + true + else + false + end + end + + def show_name + unless self.user_extensions.nil? + if self.user_extensions.identity == 2 + firstname + else + lastname+firstname + end + else + lastname+firstname + end + end + ## end + + def count_new_jour + count = self.new_jours.count + end + + #added by nie + def count_new_journal_reply + count = self.journal_reply.count + end + + def set_mail_notification + ##add byxianbo + thread=Thread.new do + self.mail_notification = Setting.default_notification_option if self.mail_notification.blank? + true + end + end + + def update_hashed_password + # update hashed_password if password was set + if self.password && self.auth_source_id.blank? + salt_password(password) + end + end + + alias :base_reload :reload + def reload(*args) + @name = nil + @projects_by_role = nil + @courses_by_role = nil + @membership_by_project_id = nil + base_reload(*args) + end + + def mail=(arg) + write_attribute(:mail, arg.to_s.strip) + end + + def identity_url=(url) + if url.blank? + write_attribute(:identity_url, '') + else + begin + write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url)) + rescue OpenIdAuthentication::InvalidOpenId + # Invalid url, don't save + end + end + self.read_attribute(:identity_url) + end + + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i + # VALID_EMAIL_REGEX = /^[0-9a-zA-Z_-]+@[0-9a-zA-Z_-]+(\.[0-9a-zA-Z_-]+)+$/ + # Returns the user that matches provided login and password, or nil + #登录,返回用户名与密码匹配的用户 + def self.try_to_login(login, password) + login = login.to_s.lstrip.rstrip + password = password.to_s + + # Make sure no one can sign in with an empty login or password + return nil if login.empty? || password.empty? + if (login =~ VALID_EMAIL_REGEX) + user = find_by_mail(login) + else + user = find_by_login(login) + end + if user + # user is already in local database + #return nil unless user.active? + return nil unless user.check_password?(password) + else + # user is not yet registered, try to authenticate with available sources + attrs = AuthSource.authenticate(login, password) + if attrs + user = new(attrs) + user.login = login + user.language = Setting.default_language + if user.save + user.reload + logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source + end + end + end + if user && !user.new_record? + last_login_on = user.last_login_on.nil? ? '' : user.last_login_on.to_s + user.update_column(:last_login_on, Time.now) + end + [user, last_login_on] + rescue => text + raise text + end + + + def self.try_to_autologin(key) + user = Token.find_active_user('autologin', key, Setting.autologin.to_i) + if user + user.update_column(:last_login_on, Time.now) + user + end + end + + def self.name_formatter(formatter = nil) + USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname] + end + + # Returns an array of fields names than can be used to make an order statement for users + # according to how user names are displayed + # Examples: + # + # User.fields_for_order_statement => ['users.login', 'users.id'] + # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id'] + def self.fields_for_order_statement(table=nil) + table ||= table_name + name_formatter[:order].map {|field| "#{table}.#{field}"} + end + + # Return user's full name for display + def realname(formatter = nil) + f = self.class.name_formatter(formatter) + if formatter + eval('"' + f[:string] + '"') + else + @name ||= eval('"' + f[:string] + '"') + end + end + + def nickname(formatter = nil) + login + end + + def name(formatter = nil) + login + end + + def active? + self.status == STATUS_ACTIVE + end + + def registered? + self.status == STATUS_REGISTERED + end + + def locked? + self.status == STATUS_LOCKED + end + + def activate + self.status = STATUS_ACTIVE + end + + def register + self.status = STATUS_REGISTERED + end + + def lock + self.status = STATUS_LOCKED + end + + def activate! + update_attribute(:status, STATUS_ACTIVE) + end + + def register! + update_attribute(:status, STATUS_REGISTERED) + end + + def lock! + update_attribute(:status, STATUS_LOCKED) + end + + # Returns true if +clear_password+ is the correct user's password, otherwise false + def check_password?(clear_password) + if auth_source_id.present? + auth_source.authenticate(self.login, clear_password) + else + User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password + end + end + def check_password1?(clear_password) + + clear_password == hashed_password + + end + # Generates a random salt and computes hashed_password for +clear_password+ + # The hashed password is stored in the following form: SHA1(salt + SHA1(password)) + def salt_password(clear_password) + self.salt = User.generate_salt + self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}") + end + + # Does the backend storage allow this user to change their password? + def change_password_allowed? + return true if auth_source.nil? + return auth_source.allow_password_changes? + end + + # Generate and set a random password. Useful for automated user creation + # Based on Token#generate_token_value + # + def random_password + chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + password = '' + 40.times { |i| password << chars[rand(chars.size-1)] } + self.password = password + self.password_confirmation = password + self + end + + def pref + self.preference ||= UserPreference.new(:user => self) + end + + def time_zone + @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone]) + end + + def wants_comments_in_reverse_order? + self.pref[:comments_sorting] == 'desc' + end + + def wants_notificationcomments_in_reverse_order? + self.pref[:notificationcomments_sorting] == 'desc' + end + # Return user's RSS key (a 40 chars long string), used to access feeds + def rss_key + if rss_token.nil? + create_rss_token(:action => 'feeds') + end + rss_token.value + end + + # Return user's API key (a 40 chars long string), used to access the API + def api_key + if api_token.nil? + create_api_token(:action => 'api') + end + api_token.value + end + + # Return an array of project ids for which the user has explicitly turned mail notifications on + def notified_projects_ids + @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id) + end + + def notified_project_ids=(ids) + Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id]) + Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty? + @notified_projects_ids = nil + notified_projects_ids + end + + def valid_notification_options + self.class.valid_notification_options(self) + end + + # Only users that belong to more than 1 project can select projects for which they are notified + def self.valid_notification_options(user=nil) + # Note that @user.membership.size would fail since AR ignores + # :include association option when doing a count + if user.nil? || user.memberships.length < 1 + MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'} + else + MAIL_NOTIFICATION_OPTIONS + end + end + + # Find a user account by matching the exact login and then a case-insensitive + # version. Exact matches will be given priority. + #通过用户名查找相应的用户,若没有匹配到,则不区分大小写进行查询 + #修改:不再匹配不区分大小写情况 -zjc + def self.find_by_login(login) + if login.present? + login = login.to_s + # First look for an exact match + user = where(:login => login).all.detect {|u| u.login == login} + #unless user + # # Fail over to case-insensitive if none was found + # user = where("LOWER(login) = ?", login.downcase).first + #end + user + end + end + + def self.find_by_rss_key(key) + Token.find_active_user('feeds', key) + end + + def self.find_by_api_key(key) + Token.find_active_user('api', key) + end + + # Makes find_by_mail case-insensitive + def self.find_by_mail(mail) + where("LOWER(mail) = ?", mail.to_s.downcase).first + end + + # Returns true if the default admin account can no longer be used + def self.default_admin_account_changed? + !User.active.find_by_login("admin").try(:check_password?, "admin") + end + + def to_s + name + end + + CSS_CLASS_BY_STATUS = { + STATUS_ANONYMOUS => 'anon', + STATUS_ACTIVE => 'active', + STATUS_REGISTERED => 'registered', + STATUS_LOCKED => 'locked' + } + + def css_classes + "user #{CSS_CLASS_BY_STATUS[status]}" + end + + # Returns the current day according to user's time zone + def today + if time_zone.nil? + Date.today + else + Time.now.in_time_zone(time_zone).to_date + end + end + + # Returns the day of +time+ according to user's time zone + def time_to_date(time) + if time_zone.nil? + time.to_date + else + time.in_time_zone(time_zone).to_date + end + end + + def logged? + true + end + + def anonymous? + !logged? + end + + # Returns user's membership for the given project + # or nil if the user is not a member of project + def membership(project) + project_id = project.is_a?(Project) ? project.id : project + + @membership_by_project_id ||= Hash.new {|h, project_id| + h[project_id] = memberships.where(:project_id => project_id).first + } + @membership_by_project_id[project_id] + end + + def coursemembership(course) + course_id = course.is_a?(Course) ? course.id : course + + @membership_by_course_id ||= Hash.new {|h, course_id| + h[course_id] = coursememberships.where(:course_id => course_id).first + } + @membership_by_course_id[course_id] + end + + # Return user's roles for project + def roles_for_project(project) + roles = [] + # No role on archived projects + return roles if project.nil? || project.archived? + if logged? + # Find project membership + membership = membership(project) + if membership + roles = membership.roles + else + @role_non_member ||= Role.non_member + roles << @role_non_member + end + else + @role_anonymous ||= Role.anonymous + roles << @role_anonymous + end + roles + end + + # 用户课程权限判断 + def roles_for_course(course) + roles = [] + # No role on archived courses + return roles if course.nil? || course.archived? + if logged? + # Find course membership + membership = coursemembership(course) + if membership + roles = membership.roles + else + @role_non_member ||= Role.non_member + roles << @role_non_member + end + else + @role_anonymous ||= Role.anonymous + roles << @role_anonymous + end + roles + end + + # Return true if the user is a member of project + def member_of?(project) + projects.to_a.include?(project) + end + + def member_of_course?(course) + courses.to_a.include?(course) + end + + def member_of_course_group?(course_group) + course_groups.to_a.include?(course_group) + end + # Returns a hash of user's projects grouped by roles + def projects_by_role + return @projects_by_role if @projects_by_role + + @projects_by_role = Hash.new([]) + memberships.each do |membership| + if membership.project + membership.roles.each do |role| + @projects_by_role[role] = [] unless @projects_by_role.key?(role) + @projects_by_role[role] << membership.project + end + end + end + @projects_by_role.each do |role, projects| + projects.uniq! + end + + @projects_by_role + end + + # 课程的角色权限 + def courses_by_role + return @courses_by_role if @courses_by_role + + @courses_by_role = Hash.new([]) + coursememberships.each do |membership| + if membership.course + membership.roles.each do |role| + @courses_by_role[role] = [] unless @courses_by_role.key?(role) + @courses_by_role[role] << membership.course + end + end + end + @courses_by_role.each do |role, courses| + courses.uniq! + end + + @courses_by_role + end + # Returns true if user is arg or belongs to arg + def is_or_belongs_to?(arg) + if arg.is_a?(User) + self == arg + elsif arg.is_a?(Group) + arg.users.include?(self) + else + false + end + end + + + # Return true if the user is allowed to do the specified action on a specific context + # Action can be: + # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') + # * a permission Symbol (eg. :edit_project) + # Context can be: + # * a project : returns true if user is allowed to do the specified action on this project + # * an array of projects : returns true if user is allowed on every project + # * nil with options[:global] set : check if user has at least one role allowed for this action, + # or falls back to Non Member / Anonymous permissions depending if the user is logged + def allowed_to?(action, context, options={}, &block) + if Project === context + return false unless context.allows_to?(action) + # Admin users are authorized for anything else + return true if admin? + + roles = roles_for_project(context) + return false unless roles + roles.any? {|role| + (context.is_public? || role.member?) && + role.allowed_to?(action) && + (block_given? ? yield(role, self) : true) + } + #添加课程相关的权限判断 + elsif Course === context + return false unless context.allows_to?(action) + # Admin users are authorized for anything else + return true if admin? + + roles = roles_for_course(context) + return false unless roles + roles.any? {|role| + (context.is_public? || role.member?) && + role.allowed_to?(action) && + (block_given? ? yield(role, self) : true) + } + elsif context && context.is_a?(Array) + if context.empty? + false + else + # Authorize if user is authorized on every element of the array + context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&) + end + elsif options[:global] + # Admin users are always authorized + return true if admin? + + # authorize if user has at least one role that has this permission + roles = memberships.collect {|m| m.roles}.flatten.uniq + if roles.count == 0 + roles = coursememberships.collect {|m| m.roles}.flatten.uniq + end + roles << (self.logged? ? Role.non_member : Role.anonymous) + roles.any? {|role| + role.allowed_to?(action) && + (block_given? ? yield(role, self) : true) + } + else + if admin? + return true + end + #无项目时 查看Non member(id为1)角色是否有权限执行action + Role.find('1').allowed_to?(action) + # false + end + end + + # Is the user allowed to do the specified action on any project? + # See allowed_to? for the actions and valid options. + def allowed_to_globally?(action, options, &block) + allowed_to?(action, nil, options.reverse_merge(:global => true), &block) + end + + # Returns true if the user is allowed to delete his own account + def own_account_deletable? + Setting.unsubscribe? && + (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?) + end + + safe_attributes 'login', + 'firstname', + 'lastname', + 'mail', + 'mail_notification', + 'language', + 'custom_field_values', + 'custom_fields', + 'identity_url' + + safe_attributes 'status', + 'auth_source_id', + :if => lambda {|user, current_user| current_user.admin?} + + safe_attributes 'group_ids', + :if => lambda {|user, current_user| current_user.admin? && !user.new_record?} + + # Utility method to help check if a user should be notified about an + # event. + # + # TODO: only supports Issue events currently + def notify_about?(object) + if mail_notification == 'all' + true + elsif mail_notification.blank? || mail_notification == 'none' + false + else + case object + when Issue + case mail_notification + when 'selected', 'only_my_events' + # user receives notifications for created/assigned issues on unselected projects + object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) + when 'only_assigned' + is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) + when 'only_owner' + object.author == self + end + when News + # always send to project members except when mail_notification is set to 'none' + true + #判定用户是否接受留言提醒邮件 + when JournalsForMessage + ##如果是直接留言并且留言对象是Project并且Project类型是课程(课程留言) + if !object.at_user && object.jour.class.to_s.to_sym == :Project && object.jour.project_type == 1 + #根据用户设置邮件接收模式判定当前用户是否接受邮件提醒 + is_notified_project object.jour + end + + end + end + end + + #用户是否接收project的消息提醒 + def is_notified_project arg + if arg.is_a?(Project) + case mail_notification + when 'selected' + notified_projects_ids.include?(arg.id) + when 'only_my_events' + projects.include?(arg) + when 'only_assigned' + false + when 'only_owner' + course = Course.find_by_extra(arg.identifier) + course.teacher == self + end + #勾选的项目或用户的项目 TODO:需改 + #notified_projects_ids.include?(arg) || projects.include?(arg) + else + false + end + end + + def self.current=(user) + Thread.current[:current_user] = user + end + + def self.current + Thread.current[:current_user] ||= User.anonymous + end + + # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only + # one anonymous user per database. + def self.anonymous + anonymous_user = AnonymousUser.first + if anonymous_user.nil? + anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0) + raise 'Unable to create the anonymous user.' if anonymous_user.new_record? + end + anonymous_user + end + + # Salts all existing unsalted passwords + # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password)) + # This method is used in the SaltPasswords migration and is to be kept as is + def self.salt_unsalted_passwords! + transaction do + User.where("salt IS NULL OR salt = ''").find_each do |user| + next if user.hashed_password.blank? + salt = User.generate_salt + hashed_password = User.hash_password("#{salt}#{user.hashed_password}") + User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password) + end + end + end + + protected + + def validate_password_length + # Password length validation based on setting + if !password.nil? && password.size < Setting.password_min_length.to_i + errors.add(:password, :too_short, :count => Setting.password_min_length.to_i) + end + end + private + + def act_as_activity + self.acts << Activity.new(:user_id => self.id) + end + + # Removes references that are not handled by associations + # Things that are not deleted are reassociated with the anonymous user + def remove_references_before_destroy + return if self.id.nil? + + substitute = User.anonymous + Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + Notificationcomment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id] + Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] + JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s] + JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s] + Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + # Remove private queries and keep public ones + ::Query.delete_all ['user_id = ? AND is_public = ?', id, false] + ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] + TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] + Token.delete_all ['user_id = ?', id] + Watcher.delete_all ['user_id = ?', id] + WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + end + + # Return password digest + def self.hash_password(clear_password) + Digest::SHA1.hexdigest(clear_password || "") + end + + # Returns a 128bits random salt as a hex string (32 chars long) + def self.generate_salt + Redmine::Utils.random_hex(16) + end + + + +end + +class AnonymousUser < User + validate :validate_anonymous_uniqueness, :on => :create + + def validate_anonymous_uniqueness + # There should be only one AnonymousUser in the database + errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists? + end + + def available_custom_fields + [] + end + + # Overrides a few properties + def logged?; false end + def admin; false end + def name(*args); I18n.t(:label_user_anonymous) end + def mail; nil end + def time_zone; nil end + def rss_key; nil end + + def pref + UserPreference.new(:user => self) + end + + # def member_of?(project) + # false + # end + + # Anonymous user can not be destroyed + def destroy + false + end +end diff --git a/app/models/wiki_content_observer.rb b/app/models/wiki_content_observer.rb index 187c02288..3ded4da86 100644 --- a/app/models/wiki_content_observer.rb +++ b/app/models/wiki_content_observer.rb @@ -1,34 +1,28 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class WikiContentObserver < ActiveRecord::Observer - def after_create(wiki_content) - ##by senluo - thread7=Thread.new do - Mailer.wiki_content_added(wiki_content).deliver if Setting.notified_events.include?('wiki_content_added') - end - end - - def after_update(wiki_content) - if wiki_content.text_changed? - ##by senluo - thread8=Thread.new do - Mailer.wiki_content_updated(wiki_content).deliver if Setting.notified_events.include?('wiki_content_updated') - end - end - end -end +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class WikiContentObserver < ActiveRecord::Observer + def after_create(wiki_content) + Mailer.run.wiki_content_added(wiki_content) if Setting.notified_events.include?('wiki_content_added') + end + + def after_update(wiki_content) + if wiki_content.text_changed? + Mailer.run.wiki_content_updated(wiki_content) if Setting.notified_events.include?('wiki_content_updated') + end + end +end diff --git a/app/models/zip_pack.rb b/app/models/zip_pack.rb new file mode 100644 index 000000000..df6ad593f --- /dev/null +++ b/app/models/zip_pack.rb @@ -0,0 +1,18 @@ +class ZipPack < ActiveRecord::Base + # attr_accessible :title, :body + + def self.packed?(bid_id, user_id, digests) + zip_pack = ZipPack.where(homework_id: bid_id, user_id: user_id).first + return false unless zip_pack && zip_pack.digests == digests + zip_pack + end + + def file_valid? + return false unless File.exist?(self.file_path) + Trustie::Utils.digest(self.file_path) == self.file_digest + end + + def digests + self.file_digests.split(',').sort + end +end diff --git a/app/services/courses_service.rb b/app/services/courses_service.rb index 7c816ec19..94efb375a 100644 --- a/app/services/courses_service.rb +++ b/app/services/courses_service.rb @@ -40,9 +40,9 @@ class CoursesService end @courses = courses_all.visible(current_user) if params[:name].present? - @courses_all = @courses.like(params[:name]) + @courses_all = @courses.like(params[:name]).order("created_at desc") else - @courses_all = @courses; + @courses_all = @courses.order("created_at desc"); end @courses_all course_list = [] @@ -161,10 +161,15 @@ class CoursesService #显示课程 def show_course(params,current_user) course = Course.find(params[:id]) + if course.school + work_unit = course.school.name + else + work_unit = get_user_work_unit course.teacher + end unless (course.is_public == 1 || current_user.member_of_course?(course) || current_user.admin?) raise '403' end - {:course => course,:img_url => url_to_avatar(course),:current_user_is_member => current_user.member_of_course?(course),:current_user_is_teacher => is_course_teacher(current_user,course)} + {:course => course,:work_unit => work_unit, :img_url => url_to_avatar(course),:current_user_is_member => current_user.member_of_course?(course),:current_user_is_teacher => is_course_teacher(current_user,course),:course_student_num => course ? course.student.count.to_s : 0} end #创建课程 @@ -193,7 +198,9 @@ class CoursesService @course.school_id = current_user.user_extensions.school_id @course.setup_time = params[:setup_time] @course.endup_time = params[:endup_time] - @course.class_period = params[:class_period] + @course.class_period = params[:class_period].to_i + params[:course][:is_public] ? @course.is_public = 1 : @course.is_public = 0 + params[:course][:open_student] ? @course.open_student = 1 : @course.open_student = 0 end @issue_custom_fields = IssueCustomField.sorted.all @@ -238,7 +245,7 @@ class CoursesService #course.safe_attributes = params[:course] course.time = params[:time] course.term = params[:term] - course.class_period = params[:class_period] + course.class_period = params[:class_period].to_i params[:course][:is_public] ? course.is_public = 1 : course.is_public = 0 params[:course][:open_student] ? course.open_student = 1 : course.open_student = 0 if course.save @@ -338,7 +345,7 @@ class CoursesService def course_dynamic(params,current_user) @user = User.find(params[:id]) - if !current_user.admin? && !@user.active? + if current_user.nil? && !current_user.admin? && !@user.active? raise '404' return end @@ -371,6 +378,16 @@ class CoursesService unless latest_bid.nil? latest_course_dynamics << {:type => 4,:time => latest_bid.updated_on,:message => l(:label_recently_updated_homework,:locale => get_user_language(current_user))} end + + # Time 2015-04-07 14:58:30 + # Author lizanle + # Description 添加课程创建动态 + if(User.find_by_id(CourseInfos.find_by_course_id(course.id).try(:user_id))) + create_user_name = User.find_by_id(CourseInfos.find_by_course_id(course.id).user_id).realname + latest_course_dynamics << {:type => 5,:time => course.created_at,:message =>l(:label_recently,:locale => get_user_language(current_user)) << create_user_name << l(:label_creat,:locale => get_user_language(current_user))} + end + + #每个作业中的最新留言 messages = [] course.homeworks.each do |bid| @@ -411,13 +428,59 @@ class CoursesService result end + # 课程课件 + def course_attachments params + result = [] + @course = Course.find(params[:course_id]) + @attachments = @course.attachments.order("created_on desc") + if !params[:name].nil? && params[:name] != "" + @attachments.each do |atta| + result << {:filename => atta.filename, + :description => atta.description, + :downloads => atta.downloads, + :quotes => atta.quotes.nil? ? 0 :atta.quotes } if atta.filename.include?(params[:name]) + + end + else + @attachments.each do |atta| + result << {:filename => atta.filename, + :description => atta.description, + :downloads => atta.downloads, + :quotes => atta.quotes.nil? ? 0 :atta.quotes } + + end + end + result + end + + # 课程学生列表 + def course_members params + @all_members = searchmember_by_name(student_homework_score(0,params[:course_id], 10,"desc"),params[:name]) + end + private + def searchmember_by_name members, name + #searchPeopleByRoles(project, StudentRoles) + mems = [] + if name != "" + name = name.to_s.downcase + members.each do |m| + username = m.user[:lastname].to_s.downcase + m.user[:firstname].to_s.downcase + if(m.user[:login].to_s.downcase.include?(name) || m.user.user_extensions[:student_id].to_s.downcase.include?(name) || username.include?(name)) + mems << m + end + end + else + mems = members + end + mems + end def show_homework_info course,bid,current_user,is_course_teacher author_real_name = bid.author.lastname + bid.author.firstname many_times = course.homeworks.index(bid) + 1 name = bid.name homework_count = bid.homeworks.count #已提交的作业数量 - student_questions_count = bid.commit.nil? ? 0 : bid.commit + student_questions_count = bid.journals_for_messages.where('m_parent_id IS NULL').count description = bid.description #if is_course_teacher(User.current, course) && @bid.open_anonymous_evaluation == 1 && @bid.homeworks.count >= 2 state = bid.comment_status @@ -437,7 +500,7 @@ class CoursesService many_times = course.homeworks.index(bid) + 1 name = bid.name homework_count = bid.homeworks.count #已提交的作业数量 - student_questions_count = bid.commit.nil? ? 0 : bid.commit + student_questions_count = bid.journals_for_messages.where('m_parent_id IS NULL').count description = bid.description #if is_course_teacher(User.current, course) && @bid.open_anonymous_evaluation == 1 && @bid.homeworks.count >= 2 state = bid.comment_status @@ -448,5 +511,52 @@ class CoursesService end + def student_homework_score(groupid,course_id, nums, score_sort_by) + #teachers = find_course_teachers(@course) + #start_from = start_from * nums + sql_select = "" + if groupid == 0 + if nums == 0 + sql_select = "SELECT members.*, SUM(homework_attaches.score) as score FROM members, homework_attaches + WHERE members.course_id = #{course_id} AND members.user_id in (SELECT students_for_courses.student_id FROM students_for_courses WHERE course_id = #{course_id}) AND members.user_id = homework_attaches.user_id + AND homework_attaches.bid_id in (SELECT bid_id FROM homework_for_courses WHERE course_id = #{course_id}) GROUP BY members.user_id + UNION all + SELECT members.*, 0 as score FROM members,homework_attaches,students_for_courses WHERE members.course_id = #{course_id} AND + students_for_courses.course_id = #{course_id} and members.user_id = students_for_courses.student_id AND + members.user_id NOT IN (SELECT homework_attaches.user_id FROM homework_attaches WHERE homework_attaches.bid_id in (SELECT bid_id FROM homework_for_courses WHERE course_id = #{course_id} ) + ) + GROUP BY members.user_id ORDER BY score #{score_sort_by}" + else + sql_select = "SELECT members.*, SUM(homework_attaches.score) as score FROM members, homework_attaches + WHERE members.course_id = #{course_id} AND members.user_id in (SELECT students_for_courses.student_id FROM students_for_courses WHERE course_id = #{course_id}) AND members.user_id = homework_attaches.user_id + AND homework_attaches.bid_id in (SELECT bid_id FROM homework_for_courses WHERE course_id = #{course_id}) GROUP BY members.user_id + UNION all + SELECT members.*, 0 as score FROM members,homework_attaches,students_for_courses WHERE members.course_id = #{course_id} AND + students_for_courses.course_id = #{course_id} and members.user_id = students_for_courses.student_id AND + members.user_id NOT IN (SELECT homework_attaches.user_id FROM homework_attaches WHERE homework_attaches.bid_id in (SELECT bid_id FROM homework_for_courses WHERE course_id = #{course_id} ) + ) + GROUP BY members.user_id ORDER BY score #{score_sort_by} " #limit #{start_from}, #{nums}" + + end + else + sql_select = "SELECT members.*, SUM(homework_attaches.score) as score FROM members, homework_attaches + WHERE members.course_id = #{course_id} AND members.user_id in (SELECT students_for_courses.student_id FROM students_for_courses WHERE course_id = #{course_id}) AND members.user_id = homework_attaches.user_id + and members.course_group_id = #{groupid} AND homework_attaches.bid_id in (SELECT bid_id FROM homework_for_courses WHERE course_id = #{course_id}) + GROUP BY members.user_id + UNION all + SELECT members.*, 0 as score FROM members,homework_attaches,students_for_courses WHERE members.course_id = #{course_id} + and members.course_group_id = #{groupid} AND + students_for_courses.course_id = #{course_id} and members.user_id = students_for_courses.student_id AND + members.user_id NOT IN (SELECT homework_attaches.user_id FROM homework_attaches WHERE homework_attaches.bid_id in (SELECT bid_id FROM homework_for_courses WHERE course_id = #{course_id} ) + ) + GROUP BY members.user_id ORDER BY score #{score_sort_by}" + end + sql = ActiveRecord::Base.connection() + homework_scores = Member.find_by_sql(sql_select) + sql.close() + + homework_scores + end + end \ No newline at end of file diff --git a/app/services/homework_service.rb b/app/services/homework_service.rb index f9be6f79d..d22c62a5a 100644 --- a/app/services/homework_service.rb +++ b/app/services/homework_service.rb @@ -20,7 +20,7 @@ class HomeworkService many_times = course.homeworks.index(@bid) + 1 name = @bid.name homework_count = @bid.homeworks.count #已提交的作业数量 - student_questions_count = @bid.commit.nil? ? 0 : @bid.commit + student_questions_count = @bid.journals_for_messages.where('m_parent_id IS NULL').count description = @bid.description #if is_course_teacher(User.current, course) && @bid.open_anonymous_evaluation == 1 && @bid.homeworks.count >= 2 state = @bid.comment_status @@ -282,7 +282,7 @@ class HomeworkService many_times = course.homeworks.index(@bid) + 1 name = @bid.name homework_count = @bid.homeworks.count #已提交的作业数量 - student_questions_count = @bid.commit.nil? ? 0 : @bid.commit + student_questions_count = @bid.journals_for_messages.where('m_parent_id IS NULL').count description = @bid.description #if is_course_teacher(User.current, course) && @bid.open_anonymous_evaluation == 1 && @bid.homeworks.count >= 2 state = @bid.comment_status diff --git a/app/services/users_service.rb b/app/services/users_service.rb index 39ca37d26..3a92bf8f1 100644 --- a/app/services/users_service.rb +++ b/app/services/users_service.rb @@ -44,12 +44,14 @@ class UsersService #location = get_user_location @user #{:id => @user.id, :img_url => img_url, :nickname => @user.login, :gender => gender, :work_unit => work_unit, :mail => @user.mail, :location => location, :brief_introduction => @user.user_extensions.brief_introduction} end + + # 自动注册功能 FOR:邮件邀请 def register_auto(login,mail,password) @user = User.new @user.admin = false @user.register @user.login = login - @user.mail =mail + @user.mail = mail password_confirmation = password should_confirmation_password = true if !password.blank? && !password_confirmation.blank? && should_confirmation_password @@ -59,9 +61,7 @@ class UsersService else @user.password = "" end - @user = automatically_register(@user) - if @user.id != nil ue = @user.user_extensions ||= UserExtensions.new ue.user_id = @user.id @@ -69,6 +69,7 @@ class UsersService end @user end + #显示用户 #id用户id def show_user(params) @@ -77,7 +78,7 @@ class UsersService gender = @user.user_extensions.gender.nil? ? 0 : @user.user_extensions.gender work_unit = get_user_work_unit @user location = get_user_location @user - {:id => @user.id, :img_url => img_url, :nickname => @user.login, :gender => gender, :work_unit => work_unit, :mail => @user.mail, :location => location, :brief_introduction => @user.user_extensions.brief_introduction} + {:id => @user.id, :img_url => img_url,:realname => @user.realname, :nickname => @user.login, :gender => gender, :work_unit => work_unit, :mail => @user.mail, :location => location, :brief_introduction => @user.user_extensions.brief_introduction} end #忘记密码 @@ -95,9 +96,7 @@ class UsersService # create a new token for password recovery token = Token.new(:user => user, :action => "recovery") if token.save - Thread.new do - Mailer.lost_password(token).deliver - end + Mailer.run.lost_password(token) return l(:notice_account_lost_email_sent,:locale => user.language) end end @@ -194,20 +193,22 @@ class UsersService #raise @current_user.errors.full_message #return @current_user else - raise 'wrong password' + raise l(:notice_account_wrong_password,:locale => 'zh') end @current_user end #搜索用户 def search_user params - @status = params[:status] || 1 + status = params[:status] || 1 has = { "show_changesets" => true } - scope = User.logged.status(@status) - @search_by = params[:search_by] ? params[:search_by] : "0" - scope = scope.like(params[:name],@search_by) if params[:name].present? + scope = User.logged.status(status) + watcher = User.watched_by(params[:user_id]) + watcher.push(params[:user_id]) + search_by = params[:search_by] ? params[:search_by] : "0" + scope = scope.where("id not in (?)",watcher).like(params[:name],search_by) if params[:name].present? scope end diff --git a/app/tasks/destroy_repository_task.rb b/app/tasks/destroy_repository_task.rb new file mode 100644 index 000000000..3e434422e --- /dev/null +++ b/app/tasks/destroy_repository_task.rb @@ -0,0 +1,25 @@ +#coding=utf-8 +# + +class DestroyRepositoryTask + def destroy(user_id, rep_id) + user = User.find(user_id) + repository = Repository.find(rep_id) + + Rails.logger.info "start delete repository #{user} #{repository}" + @root_path=RepositoriesHelper::ROOT_PATH + @repo_name=user.login.to_s+"_"+repository.identifier.to_s + @repository_name=user.login.to_s+"/"+repository.identifier.to_s+".git" + @middle=user.login.to_s+"_"+repository.identifier.to_s+"-write:" + repository.destroy + if(repository.type=="Repository::Git") + Rails.logger.info "destory the repository value"+"root path"+@root_path+"repo_name"+@repo_name+ + "repository_name"+@repository_name+"user group"+@middle + system "sed -i /"+@repo_name+"/{d} "+@root_path+"htdocs/user.passwd" + system "sed -i /"+@middle+"/{d} "+@root_path+"htdocs/group.passwd" + system "rm -r "+@root_path+"htdocs/"+@repository_name + end + end + + handle_asynchronously :destroy,:queue => 'repository' +end diff --git a/app/views/account/login.html.erb b/app/views/account/login.html.erb index 4175282b3..bb289eb03 100644 --- a/app/views/account/login.html.erb +++ b/app/views/account/login.html.erb @@ -68,7 +68,7 @@<% if Setting.autologin? %> <% end %> diff --git a/app/views/applied_project/applied_join_project.js.erb b/app/views/applied_project/applied_join_project.js.erb index 3f4f6aff7..ab5cdd484 100644 --- a/app/views/applied_project/applied_join_project.js.erb +++ b/app/views/applied_project/applied_join_project.js.erb @@ -1,11 +1,11 @@ <% if @status == 0%> - alert("您申请的项目不存在"); + alert("<%= l('project.join.tips.notexist') %>"); <% elsif @status == 1%> - alert("请勿重复申请加入该项目"); + alert("<%= l('project.join.tips.repeat') %>"); <% elsif @status == 2%> - alert("申请成功"); + alert("<%= l('project.join.tips.success') %>"); <% elsif @status == 3%> - alert("您已加入该项目"); + alert("<%= l('project.join.tips.has') %>"); <%else%> - alert("申请失败"); + alert("<%= l('project.join.tips.fail') %>"); <%end%> \ No newline at end of file diff --git a/app/views/attachments/_form.html.erb b/app/views/attachments/_form.html.erb index b026fead2..2e8800617 100644 --- a/app/views/attachments/_form.html.erb +++ b/app/views/attachments/_form.html.erb @@ -1,11 +1,12 @@ + <% if defined?(container) && container && container.saved_attachments %> <% container.attachments.each_with_index do |attachment, i| %>diff --git a/app/views/attachments/_form_course.html.erb b/app/views/attachments/_form_course.html.erb index 39c760690..1e81aaf65 100644 --- a/app/views/attachments/_form_course.html.erb +++ b/app/views/attachments/_form_course.html.erb @@ -38,7 +38,7 @@ <%if is_float%> @@ -26,9 +26,7 @@ <%= h(truncate(" - #{attachment.description}", length: options[:length] ? options[:length]:15, omission: '...')) unless attachment.description.blank? %> - ( - <%= number_to_human_size attachment.filesize %>) - + (<%= number_to_human_size attachment.filesize , :precision => 0 %>) <% if options[:deletable] %> <% if attachment.container_type == 'HomeworkAttach' %> <%= link_to image_tag('delete.png'), {:controller => 'attachments', :action => 'delete_homework', :id => attachment.id}, @@ -62,7 +60,7 @@ <% if defined?(thumbnails) && thumbnails %> <% images = attachments.select(&:thumbnailable?) %> <% if images.any? %> -++ + +<% images.each do |attachment| %><% content_for :header_tags do %> <%= javascript_include_tag 'avatars' %> diff --git a/app/views/avatar/_avatar_preview.html.erb b/app/views/avatar/_avatar_preview.html.erb new file mode 100644 index 000000000..f7a014c64 --- /dev/null +++ b/app/views/avatar/_avatar_preview.html.erb @@ -0,0 +1,66 @@ + + +<%= thumbnail_tag(attachment) %><% end %> diff --git a/app/views/attachments/_new_form.html.erb b/app/views/attachments/_new_form.html.erb index 696d223fd..38b471897 100644 --- a/app/views/attachments/_new_form.html.erb +++ b/app/views/attachments/_new_form.html.erb @@ -25,13 +25,13 @@ <% project = project %>+++
+ diff --git a/app/views/avatar/_new_avatar_form.html.erb b/app/views/avatar/_new_avatar_form.html.erb index 8ca08c766..e341bc83a 100644 --- a/app/views/avatar/_new_avatar_form.html.erb +++ b/app/views/avatar/_new_avatar_form.html.erb @@ -2,10 +2,10 @@ <%= image_tag(url_to_avatar(source), id: "avatar_image", :width =>"60", :height =>"60",:alt=>"上传图片")%> <%#= link_to l(:button_delete_file),{:controller => :avatar,:action => :delete_image,:remote=>true,:source_type=> source.class,:source_id=>source.id},:confirm => l(:text_are_you_sure), :method => :post, :class => "upbtn fl" %> -上传图片 +<%= l(:button_upload_photo) %> <%= file_field_tag 'avatar[image]', :id => nil, - :class => 'upload_file ', + :class => 'upload_file', :size => "1", :multiple => false, :onchange => 'addInputAvatar(this);', diff --git a/app/views/bids/_alert_anonyoms.html.erb b/app/views/bids/_alert_anonyoms.html.erb index 6467b3f27..4da6e0a10 100644 --- a/app/views/bids/_alert_anonyoms.html.erb +++ b/app/views/bids/_alert_anonyoms.html.erb @@ -39,37 +39,37 @@ <% if @bid.comment_status == 0%>开启匿评功能
- 开启匿评后学生将不能对作业进行 + 开启匿评后学生将不能对作品进行 修改、删除 等操作,目前有 <%= totle_size%>个 学生,共提交了 <%= cur_size %> - 份作业,占 + 份作品,占 <%= percent %>%, 是否确定开启匿评?
<% elsif @bid.comment_status == 1 %>关闭匿评功能
- 关闭匿评后学生将不能对作业进行 + 关闭匿评后学生将不能对作品进行 匿评 - ,且作业列表将会 + ,且作品列表将会 公开, 目前分配了 <%= totle_size%>份 - 匿评作业,已评了 + 匿评作品,已评了 <%= cur_size %> - 份作业,占 + 份作品,占 <%= percent %>%, 是否确定关闭匿评?
<% end %> diff --git a/app/views/bids/_bid_homework_show.html.erb b/app/views/bids/_bid_homework_show.html.erb index 6cb975055..2f2dbcd07 100644 --- a/app/views/bids/_bid_homework_show.html.erb +++ b/app/views/bids/_bid_homework_show.html.erb @@ -10,7 +10,7 @@ var hour=Math.floor((leftsecond-day1*24*60*60)/3600); var minute=Math.floor((leftsecond-day1*24*60*60-hour*3600)/60); var second=Math.floor(leftsecond-day1*24*60*60-hour*3600-minute*60); - $("#"+divname).html("作业提交还剩 : " + $("#"+divname).html("作品提交还剩 : " +day1+" 天 " +hour+" 时 " +minute+" 分 " diff --git a/app/views/bids/_homework.html.erb b/app/views/bids/_homework.html.erb index 05bec9874..0cb1ff7cc 100644 --- a/app/views/bids/_homework.html.erb +++ b/app/views/bids/_homework.html.erb @@ -1,16 +1,5 @@ <%= render_flash_messages %> <%= @jours_count %>) -- <%= link_to "作品打包下载", zipdown_assort_path(obj_class: @bid.class, obj_id: @bid), class: "tb_all" unless @bid.homeworks.empty? %> + <%= link_to "作品打包下载", zipdown_assort_path(obj_class: @bid.class, obj_id: @bid, format: :json), + remote: true, class: "tb_all" unless @bid.homeworks.empty? %> <% else %> @@ -57,12 +57,24 @@<%= render :partial => 'homework_attach/homeworks_list', :locals => {:homeworks => @homework_list, - :homework_count => @obj_count, :remote => false, :not_batch_homework => @not_batch_homework,:is_student_batch_homework => @is_student_batch_homework}%>diff --git a/app/views/bids/_homework_list.html.erb b/app/views/bids/_homework_list.html.erb index 6572ee2c9..f11d9d3af 100644 --- a/app/views/bids/_homework_list.html.erb +++ b/app/views/bids/_homework_list.html.erb @@ -22,8 +22,8 @@ <%= link_to "留言", get_homework_jours_homework_attach_index_path(:bid_id => @bid.id), {:remote => true}%> (