diff --git a/app/assets/javascripts/forums.js b/app/assets/javascripts/forums.js
new file mode 100644
index 000000000..dee720fac
--- /dev/null
+++ b/app/assets/javascripts/forums.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/javascripts/memos.js b/app/assets/javascripts/memos.js
new file mode 100644
index 000000000..dee720fac
--- /dev/null
+++ b/app/assets/javascripts/memos.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/stylesheets/forums.scss b/app/assets/stylesheets/forums.scss
new file mode 100644
index 000000000..fafd631e1
--- /dev/null
+++ b/app/assets/stylesheets/forums.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the forums controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index f01ad4b6b..c76ca51a7 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -38,6 +38,7 @@ class ApplicationController < ActionController::Base
def user_course_identity
@user_course_identity = current_user.course_identity(@course)
if @user_course_identity > Course::STUDENT && @course.is_public == 0
+ tip_exception(401, "..") unless User.current.logged?
tip_exception(409, "您没有权限进入")
end
uid_logger("###############user_course_identity:#{@user_course_identity}")
@@ -582,4 +583,8 @@ class ApplicationController < ActionController::Base
def render_parameter_missing
render json: { status: -1, message: '参数缺失' }
end
+
+ def set_export_cookies
+ cookies[:fileDownload] = true
+ end
end
diff --git a/app/controllers/concerns/paginate_helper.rb b/app/controllers/concerns/paginate_helper.rb
index 34740eb5d..bbe84a348 100644
--- a/app/controllers/concerns/paginate_helper.rb
+++ b/app/controllers/concerns/paginate_helper.rb
@@ -1,7 +1,7 @@
module PaginateHelper
def paginate(objs, **opts)
page = params[:page].to_i <= 0 ? 1 : params[:page].to_i
- per_page = params[:per_page].to_i > 0 ? params[:per_page].to_i : opts[:per_page] || 20
+ per_page = params[:per_page].to_i > 0 && params[:per_page].to_i < 50 ? params[:per_page].to_i : opts[:per_page] || 20
Kaminari.paginate_array(objs).page(page).per(per_page)
end
diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb
index bdb367111..9ae909dbc 100644
--- a/app/controllers/courses_controller.rb
+++ b/app/controllers/courses_controller.rb
@@ -1029,8 +1029,8 @@ class CoursesController < ApplicationController
normal_status(-1,"课堂暂时没有学生")
else
member_to_xlsx(@course, @all_members, @c_homeworks, @c_exercises, @c_tasks)
- filename_ = "#{current_user.real_name}_#{@course.name}_全部成绩"
- render xlsx: "#{format_sheet_name filename_.strip.first(30)}",template: "courses/export_member_scores_excel.xlsx.axlsx",
+ filename_ = "#{current_user.real_name}_#{@course.name}_全部成绩_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
+ render xlsx: "#{format_sheet_name filename_.strip}",template: "courses/export_member_scores_excel.xlsx.axlsx",
locals: {course_info:@course_info, activity_level:@user_activity_level,
course_scores:@course_user_scores,shixun_works:@shixun_work_arrays,
common_works:@common_work_arrays,group_works:@group_work_arrays,task_works:@task_work_arrays,
diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb
index 03c943f8d..915a4e22c 100644
--- a/app/controllers/exercises_controller.rb
+++ b/app/controllers/exercises_controller.rb
@@ -1256,13 +1256,15 @@ class ExercisesController < ApplicationController
normal_status(-1,"试卷未发布")
elsif (@exercise_users_size == 0) || ( @export_ex_users&.exercise_user_committed.size == 0)
normal_status(-1,"暂无用户提交")
+ elsif params[:export].present? && params[:export]
+ normal_status(0,"正在下载中")
else
respond_to do |format|
format.xlsx{
get_export_users(@exercise,@course,@export_ex_users)
exercise_export_name_ =
"#{current_user.real_name}_#{@course.name}_#{@exercise.exercise_name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
- render xlsx: "#{exercise_export_name_.strip.first(30)}",template: "exercises/exercise_lists.xlsx.axlsx",locals: {table_columns:@table_columns,exercise_users:@user_columns}
+ render xlsx: "#{exercise_export_name_.strip}",template: "exercises/exercise_lists.xlsx.axlsx",locals: {table_columns:@table_columns,exercise_users:@user_columns}
}
end
end
@@ -1281,7 +1283,11 @@ class ExercisesController < ApplicationController
@exercise_questions = @exercise.exercise_questions.includes(:exercise_choices).order("question_number ASC")
filename_ = "#{@exercise.user.real_name}_#{@course.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.pdf"
stylesheets = "#{Rails.root}/app/templates/exercise_export/exercise_export.css"
- render pdf: 'exercise_export/blank_exercise', filename: filename_, stylesheets: stylesheets
+ if params[:export].present? && params[:export]
+ normal_status(0,"正在下载中")
+ else
+ render pdf: 'exercise_export/blank_exercise', filename: filename_, stylesheets: stylesheets
+ end
end
#空白试卷预览页面,仅供测试使用,无其他任何用途
diff --git a/app/controllers/forums_controller.rb b/app/controllers/forums_controller.rb
new file mode 100644
index 000000000..82573fdd0
--- /dev/null
+++ b/app/controllers/forums_controller.rb
@@ -0,0 +1,2 @@
+class ForumsController < ApplicationController
+end
diff --git a/app/controllers/graduation_tasks_controller.rb b/app/controllers/graduation_tasks_controller.rb
index ab99e8d86..aeb5c409d 100644
--- a/app/controllers/graduation_tasks_controller.rb
+++ b/app/controllers/graduation_tasks_controller.rb
@@ -137,7 +137,7 @@ class GraduationTasksController < ApplicationController
format.xlsx{
graduation_work_to_xlsx(@work_excel,@task,current_user)
task_export_name_ = "#{current_user.real_name}_#{@course.name}_#{@task.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
- render xlsx: "#{task_export_name_.strip.first(30)}",template: "graduation_tasks/tasks_list.xlsx.axlsx",locals: {table_columns:@head_cells_column, task_users:@task_cells_column}
+ render xlsx: "#{task_export_name_.strip}",template: "graduation_tasks/tasks_list.xlsx.axlsx",locals: {table_columns:@head_cells_column, task_users:@task_cells_column}
}
end
end
diff --git a/app/controllers/graduation_topics_controller.rb b/app/controllers/graduation_topics_controller.rb
index 754b472c5..96816c2a6 100644
--- a/app/controllers/graduation_topics_controller.rb
+++ b/app/controllers/graduation_topics_controller.rb
@@ -271,7 +271,7 @@ class GraduationTopicsController < ApplicationController
students = course.students.joins(user: :user_extension).order("user_extensions.student_id")
graduation_topic_to_xlsx(students,course)
topic_export_name_ = "#{current_user.real_name}_#{course.name}_毕设选题_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
- render xlsx: "#{topic_export_name_.strip.first(30)}",template: "graduation_topics/export.xlsx.axlsx",locals: {table_columns:@topic_head_cells,topic_users:@topic_body_cells}
+ render xlsx: "#{topic_export_name_.strip}",template: "graduation_topics/export.xlsx.axlsx",locals: {table_columns:@topic_head_cells,topic_users:@topic_body_cells}
rescue Exception => e
uid_logger(e.message)
missing_template
diff --git a/app/controllers/homework_commons_controller.rb b/app/controllers/homework_commons_controller.rb
index 5cccb3ee7..f7906ecda 100644
--- a/app/controllers/homework_commons_controller.rb
+++ b/app/controllers/homework_commons_controller.rb
@@ -207,9 +207,12 @@ class HomeworkCommonsController < ApplicationController
tip_exception(403, "无权限操作")
elsif @work_excel.blank? || @work_excel.size == 0
normal_status(-1,"暂无用户提交!")
+ elsif params[:export].present? && params[:export]
+ normal_status(0,"正在下载中")
else
respond_to do |format|
format.xlsx{
+ set_export_cookies
student_work_to_xlsx(@work_excel,@homework)
exercise_export_name = "#{current_user.real_name}_#{@course.name}_#{@homework.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
render xlsx: "#{exercise_export_name.strip}",template: "homework_commons/works_list.xlsx.axlsx",locals:
@@ -229,12 +232,17 @@ class HomeworkCommonsController < ApplicationController
end
if status == 0
- respond_to do |format|
- format.zip{
- zipfile = zip_homework_common @homework, zip_works
- file = decode64(zipfile[0][:base64file])
- send_file "#{OUTPUT_FOLDER}/#{file}", filename: filename_for_content_disposition(file), type: 'application/zip'
- }
+ if params[:export].present? && params[:export]
+ normal_status(0,"正在下载中")
+ else
+ respond_to do |format|
+ format.zip{
+ set_export_cookies
+ zipfile = zip_homework_common @homework, zip_works
+ file = decode64(zipfile[0][:base64file])
+ send_file "#{OUTPUT_FOLDER}/#{file}", filename: filename_for_content_disposition(file), type: 'application/zip'
+ }
+ end
end
else
normal_status(status, status == -2 ? "500M" : "无附件可下载")
diff --git a/app/controllers/memos_controller.rb b/app/controllers/memos_controller.rb
index 723f2e9a4..869d787db 100644
--- a/app/controllers/memos_controller.rb
+++ b/app/controllers/memos_controller.rb
@@ -66,9 +66,7 @@ class MemosController < ApplicationController
# GET /memos/new
def new
- @csrf_token = session[:_csrf_toke] ||= SecureRandom.base64(32)
@tag_list = TagRepertoire.field_for_list.order("name asc")
-
end
# GET /memos/1/edit
diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb
index 4348b3bfc..87435f743 100644
--- a/app/controllers/polls_controller.rb
+++ b/app/controllers/polls_controller.rb
@@ -950,7 +950,7 @@ class PollsController < ApplicationController
format.xlsx{
polls_export_name_ = "#{current_user.real_name}_#{@course.name}_#{@poll.polls_name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
polls_user_commit = poll_commit_result(@poll,@poll_export_questions,@poll_users,@poll_commit_ids)
- render xlsx: "#{polls_export_name_.strip.first(30)}",template: "polls/commit_result.xlsx.axlsx",locals: {polls_user_commit:polls_user_commit}
+ render xlsx: "#{polls_export_name_.strip}",template: "polls/commit_result.xlsx.axlsx",locals: {polls_user_commit:polls_user_commit}
}
end
end
diff --git a/app/controllers/shixuns_controller.rb b/app/controllers/shixuns_controller.rb
index 6c3f34a60..26effd12e 100644
--- a/app/controllers/shixuns_controller.rb
+++ b/app/controllers/shixuns_controller.rb
@@ -714,6 +714,7 @@ class ShixunsController < ApplicationController
end
end
rescue Exception => e
+ logger.info("shixun_exec error: #{e.message}")
if e.message != "ActiveRecord::RecordInvalid"
logger.error("##########project_fork error #{e.message}")
@myshixun.destroy!
diff --git a/app/controllers/student_works_controller.rb b/app/controllers/student_works_controller.rb
index f9f4717a6..a5d46e46a 100644
--- a/app/controllers/student_works_controller.rb
+++ b/app/controllers/student_works_controller.rb
@@ -457,8 +457,8 @@ class StudentWorksController < ApplicationController
@myself_eff = @echart_data[:efficiency_list].find { |item| item.last == @user.id }
@myself_consume = @echart_data[:consume_list].find { |item| item.last == @user.id }
- filename_ = "实训报告_#{@shixun&.name}_#{@use&.real_name}"
- filename = Base64.urlsafe_encode64(filename_.strip.first(30))
+ filename_ = "#{@use&.student_id}_#{@use&.real_name}_#{@shixun&.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
+ filename = Base64.urlsafe_encode64(filename_.strip)
stylesheets = %w(shixun_work/shixun_work.css shared/codemirror.css)
render pdf: 'shixun_work/shixun_work', filename: filename, stylesheets: stylesheets
end
diff --git a/app/controllers/users/base_controller.rb b/app/controllers/users/base_controller.rb
index afc03ee13..fd138a182 100644
--- a/app/controllers/users/base_controller.rb
+++ b/app/controllers/users/base_controller.rb
@@ -26,9 +26,22 @@ class Users::BaseController < ApplicationController
render_forbidden
end
+ def page_value
+ params[:page].to_i <= 0 ? 1 : params[:page].to_i
+ end
+
+ def per_page_value
+ params[:per_page].to_i > 0 && params[:per_page].to_i < 50 ? params[:per_page].to_i : 20
+ end
+ alias_method :limit_value, :per_page_value
+
+ def offset_value
+ (page_value - 1) * limit_value
+ end
+
def paginate(objs, **opts)
- page = params[:page].to_i <= 0 ? 1 : params[:page].to_i
- per_page = params[:per_page].to_i > 0 ? params[:per_page].to_i : 20
+ page = page_value
+ per_page = per_page_value
return Kaminari.paginate_array(objs).page(page).per(per_page) unless observed_logged_user? && opts[:special]
diff --git a/app/controllers/users/private_message_details_controller.rb b/app/controllers/users/private_message_details_controller.rb
new file mode 100644
index 000000000..486d23d7f
--- /dev/null
+++ b/app/controllers/users/private_message_details_controller.rb
@@ -0,0 +1,23 @@
+class Users::PrivateMessageDetailsController < Users::BaseController
+ before_action :private_user_resources!
+
+ after_action :update_message_status, only: [:show]
+
+ def show
+ messages = observed_user.private_messages.without_deleted.where(target: target_user)
+
+ @count = messages.count
+ @messages = messages.order(send_time: :asc).includes(sender: :user_extension)
+ end
+
+ private
+
+ def target_user
+ @_target_user ||= User.find(params[:target_id])
+ end
+
+ # 置为已读
+ def update_message_status
+ observed_user.private_messages.only_unread.where(target: target_user).update_all(status: 1)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users/private_messages_controller.rb b/app/controllers/users/private_messages_controller.rb
new file mode 100644
index 000000000..b80b6152b
--- /dev/null
+++ b/app/controllers/users/private_messages_controller.rb
@@ -0,0 +1,39 @@
+class Users::PrivateMessagesController < Users::BaseController
+ before_action :private_user_resources!
+ after_action :update_onclick_time!, only: [:index]
+
+ def index
+ @count = observed_user.private_messages.without_deleted.group(:target_id).count.count
+
+ subquery = observed_user.private_messages.without_deleted.order(send_time: :desc).to_sql
+ query = "SELECT subquery.*, COUNT(*) message_count FROM (#{subquery}) subquery "\
+ "GROUP BY subquery.target_id ORDER BY subquery.send_time desc LIMIT #{limit_value} OFFSET #{offset_value}"
+ @messages = PrivateMessage.select('*').from("(#{query}) AS query").includes(target: :user_extension)
+ end
+
+ def create
+ receiver = User.find_by(id: params[:target_id])
+ return render_error('用户未找到') if receiver.blank?
+
+ @message = PrivateMessages::CreateService.call(observed_user, receiver, create_params)
+ rescue PrivateMessages::CreateService::Error => ex
+ render_error(ex.message)
+ end
+
+ def destroy
+ message = observed_user.private_messages.without_deleted.find(params[:id])
+ message.destroy!
+
+ render_ok
+ end
+
+ private
+
+ def update_onclick_time!
+ current_user.onclick_time.touch(:onclick_time)
+ end
+
+ def create_params
+ params.permit(:content)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users/recent_contacts_controller.rb b/app/controllers/users/recent_contacts_controller.rb
new file mode 100644
index 000000000..bc4b8ea8f
--- /dev/null
+++ b/app/controllers/users/recent_contacts_controller.rb
@@ -0,0 +1,8 @@
+class Users::RecentContactsController < Users::BaseController
+ before_action :private_user_resources!
+
+ def index
+ contacts = observed_user.recent_contacts.distinct
+ @contacts = contacts.order('private_messages.created_at DESC').limit(10).includes(:user_extension)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users/unread_message_infos_controller.rb b/app/controllers/users/unread_message_infos_controller.rb
new file mode 100644
index 000000000..7abd36304
--- /dev/null
+++ b/app/controllers/users/unread_message_infos_controller.rb
@@ -0,0 +1,12 @@
+class Users::UnreadMessageInfosController < Users::BaseController
+ before_action :private_user_resources!
+
+ def show
+ click_time = observed_user.click_time
+
+ unread_tiding_count = observed_user.tidings.where('created_at > ?', click_time).count
+ unread_message_count = observed_user.private_messages.only_unread.group(:target_id).count.count
+
+ render_ok(unread_tiding_count: unread_tiding_count, unread_message_count: unread_message_count)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users_for_private_messages_controller.rb b/app/controllers/users_for_private_messages_controller.rb
new file mode 100644
index 000000000..bbd5682a1
--- /dev/null
+++ b/app/controllers/users_for_private_messages_controller.rb
@@ -0,0 +1,17 @@
+class UsersForPrivateMessagesController < ApplicationController
+ before_action :require_login, :check_auth
+
+ def index
+ users = User.active.where.not(id: current_user.id)
+
+ keyword = params[:keyword].to_s.strip
+ if keyword.blank?
+ @users = []
+ return
+ end
+
+ users = users.where('LOWER(concat(lastname, firstname, nickname)) LIKE ?', "%#{keyword}%")
+
+ @users = users.limit(10).includes(:user_extension)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/zips_controller.rb b/app/controllers/zips_controller.rb
index f4822b96b..b1f7804a9 100644
--- a/app/controllers/zips_controller.rb
+++ b/app/controllers/zips_controller.rb
@@ -8,7 +8,13 @@ class ZipsController < ApplicationController
def shixun_report
service = BatchExportShixunReportService.new(@homework, @all_student_works)
filename_ = filename_for_content_disposition(service.filename)
- send_file service.zip, filename: filename_, type: 'application/zip'
+ if params[:export].present? && params[:export]
+ normal_status(0,"正在下载中")
+ else
+ set_export_cookies
+ send_file service.zip, filename: filename_, type: 'application/zip'
+ end
+
rescue BatchExportShixunReportService::Error => ex
normal_status(-1, ex.message)
end
@@ -18,7 +24,11 @@ class ZipsController < ApplicationController
exercises = ExportExercisesService.new(@exercise,@ex_users,@request_url)
file_name_ = filename_for_content_disposition(exercises.filename)
- send_file exercises.ex_zip, filename: file_name_, type: 'application/zip'
+ if params[:export].present? && params[:export]
+ normal_status(0,"正在下载中")
+ else
+ send_file exercises.ex_zip, filename: file_name_, type: 'application/zip'
+ end
rescue Exception => e
normal_status(-1, e.message)
end
diff --git a/app/decorators/private_message_decorator.rb b/app/decorators/private_message_decorator.rb
new file mode 100644
index 000000000..6db17acf3
--- /dev/null
+++ b/app/decorators/private_message_decorator.rb
@@ -0,0 +1,9 @@
+module PrivateMessageDecorator
+ extend ApplicationDecorator
+
+ display_time_method :send_time
+
+ def unread?
+ status.zero?
+ end
+end
\ No newline at end of file
diff --git a/app/helpers/export_helper.rb b/app/helpers/export_helper.rb
index 742deb85b..5d36c465f 100644
--- a/app/helpers/export_helper.rb
+++ b/app/helpers/export_helper.rb
@@ -418,7 +418,7 @@ module ExportHelper
end
end
- out_file_name = "#{Time.now.to_i}_#{homework_common.name}.zip"
+ out_file_name = "作品附件_#{homework_common&.course&.name}_#{homework_common.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.zip"
out_file_name.gsub!(" ", "-")
out_file_name.gsub!("/", "_")
out_file = find_or_pack(homework_common, homework_common.user_id, digests.sort){
@@ -496,8 +496,8 @@ module ExportHelper
def make_zip_name(work, file_name="")
Rails.logger.info("######################file_name: #{file_name}")
- name = file_name === "" ? "" : (file_name[0, file_name.rindex('.')]+"_")
- "#{name}#{work.user.real_name}_#{((work.user.student_id.nil?) ? "" : work.user.student_id)}"
+ # name = file_name === "" ? "" : (file_name[0, file_name.rindex('.')]+"_")
+ "#{work&.user&.student_id}_#{work&.user&.real_name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
end
def zipping(zip_name_refer, files_paths, output_path, is_attachment=false, not_exist_file=[])
diff --git a/app/helpers/forums_helper.rb b/app/helpers/forums_helper.rb
new file mode 100644
index 000000000..2e531fd46
--- /dev/null
+++ b/app/helpers/forums_helper.rb
@@ -0,0 +1,2 @@
+module ForumsHelper
+end
diff --git a/app/helpers/memos_helper.rb b/app/helpers/memos_helper.rb
index 7df887a4b..434d9b66a 100644
--- a/app/helpers/memos_helper.rb
+++ b/app/helpers/memos_helper.rb
@@ -1,2 +1,6 @@
module MemosHelper
+
+ def forum_list
+ [{id: 5, name: "技术分享"}, {id: 3, name: "操作指南"}]
+ end
end
diff --git a/app/models/forum.rb b/app/models/forum.rb
new file mode 100644
index 000000000..88aafa676
--- /dev/null
+++ b/app/models/forum.rb
@@ -0,0 +1,2 @@
+class Forum < ApplicationRecord
+end
diff --git a/app/models/memo.rb b/app/models/memo.rb
index 610a7684d..4dc153c81 100644
--- a/app/models/memo.rb
+++ b/app/models/memo.rb
@@ -1,7 +1,9 @@
class Memo < ApplicationRecord
include Searchable::Memo
- has_many :memo_tag_repertoires, :dependent => :destroy
+ belongs_to :forum, touch: true
+
+ has_many :memo_tag_repertoires, dependent: :destroy
has_many :tag_repertoires, :through => :memo_tag_repertoires
has_many :praise_tread, as: :praise_tread_object, dependent: :destroy
diff --git a/app/models/private_message.rb b/app/models/private_message.rb
index 1db4c9f66..640e48db7 100644
--- a/app/models/private_message.rb
+++ b/app/models/private_message.rb
@@ -1,3 +1,9 @@
class PrivateMessage < ApplicationRecord
belongs_to :user
+ belongs_to :target, class_name: "User"
+ belongs_to :sender, class_name: "User"
+ belongs_to :receiver, class_name: "User"
+
+ scope :without_deleted, -> { where.not(status: 2) }
+ scope :only_unread, -> { where(status: 0) }
end
diff --git a/app/models/tiding.rb b/app/models/tiding.rb
index 3ef625c57..90abdf809 100644
--- a/app/models/tiding.rb
+++ b/app/models/tiding.rb
@@ -13,9 +13,14 @@ class Tiding < ApplicationRecord
value = container.try(:identifier)
end
+ if value.blank? && parent_container_type && Object.const_defined?(parent_container_type)
+ value = parent_container_type.try(:identifier)
+ end
+
if value.blank? && belong_container_type && Object.const_defined?(belong_container_type)
value = belong_container.try(:identifier)
end
+
value
end
end
\ No newline at end of file
diff --git a/app/models/user.rb b/app/models/user.rb
index 14d7b2697..ed9d70c00 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -54,7 +54,8 @@ class User < ApplicationRecord
has_one :onclick_time, :dependent => :destroy
# 新版私信
- has_many :private_messages, :dependent => :destroy
+ has_many :private_messages, dependent: :destroy
+ has_many :recent_contacts, through: :private_messages, source: :target
has_many :tidings, :dependent => :destroy
has_many :games, :dependent => :destroy
diff --git a/app/services/batch_export_shixun_report_service.rb b/app/services/batch_export_shixun_report_service.rb
index d71235790..253eb4480 100644
--- a/app/services/batch_export_shixun_report_service.rb
+++ b/app/services/batch_export_shixun_report_service.rb
@@ -14,7 +14,7 @@ class BatchExportShixunReportService
end
def filename
- @_filename ||= "#{homework.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
+ @_filename ||= "实训报告_#{homework&.course&.name}_#{homework.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
end
def zip
diff --git a/app/services/exercise_user_pdf_service.rb b/app/services/exercise_user_pdf_service.rb
index 73711192f..6891e53ea 100644
--- a/app/services/exercise_user_pdf_service.rb
+++ b/app/services/exercise_user_pdf_service.rb
@@ -15,8 +15,8 @@ class ExerciseUserPdfService
end
def filename
- user_course = @course.course_members.find_by(user_id:@ex_user_user.id)&.course_group_name
- exercise_user_name = "#{@ex_user_user.real_name}_#{user_course.present? ? user_course : "未分班"}_#{exercise.exercise_name}_#{Time.now.strftime('%Y%m%d_%H%M')}"
+ # user_course = @course.course_members.find_by(user_id:@ex_user_user.id)&.course_group_name
+ exercise_user_name = "#{@ex_user_user&.student_id}_#{@ex_user_user.real_name}_#{exercise.exercise_name}_#{Time.now.strftime('%Y%m%d_%H%M')}"
"#{exercise_user_name.strip}.pdf"
end
diff --git a/app/services/export_exercises_service.rb b/app/services/export_exercises_service.rb
index ca2d347a6..1f1e15326 100644
--- a/app/services/export_exercises_service.rb
+++ b/app/services/export_exercises_service.rb
@@ -10,7 +10,7 @@ class ExportExercisesService
end
def filename
- exercise_export_name = "#{exercise.user.real_name}_#{exercise.exercise_name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
+ exercise_export_name = "学生答题_#{exercise&.course&.name}_#{exercise.exercise_name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
"#{exercise_export_name.strip}.zip"
end
diff --git a/app/services/export_shixun_report_service.rb b/app/services/export_shixun_report_service.rb
index f9da3d6f2..57b955762 100644
--- a/app/services/export_shixun_report_service.rb
+++ b/app/services/export_shixun_report_service.rb
@@ -10,7 +10,7 @@ class ExportShixunReportService
end
def filename
- @_filename ||= "#{homework.name}-#{work.user&.student_id}-#{work.user.real_name}.pdf".gsub(' ', '-').gsub('/', '_')
+ @_filename ||= "#{work.user&.student_id}_#{work.user.real_name}_#{homework.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.pdf".gsub(' ', '-').gsub('/', '_')
end
def prepare_binding
diff --git a/app/services/private_messages/create_service.rb b/app/services/private_messages/create_service.rb
new file mode 100644
index 000000000..560f1a540
--- /dev/null
+++ b/app/services/private_messages/create_service.rb
@@ -0,0 +1,35 @@
+class PrivateMessages::CreateService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :sender, :receiver, :params
+
+ def initialize(sender, receiver, **params)
+ @sender = sender
+ @receiver = receiver
+ @params = params
+ end
+
+ def call
+ validate!
+
+ same_attr = { sender: sender, receiver: receiver, content: content, send_time: Time.now }
+
+ message = nil
+ ActiveRecord::Base.transaction do
+ message = sender.private_messages.create!(same_attr.merge(target: receiver, status: 1))
+ receiver.private_messages.create!(same_attr.merge(target: sender, status: 0))
+ end
+ message
+ end
+
+ private
+
+ def content
+ @_content ||= params[:content].to_s.strip
+ end
+
+ def validate!
+ raise Error, '内容不能为空' if content.blank?
+ raise Error, '内容太长' if content.size > 255
+ end
+end
\ No newline at end of file
diff --git a/app/services/project_packages/save_service.rb b/app/services/project_packages/save_service.rb
index bcfc19a10..a876f56b3 100644
--- a/app/services/project_packages/save_service.rb
+++ b/app/services/project_packages/save_service.rb
@@ -15,7 +15,7 @@ class ProjectPackages::SaveService < ApplicationService
is_create = package.new_record?
raise Error, '类型不存在' unless ProjectPackageCategory.where(id: params[:category_id]).exists?
- params[:project_package_category_id] = params[:category_id].to_i
+ params[:project_package_category_id] = params.delete(:category_id).to_i
raise Error, '竞标截止时间不能小于当前时间' if params[:deadline_at].present? && params[:deadline_at].to_time < Time.now
@@ -25,7 +25,9 @@ class ProjectPackages::SaveService < ApplicationService
end
ActiveRecord::Base.transaction do
- package.assign_attributes(params)
+ columns = %i[project_package_category_id title content deadline_at
+ min_price max_price contact_name contact_phone]
+ package.assign_attributes(params.slice(*columns))
package.save!
# 处理附件
diff --git a/app/views/courses/search_users.json.jbuilder b/app/views/courses/search_users.json.jbuilder
index ac826fdf2..61362de66 100644
--- a/app/views/courses/search_users.json.jbuilder
+++ b/app/views/courses/search_users.json.jbuilder
@@ -1,6 +1,7 @@
json.users do
json.array! @users do |user|
json.id user.id
+ json.login user.login
json.name user.real_name
json.student_id user&.student_id
json.school_name user&.school_name
diff --git a/app/views/memos/new.json.jbuilder b/app/views/memos/new.json.jbuilder
index 14313b2ac..47c023ea0 100644
--- a/app/views/memos/new.json.jbuilder
+++ b/app/views/memos/new.json.jbuilder
@@ -1,3 +1,3 @@
json.tag_list @tag_list
-json.csrf_token @csrf_token
+json.forums @csrf_token
diff --git a/app/views/users/get_user_info.json.jbuilder b/app/views/users/get_user_info.json.jbuilder
index d9eb736ad..d12b950f9 100644
--- a/app/views/users/get_user_info.json.jbuilder
+++ b/app/views/users/get_user_info.json.jbuilder
@@ -7,6 +7,7 @@ json.is_teacher @user.user_extension&.teacher?
json.user_identity @user.identity
json.tidding_count 0
json.user_phone_binded @user.phone.present?
+json.phone @user.phone
json.profile_completed @user.profile_completed?
if @course
json.course_identity @course_identity
diff --git a/app/views/users/private_message_details/show.json.jbuilder b/app/views/users/private_message_details/show.json.jbuilder
new file mode 100644
index 000000000..065767fb4
--- /dev/null
+++ b/app/views/users/private_message_details/show.json.jbuilder
@@ -0,0 +1,11 @@
+json.count @count
+json.messages do
+ json.array! @messages.each do |message|
+ json.extract! message, :id, :user_id, :receiver_id, :sender_id, :content
+
+ json.send_time message.display_send_time
+ json.sender do
+ json.partial! 'users/user_simple', user: message.sender
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/views/users/private_messages/create.json.jbuilder b/app/views/users/private_messages/create.json.jbuilder
new file mode 100644
index 000000000..888cfeff6
--- /dev/null
+++ b/app/views/users/private_messages/create.json.jbuilder
@@ -0,0 +1,10 @@
+json.status 0
+json.message 'success'
+json.private_message do
+ json.extract! @message, :id, :user_id, :receiver_id, :sender_id, :content
+
+ json.send_time @message.display_send_time
+ json.sender do
+ json.partial! 'users/user_simple', user: @message.sender
+ end
+end
\ No newline at end of file
diff --git a/app/views/users/private_messages/index.json.jbuilder b/app/views/users/private_messages/index.json.jbuilder
new file mode 100644
index 000000000..c18a7d209
--- /dev/null
+++ b/app/views/users/private_messages/index.json.jbuilder
@@ -0,0 +1,13 @@
+json.count @count
+json.private_messages do
+ json.array! @messages.each do |message|
+ json.extract! message, :id, :content, :message_count
+
+ json.unread message.unread?
+ json.send_time message.display_send_time
+
+ json.target do
+ json.partial! 'users/user_simple', user: message.target
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/views/users/recent_contacts/index.json.jbuilder b/app/views/users/recent_contacts/index.json.jbuilder
new file mode 100644
index 000000000..f2f7f025a
--- /dev/null
+++ b/app/views/users/recent_contacts/index.json.jbuilder
@@ -0,0 +1,2 @@
+json.users @contacts, partial: 'users/user_simple', as: :user
+json.count @contacts.size
\ No newline at end of file
diff --git a/app/views/users_for_private_messages/index.json.jbuilder b/app/views/users_for_private_messages/index.json.jbuilder
new file mode 100644
index 000000000..be040e368
--- /dev/null
+++ b/app/views/users_for_private_messages/index.json.jbuilder
@@ -0,0 +1,2 @@
+json.users @users, partial: 'users/user_simple', as: :user
+json.count @users.size
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index 6623edd99..2f9eed61b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -53,6 +53,11 @@ Rails.application.routes.draw do
resource :grade_records, only: [:show]
resource :watch, only: [:create, :destroy]
resources :project_packages, only: [:index]
+ # 私信
+ resources :private_messages, only: [:index, :create, :destroy]
+ resources :recent_contacts, only: [:index]
+ resource :private_message_details, only: [:show]
+ resource :unread_message_info, only: [:show]
end
@@ -91,6 +96,7 @@ Rails.application.routes.draw do
end
end
end
+ resources :users_for_private_messages, only: [:index]
resources :myshixuns, param: :identifier, shallow: true do
member do
diff --git a/db/migrate/20190730024112_add_index_to_user.rb b/db/migrate/20190730024112_add_index_to_user.rb
new file mode 100644
index 000000000..fa198cffa
--- /dev/null
+++ b/db/migrate/20190730024112_add_index_to_user.rb
@@ -0,0 +1,7 @@
+class AddIndexToUser < ActiveRecord::Migration[5.2]
+ def change
+ # add_index :users, :login, unique: true
+ # add_index :users, :mail, unique: true
+ # add_index :users, :phone, unique: true
+ end
+end
diff --git a/public/compatibility.html b/public/compatibility.html
index f13c673f5..f28bad2c8 100644
--- a/public/compatibility.html
+++ b/public/compatibility.html
@@ -5,9 +5,9 @@
EduCoder
-
-
-
+
+
+
diff --git a/public/images/educoder/path.png b/public/images/educoder/path.png
new file mode 100644
index 000000000..d8b6f1715
Binary files /dev/null and b/public/images/educoder/path.png differ
diff --git a/public/images/educoder/project_packages/ai.png b/public/images/educoder/project_packages/ai.png
new file mode 100755
index 000000000..825e75165
Binary files /dev/null and b/public/images/educoder/project_packages/ai.png differ
diff --git a/public/images/educoder/project_packages/backend.png b/public/images/educoder/project_packages/backend.png
new file mode 100755
index 000000000..359bca746
Binary files /dev/null and b/public/images/educoder/project_packages/backend.png differ
diff --git a/public/images/educoder/project_packages/cloud_compute_and_big_data.png b/public/images/educoder/project_packages/cloud_compute_and_big_data.png
new file mode 100755
index 000000000..42e64da3b
Binary files /dev/null and b/public/images/educoder/project_packages/cloud_compute_and_big_data.png differ
diff --git a/public/images/educoder/project_packages/database.png b/public/images/educoder/project_packages/database.png
new file mode 100755
index 000000000..2b668e774
Binary files /dev/null and b/public/images/educoder/project_packages/database.png differ
diff --git a/public/images/educoder/project_packages/devops_and_test.png b/public/images/educoder/project_packages/devops_and_test.png
new file mode 100755
index 000000000..77b8e6f3b
Binary files /dev/null and b/public/images/educoder/project_packages/devops_and_test.png differ
diff --git a/public/images/educoder/project_packages/front.png b/public/images/educoder/project_packages/front.png
new file mode 100755
index 000000000..23ba2a755
Binary files /dev/null and b/public/images/educoder/project_packages/front.png differ
diff --git a/public/images/educoder/project_packages/mobile.png b/public/images/educoder/project_packages/mobile.png
new file mode 100755
index 000000000..cd73520c7
Binary files /dev/null and b/public/images/educoder/project_packages/mobile.png differ
diff --git a/public/images/educoder/project_packages/other.png b/public/images/educoder/project_packages/other.png
new file mode 100755
index 000000000..a700cd5de
Binary files /dev/null and b/public/images/educoder/project_packages/other.png differ
diff --git a/public/javascripts/download/jquery.fileDownload.js b/public/javascripts/download/jquery.fileDownload.js
new file mode 100644
index 000000000..0d0301c23
--- /dev/null
+++ b/public/javascripts/download/jquery.fileDownload.js
@@ -0,0 +1,490 @@
+/*
+* jQuery File Download Plugin v1.4.5
+*
+* http://www.johnculviner.com
+*
+* Copyright (c) 2013 - John Culviner
+*
+* Licensed under the MIT license:
+* http://www.opensource.org/licenses/mit-license.php
+*
+* !!!!NOTE!!!!
+* You must also write a cookie in conjunction with using this plugin in the server's response headers containing the file download:
+* Set-Cookie: fileDownload=true; path=/"
+* !!!!NOTE!!!!
+*/
+
+(function($, window){
+ // i'll just put them here to get evaluated on script load
+ var htmlSpecialCharsRegEx = /[<>&\r\n"']/gm;
+ var htmlSpecialCharsPlaceHolders = {
+ '<': 'lt;',
+ '>': 'gt;',
+ '&': 'amp;',
+ '\r': "#13;",
+ '\n': "#10;",
+ '"': 'quot;',
+ "'": '#39;' /*single quotes just to be safe, IE8 doesn't support ', so use ' instead */
+ };
+
+$.extend({
+ //
+ //$.fileDownload('/path/to/url/', options)
+ // see directly below for possible 'options'
+ fileDownload: function (fileUrl, options) {
+
+ //provide some reasonable defaults to any unspecified options below
+ var settings = $.extend({
+
+ //
+ //Requires jQuery UI: provide a message to display to the user when the file download is being prepared before the browser's dialog appears
+ //
+ preparingMessageHtml: null,
+
+ //
+ //Requires jQuery UI: provide a message to display to the user when a file download fails
+ //
+ failMessageHtml: null,
+
+ //
+ //the stock android browser straight up doesn't support file downloads initiated by a non GET: http://code.google.com/p/android/issues/detail?id=1780
+ //specify a message here to display if a user tries with an android browser
+ //if jQuery UI is installed this will be a dialog, otherwise it will be an alert
+ //Set to null to disable the message and attempt to download anyway
+ //
+ androidPostUnsupportedMessageHtml: "Unfortunately your Android browser doesn't support this type of file download. Please try again with a different browser.",
+
+ //
+ //Requires jQuery UI: options to pass into jQuery UI Dialog
+ //
+ dialogOptions: { modal: true },
+
+ //
+ //a function to call while the dowload is being prepared before the browser's dialog appears
+ //Args:
+ // url - the original url attempted
+ //
+ prepareCallback: function (url) { },
+
+ //
+ //a function to call after a file download successfully completed
+ //Args:
+ // url - the original url attempted
+ //
+ successCallback: function (url) { },
+
+ //
+ //a function to call after a file download request was canceled
+ //Args:
+ // url - the original url attempted
+ //
+ abortCallback: function (url) { },
+
+ //
+ //a function to call after a file download failed
+ //Args:
+ // responseHtml - the html that came back in response to the file download. this won't necessarily come back depending on the browser.
+ // in less than IE9 a cross domain error occurs because 500+ errors cause a cross domain issue due to IE subbing out the
+ // server's error message with a "helpful" IE built in message
+ // url - the original url attempted
+ // error - original error cautch from exception
+ //
+ failCallback: function (responseHtml, url, error) { },
+
+ //
+ // the HTTP method to use. Defaults to "GET".
+ //
+ httpMethod: "GET",
+
+ //
+ // if specified will perform a "httpMethod" request to the specified 'fileUrl' using the specified data.
+ // data must be an object (which will be $.param serialized) or already a key=value param string
+ //
+ data: null,
+
+ //
+ //a period in milliseconds to poll to determine if a successful file download has occured or not
+ //
+ checkInterval: 100,
+
+ //
+ //the cookie name to indicate if a file download has occured
+ //
+ cookieName: "fileDownload",
+
+ //
+ //the cookie value for the above name to indicate that a file download has occured
+ //
+ cookieValue: "true",
+
+ //
+ //the cookie path for above name value pair
+ //
+ cookiePath: "/",
+
+ //
+ //if specified it will be used when attempting to clear the above name value pair
+ //useful for when downloads are being served on a subdomain (e.g. downloads.example.com)
+ //
+ cookieDomain: null,
+
+ //
+ //the title for the popup second window as a download is processing in the case of a mobile browser
+ //
+ popupWindowTitle: "Initiating file download...",
+
+ //
+ //Functionality to encode HTML entities for a POST, need this if data is an object with properties whose values contains strings with quotation marks.
+ //HTML entity encoding is done by replacing all &,<,>,',",\r,\n characters.
+ //Note that some browsers will POST the string htmlentity-encoded whilst others will decode it before POSTing.
+ //It is recommended that on the server, htmlentity decoding is done irrespective.
+ //
+ encodeHTMLEntities: true
+
+ }, options);
+
+ var deferred = new $.Deferred();
+
+ //Setup mobile browser detection: Partial credit: http://detectmobilebrowser.com/
+ var userAgent = (navigator.userAgent || navigator.vendor || window.opera).toLowerCase();
+
+ var isIos; //has full support of features in iOS 4.0+, uses a new window to accomplish this.
+ var isAndroid; //has full support of GET features in 4.0+ by using a new window. Non-GET is completely unsupported by the browser. See above for specifying a message.
+ var isOtherMobileBrowser; //there is no way to reliably guess here so all other mobile devices will GET and POST to the current window.
+
+ if (/ip(ad|hone|od)/.test(userAgent)) {
+
+ isIos = true;
+
+ } else if (userAgent.indexOf('android') !== -1) {
+
+ isAndroid = true;
+
+ } else {
+
+ isOtherMobileBrowser = /avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|playbook|silk|iemobile|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(userAgent) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(userAgent.substr(0, 4));
+
+ }
+
+ var httpMethodUpper = settings.httpMethod.toUpperCase();
+
+ if (isAndroid && httpMethodUpper !== "GET" && settings.androidPostUnsupportedMessageHtml) {
+ //the stock android browser straight up doesn't support file downloads initiated by non GET requests: http://code.google.com/p/android/issues/detail?id=1780
+
+ if ($().dialog) {
+ $("
").html(settings.androidPostUnsupportedMessageHtml).dialog(settings.dialogOptions);
+ } else {
+ alert(settings.androidPostUnsupportedMessageHtml);
+ }
+
+ return deferred.reject();
+ }
+
+ var $preparingDialog = null;
+
+ var internalCallbacks = {
+
+ onPrepare: function (url) {
+
+ //wire up a jquery dialog to display the preparing message if specified
+ if (settings.preparingMessageHtml) {
+
+ $preparingDialog = $("
").html(settings.preparingMessageHtml).dialog(settings.dialogOptions);
+
+ } else if (settings.prepareCallback) {
+
+ settings.prepareCallback(url);
+
+ }
+
+ },
+
+ onSuccess: function (url) {
+
+ //remove the perparing message if it was specified
+ if ($preparingDialog) {
+ $preparingDialog.dialog('close');
+ }
+
+ settings.successCallback(url);
+
+ deferred.resolve(url);
+ },
+
+ onAbort: function (url) {
+
+ //remove the perparing message if it was specified
+ if ($preparingDialog) {
+ $preparingDialog.dialog('close');
+ };
+
+ settings.abortCallback(url);
+
+ deferred.reject(url);
+ },
+
+ onFail: function (responseHtml, url, error) {
+
+ //remove the perparing message if it was specified
+ if ($preparingDialog) {
+ $preparingDialog.dialog('close');
+ }
+
+ //wire up a jquery dialog to display the fail message if specified
+ if (settings.failMessageHtml) {
+ $("
").html(settings.failMessageHtml).dialog(settings.dialogOptions);
+ }
+
+ settings.failCallback(responseHtml, url, error);
+
+ deferred.reject(responseHtml, url);
+ }
+ };
+
+ internalCallbacks.onPrepare(fileUrl);
+
+ //make settings.data a param string if it exists and isn't already
+ if (settings.data !== null && typeof settings.data !== "string") {
+ settings.data = $.param(settings.data);
+ }
+
+
+ var $iframe,
+ downloadWindow,
+ formDoc,
+ $form;
+
+ if (httpMethodUpper === "GET") {
+
+ if (settings.data !== null) {
+ //need to merge any fileUrl params with the data object
+
+ var qsStart = fileUrl.indexOf('?');
+
+ if (qsStart !== -1) {
+ //we have a querystring in the url
+
+ if (fileUrl.substring(fileUrl.length - 1) !== "&") {
+ fileUrl = fileUrl + "&";
+ }
+ } else {
+
+ fileUrl = fileUrl + "?";
+ }
+
+ fileUrl = fileUrl + settings.data;
+ }
+
+ if (isIos || isAndroid) {
+
+ downloadWindow = window.open(fileUrl);
+ downloadWindow.document.title = settings.popupWindowTitle;
+ window.focus();
+
+ } else if (isOtherMobileBrowser) {
+
+ window.location(fileUrl);
+
+ } else {
+
+ //create a temporary iframe that is used to request the fileUrl as a GET request
+ $iframe = $("").appendTo("body");
+ }
+
+ } else {
+
+ var formInnerHtml = "";
+
+ if (settings.data !== null) {
+
+ $.each(settings.data.replace(/\+/g, ' ').split("&"), function () {
+
+ var kvp = this.split("=");
+
+ //Issue: When value contains sign '=' then the kvp array does have more than 2 items. We have to join value back
+ var k = kvp[0];
+ kvp.shift();
+ var v = kvp.join("=");
+ kvp = [k, v];
+
+ var key = settings.encodeHTMLEntities ? htmlSpecialCharsEntityEncode(decodeURIComponent(kvp[0])) : decodeURIComponent(kvp[0]);
+ if (key) {
+ var value = settings.encodeHTMLEntities ? htmlSpecialCharsEntityEncode(decodeURIComponent(kvp[1])) : decodeURIComponent(kvp[1]);
+ formInnerHtml += '';
+ }
+ });
+ }
+
+ if (isOtherMobileBrowser) {
+
+ $form = $("" + settings.popupWindowTitle + "");
+ $form = $(formDoc).find('form');
+ }
+
+ $form.submit();
+ }
+
+
+ //check if the file download has completed every checkInterval ms
+ setTimeout(checkFileDownloadComplete, settings.checkInterval);
+
+
+ function checkFileDownloadComplete() {
+ //has the cookie been written due to a file download occuring?
+
+ var cookieValue = settings.cookieValue;
+ if(typeof cookieValue == 'string') {
+ cookieValue = cookieValue.toLowerCase();
+ }
+
+ var lowerCaseCookie = settings.cookieName.toLowerCase() + "=" + cookieValue;
+
+ if (document.cookie.toLowerCase().indexOf(lowerCaseCookie) > -1) {
+
+ //execute specified callback
+ internalCallbacks.onSuccess(fileUrl);
+
+ //remove cookie
+ var cookieData = settings.cookieName + "=; path=" + settings.cookiePath + "; expires=" + new Date(0).toUTCString() + ";";
+ if (settings.cookieDomain) cookieData += " domain=" + settings.cookieDomain + ";";
+ document.cookie = cookieData;
+
+ //remove iframe
+ cleanUp(false);
+
+ return;
+ }
+
+ //has an error occured?
+ //if neither containers exist below then the file download is occuring on the current window
+ if (downloadWindow || $iframe) {
+
+ //has an error occured?
+ try {
+
+ var formDoc = downloadWindow ? downloadWindow.document : getiframeDocument($iframe);
+
+ if (formDoc && formDoc.body !== null && formDoc.body.innerHTML.length) {
+
+ var isFailure = true;
+
+ if ($form && $form.length) {
+ var $contents = $(formDoc.body).contents().first();
+
+ try {
+ if ($contents.length && $contents[0] === $form[0]) {
+ isFailure = false;
+ }
+ } catch (e) {
+ if (e && e.number == -2146828218) {
+ // IE 8-10 throw a permission denied after the form reloads on the "$contents[0] === $form[0]" comparison
+ isFailure = true;
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ if (isFailure) {
+ // IE 8-10 don't always have the full content available right away, they need a litle bit to finish
+ setTimeout(function () {
+ internalCallbacks.onFail(formDoc.body.innerHTML, fileUrl);
+ cleanUp(true);
+ }, 100);
+
+ return;
+ }
+ }
+ }
+ catch (err) {
+
+ //500 error less than IE9
+ internalCallbacks.onFail('', fileUrl, err);
+
+ cleanUp(true);
+
+ return;
+ }
+ }
+
+
+ //keep checking...
+ setTimeout(checkFileDownloadComplete, settings.checkInterval);
+ }
+
+ //gets an iframes document in a cross browser compatible manner
+ function getiframeDocument($iframe) {
+ var iframeDoc = $iframe[0].contentWindow || $iframe[0].contentDocument;
+ if (iframeDoc.document) {
+ iframeDoc = iframeDoc.document;
+ }
+ return iframeDoc;
+ }
+
+ function cleanUp(isFailure) {
+
+ setTimeout(function() {
+
+ if (downloadWindow) {
+
+ if (isAndroid) {
+ downloadWindow.close();
+ }
+
+ if (isIos) {
+ if (downloadWindow.focus) {
+ downloadWindow.focus(); //ios safari bug doesn't allow a window to be closed unless it is focused
+ if (isFailure) {
+ downloadWindow.close();
+ }
+ }
+ }
+ }
+
+ //iframe cleanup appears to randomly cause the download to fail
+ //not doing it seems better than failure...
+ //if ($iframe) {
+ // $iframe.remove();
+ //}
+
+ }, 0);
+ }
+
+
+ function htmlSpecialCharsEntityEncode(str) {
+ return str.replace(htmlSpecialCharsRegEx, function(match) {
+ return '&' + htmlSpecialCharsPlaceHolders[match];
+ });
+ }
+ var promise = deferred.promise();
+ promise.abort = function() {
+ cleanUp();
+ $iframe.attr('src', '').html('');
+ internalCallbacks.onAbort(fileUrl);
+ };
+ return promise;
+ }
+});
+
+})(jQuery, this || window);
\ No newline at end of file
diff --git a/public/javascripts/download/jquery.fileDownload.min.js b/public/javascripts/download/jquery.fileDownload.min.js
new file mode 100644
index 000000000..c4ae8b923
--- /dev/null
+++ b/public/javascripts/download/jquery.fileDownload.min.js
@@ -0,0 +1 @@
+!function(e,o){var t=/[<>&\r\n"']/gm,a={"<":"lt;",">":"gt;","&":"amp;","\r":"#13;","\n":"#10;",'"':"quot;","'":"#39;"};e.extend({fileDownload:function(i,n){var r,l,c,d=e.extend({preparingMessageHtml:null,failMessageHtml:null,androidPostUnsupportedMessageHtml:"Unfortunately your Android browser doesn't support this type of file download. Please try again with a different browser.",dialogOptions:{modal:!0},prepareCallback:function(e){},successCallback:function(e){},abortCallback:function(e){},failCallback:function(e,o,t){},httpMethod:"GET",data:null,checkInterval:100,cookieName:"fileDownload",cookieValue:"true",cookiePath:"/",cookieDomain:null,popupWindowTitle:"Initiating file download...",encodeHTMLEntities:!0},n),s=new e.Deferred,p=(navigator.userAgent||navigator.vendor||o.opera).toLowerCase();/ip(ad|hone|od)/.test(p)?r=!0:-1!==p.indexOf("android")?l=!0:c=/avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|playbook|silk|iemobile|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(p)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(p.substr(0,4));var m=d.httpMethod.toUpperCase();if(l&&"GET"!==m&&d.androidPostUnsupportedMessageHtml)return e().dialog?e("