From 70427d82e22962bd7832d2af30290076e3adbde7 Mon Sep 17 00:00:00 2001 From: bohung Date: Sun, 22 May 2022 18:32:22 +0800 Subject: [PATCH] Add feed feature. Add new template(Tab list). --- .../admin/archive_files_controller.rb | 80 +- app/controllers/archive_feeds_controller.rb | 92 ++ app/controllers/archives_controller.rb | 805 ++++++++---------- app/helpers/admin/archive_files_helper.rb | 25 +- app/models/archive_cache.rb | 6 + app/models/archive_file.rb | 411 ++++++++- app/models/archive_file_feed.rb | 107 +++ app/models/archive_file_feed_cache.rb | 27 + app/models/archive_sort_order.rb | 38 +- .../archive_files/_edit_feed_form.html.erb | 61 ++ app/views/admin/archive_files/_feed.html.erb | 83 ++ .../admin/archive_files/_feed_form.html.erb | 61 ++ app/views/admin/archive_files/_form.html.erb | 3 +- app/views/admin/archive_files/_index.html.erb | 91 +- app/views/admin/archive_files/feed.html.erb | 126 +++ app/views/archive_feeds/rssfeed.rss.builder | 16 + app/views/archives/show.html.erb | 34 + archive.gemspec | 10 +- config/locales/en.yml | 6 + config/locales/zh_tw.yml | 6 + config/routes.rb | 35 +- lib/archive/engine.rb | 29 + lib/archive_model/cache.rb | 37 + modules/archive/archive_index1.html.erb | 2 +- modules/archive/archive_index10.html.erb | 2 +- modules/archive/archive_index11.html.erb | 2 +- modules/archive/archive_index12.html.erb | 4 +- modules/archive/archive_index13.html.erb | 41 + modules/archive/archive_index2.html.erb | 2 +- modules/archive/archive_index3.html.erb | 2 +- modules/archive/archive_index4.html.erb | 2 +- modules/archive/archive_index5.html.erb | 2 +- modules/archive/archive_index6.html.erb | 2 +- modules/archive/archive_index7.html.erb | 2 +- modules/archive/archive_index8.html.erb | 2 +- modules/archive/archive_index9.html.erb | 2 +- modules/archive/info.json | 10 +- modules/archive/thumbs/ar-tab1.png | Bin 0 -> 40955 bytes 38 files changed, 1763 insertions(+), 503 deletions(-) create mode 100644 app/controllers/archive_feeds_controller.rb create mode 100644 app/models/archive_cache.rb create mode 100644 app/models/archive_file_feed.rb create mode 100644 app/models/archive_file_feed_cache.rb create mode 100644 app/views/admin/archive_files/_edit_feed_form.html.erb create mode 100644 app/views/admin/archive_files/_feed.html.erb create mode 100644 app/views/admin/archive_files/_feed_form.html.erb create mode 100644 app/views/admin/archive_files/feed.html.erb create mode 100644 app/views/archive_feeds/rssfeed.rss.builder create mode 100644 app/views/archives/show.html.erb create mode 100644 lib/archive_model/cache.rb create mode 100644 modules/archive/archive_index13.html.erb create mode 100644 modules/archive/thumbs/ar-tab1.png diff --git a/app/controllers/admin/archive_files_controller.rb b/app/controllers/admin/archive_files_controller.rb index 1cadfca..e2205e9 100644 --- a/app/controllers/admin/archive_files_controller.rb +++ b/app/controllers/admin/archive_files_controller.rb @@ -27,7 +27,7 @@ class Admin::ArchiveFilesController < OrbitAdminController def index if ArchiveSortOrder.count == 0 - ArchiveSortOrder.new('sort_order' => false).save + ArchiveSortOrder.create('sort_order' => false) end if !(params['order_asc'].nil?) if params['order_asc'] == 'true' @@ -41,7 +41,7 @@ class Admin::ArchiveFilesController < OrbitAdminController @categories = @module_app.categories.enabled @tags = @module_app.tags @filter_fields = filter_fields(@categories, @tags) - @archives = ArchiveFile.order_by(sort) + @archives = ArchiveFile.local_data.order_by(sort) .with_categories(filters("category")) .with_tags(filters("tag")) .with_status(filters("status")) @@ -52,9 +52,10 @@ class Admin::ArchiveFilesController < OrbitAdminController end def new - @archive_file = ArchiveFile.new - @tags = @module_app.tags - @categories = @module_app.categories.enabled + sort_number = ArchiveSortOrder.first.get_default_order + @archive_file = ArchiveFile.new(:sort_number=>sort_number) + @tags = @module_app.tags + @categories = @module_app.categories.enabled end def edit @@ -99,6 +100,9 @@ class Admin::ArchiveFilesController < OrbitAdminController respond_to do |format| if @archive_file.save + Thread.new do + @archive_file.notify_feed("create") + end format.html { redirect_to(admin_archive_files_path) } format.xml { render :xml => @archive_file, :status => :created, :location => @archive_file } else @@ -129,6 +133,9 @@ class Admin::ArchiveFilesController < OrbitAdminController respond_to do |format| if @archive_file.update_attributes(archive_vars) + Thread.new do + @archive_file.notify_feed("update") + end format.html { redirect_to(admin_archive_files_path) } format.xml { head :ok } else @@ -141,11 +148,72 @@ class Admin::ArchiveFilesController < OrbitAdminController def destroy archive_file = ArchiveFile.find(params[:id]) archive_file.destroy + Thread.new do + archive_file.notify_feed("destroy") + end redirect_to admin_archive_files_path end + def delete + if params[:ids] + ArchiveFile.any_in(:uid => params[:ids]).destroy_all + Thread.new do + ArchiveFile.notify_feed_delete(params[:ids]) + end + end + if request.xhr? + render :nothing => true, :status => 204 + else + redirect_to admin_archive_files_path + end + end + def feed + @table_feed_fields = ["archive.feed_name",:tags , :category , "archive.rssfeed", "archive.jsonfeed"] + @feeds = ArchiveFileFeed.all.asc(:created_at) + end + def feedform + if params[:type] == "new" + @archive_feed = ArchiveFileFeed.new(id: nil) + render :partial => "feed_form" + else params[:type] == "edit" + @archive_feed = ArchiveFileFeed.find(params[:id]) + render :partial => "edit_feed_form" + end + end + def createfeed + archive_feed = ArchiveFileFeed.new(feed_params) + archive_feed.save + #ArchiveFileFeed.create_feed_cache(nil,archive_feed) + feeds = ArchiveFileFeed.all.asc(:created_at) + render :partial => "feed", :collection => feeds + end + + def updatefeed + archive_feed = ArchiveFileFeed.find(params[:id]) + archive_feed.update_attributes(feed_params) + archive_feed.save + #ArchiveFileFeed.create_feed_cache(nil,archive_feed) + feeds = ArchiveFileFeed.all.asc(:created_at) + render :partial => "feed", :collection => feeds + end + + def deletefeed + archive_feed = ArchiveFileFeed.find(params[:id]) + archive_feed.destroy + feeds = ArchiveFileFeed.all.asc(:created_at) + render :partial => "feed", :collection => feeds + end private - + def feed_params + feed_params = params.require(:archive_file_feed).permit! + if feed_params[:tag_ids].nil? + feed_params[:tag_ids] = [] + end + if feed_params[:category_ids].nil? + feed_params[:category_ids] = [] + end + feed_params + end def archive_vars params[:archive_file][:tags] ||=[] params.require(:archive_file).permit! diff --git a/app/controllers/archive_feeds_controller.rb b/app/controllers/archive_feeds_controller.rb new file mode 100644 index 0000000..a42c641 --- /dev/null +++ b/app/controllers/archive_feeds_controller.rb @@ -0,0 +1,92 @@ +require "rss" +class ArchiveFeedsController < ApplicationController + include Admin::ArchiveFilesHelper + def feed_add_remote + if params[:url].present? + uid = params[:uid] + archive_file_feed = ArchiveFileFeed.where(uid: uid).first + if !(archive_file_feed.remote_urls.include?(params[:url])) + archive_file_feed.remote_urls << params[:url] + archive_file_feed.save + end + end + render :json => {success: true} + end + def feed_remove_remote + if params[:url].present? + uid = params[:uid] + archive_file_feed = ArchiveFileFeed.where(uid: uid).first + if archive_file_feed.remote_urls.delete(params[:url]) + archive_file_feed.save + end + end + render :json => {success: true} + end + def feed + uid = params[:uid] + feed_cache = ArchiveFileFeedCache.where(uid: uid) + feed_cache_old = feed_cache.last + count = feed_cache.count + if count > 1 + feed_cache.limit(count-1).destroy + end + feed_cache = feed_cache.first + archive_content = '' + if feed_cache.nil? + archive_content = ArchiveFileFeed.where(uid: uid).first.generate_one_cache_timeout(base_url: request.base_url,timeout: 20) + archive_content = (feed_cache_old.content rescue "") if archive_content.nil? + else + archive_content = feed_cache.content + end + render :json => archive_content + end + + def rssfeed + uid = params[:uid] + @aff = ArchiveFileFeed.find_by(:uid => uid) rescue nil + if !@aff.nil? + tags = @aff.tag_ids + if !tags.empty? + @archive_files = ArchiveFile.local_data.can_display_and_sorted.filter_by_tags(tags) + else + @archive_files = ArchiveFile.local_data.can_display_and_sorted + end + categories = @aff.category_ids + if !categories.empty? + @archive_files = @archive_files.filter_by_categories(categories) + end + end + respond_to do |format| + format.html {redirect_to "/xhr/archive_files/rssfeed/#{@aff.uid}.rss"} + format.rss + end + end + + def feeds + feeds = [] + ArchiveFileFeed.all.each do |aff| + feed = {} + feed["title_translations"] = aff.title_translations + feed["uid"] = aff.uid + feed["url"] = "#{request.base_url}/xhr/archive_files/feed/#{aff.uid}" + feed["xml_url"] = "#{request.base_url}/xhr/archive_files/rssfeed/#{aff.uid}.rss" + feed["tags"] = [] + aff.tag_ids.each do |t| + tag = Tag.find(t) + d = {} + d["name_translations"] = tag.name_translations + feed["tags"] << d + end + feeds << feed + end + render :json => {"feeds" => feeds}.to_json + end + + +end + + + + + + diff --git a/app/controllers/archives_controller.rb b/app/controllers/archives_controller.rb index 518b293..e88c767 100644 --- a/app/controllers/archives_controller.rb +++ b/app/controllers/archives_controller.rb @@ -4,331 +4,226 @@ class ArchivesController < ApplicationController ac.render_to_string("archives/custom_widget_data",:locals=>{:@custom_data_field=>@custom_data_field,:@field_name=>@field_name}) end #avoid the categories to be not in the ArchiveCategory - def serve_cmap - serve_path=File.expand_path("../../assets/javascripts/archive/pdf/bcmaps/#{params[:file_name]}.#{params[:extension]}",__FILE__) - if Dir.glob(serve_path).length != 0 - send_file(serve_path, type: "application/octet-stream") - else - render :file => "#{Rails.root}/app/views/errors/404.html", :layout => false, :status => :not_found, :content_type => 'text/html' and return - end - end - def index - page = OrbitHelper.page rescue nil - unless page - page = Page.where(url:params['url']).first - end - params = OrbitHelper.params + def serve_cmap + serve_path=File.expand_path("../../assets/javascripts/archive/pdf/bcmaps/#{params[:file_name]}.#{params[:extension]}",__FILE__) + if Dir.glob(serve_path).length != 0 + send_file(serve_path, type: "application/octet-stream") + else + render :file => "#{Rails.root}/app/views/errors/404.html", :layout => false, :status => :not_found, :content_type => 'text/html' and return + end + end + def show + params = OrbitHelper.params + archive_file = ArchiveFile.find_by(:uid=>params[:uid]) + locale = I18n.locale.to_s + data, serial_number, idx = archive_file.get_frontend_data(locale, 0, 0, true, OrbitHelper.url_to_show("")) + { + "data" => data + } + end + def index + ArchiveFile.check_sort_number + page = OrbitHelper.page rescue nil + unless page + page = Page.where(url:params['url']).first + end + params = OrbitHelper.params @@total_pages = 1 in_class = "" custom_data_field = page.custom_data_field rescue nil - if custom_data_field && custom_data_field["expanded"] == "yes" - in_class = "in" - end + if custom_data_field && custom_data_field["expanded"] == "yes" + in_class = "in" + end + locale = I18n.locale.to_s cats_last = [] - sort_order = (ArchiveSortOrder.first['sort_order'] rescue false) ? 1 : -1 #Order with ascending - if !params['title'].nil? - files_by_category = ArchiveFile.where(is_hidden: false).order_by(:created_at => "desc").group_by(&:category) - categories = files_by_category.keys - categories_sort = get_sorted_cat_with_filter(categories,'orm') - categories_sort.each do |category| - url_to_edit = "" - flag = false - archives = [] - serial_number = 0 - files_by_category[category].each_with_index do |archive,idx| - if archive.title == params['title'] - flag = true - statuses = archive.statuses_with_classname.collect do |status| - { - "status" => status["name"] || "", - "status-class" => "status-#{status['classname']}" - } - end - files = [] - archive.archive_file_multiples.order_by(:sort_number=>'desc').each do |file| - if file.choose_lang.include?(I18n.locale.to_s) - title = (file.file_title.blank? ? File.basename(file.file.path) : file.file_title) rescue "" - extension = file.file.file.extension.downcase rescue "" - serial_number += 1 - files << { - "file-name" => title, - "file-type" => extension, - "file-url" => "/xhr/archive/download?file=#{file.id}", - "serial_number" => serial_number - } - end - end - if archive.urls.present? - archive.urls.each_with_index do |url,i| - serial_number += 1 - files << { - "file-name" => archive.title, - "file-type" => archive.get_url_text(i), - "file-url" => url, - "serial_number" => serial_number - } - end - end - archives << { - "archive-title" => archive.title, - "description" => archive.description, - "created_at" => archive.created_at.strftime('%Y%m%d').to_i, - "archive-url" => archive.url, - "url" => archive.url, - "statuses" => statuses, - "sort_number" => archive.sort_number, - "is_top" => (archive.is_top ? 1 : 0), - "files" => files, - "idx" => (idx + 1) - } - end - end - if flag - cats_last << { - "category-title" => category.title, - "archives" => archives, - "url_to_edit" => url_to_edit - } - end - end - else - if OrbitHelper.page_data_count > 0 - OrbitHelper.set_page_data_count 0 - page = Page.where(:page_id => params["page_id"]).first rescue nil - if !page.nil? - page.data_count = 0 - page.save - end - end - categories = OrbitHelper.page_categories - tags = OrbitHelper.page_tags - module_app = ModuleApp.where(:key=>'archive').first - if categories == ["all"] - categories = module_app.categories.pluck(:id).map(&:to_s) - end - if tags == ["all"] - tags = module_app.tags.sort_by{|tag| ((module_app.asc rescue true) ? tag.sort_number.to_i : -tag.sort_number.to_i)}.map{|tag| tag.id.to_s} - tags << nil - end - if params[:data_count].to_i <=0 - page_data_count = 0 - else - page_data_count = params[:data_count].to_i - end - if params[:page_no].nil? - page_no = 1 - else - page_no = params[:page_no].to_i - end - if (tags.count > 1 && categories.count <= 1 rescue false) - archive_files = ArchiveFile.where(is_hidden: false,:title.nin=>["",nil]).filter_by_categories.order_by(:created_at => "desc") - group_archive_files = {} - cats = [] - each_data_count = [] - archive_files_ids = archive_files.pluck(:id) - archive_files_ids_count = archive_files_ids.count - tags.each do |tag_id| - tag_name = "" - if tag_id - tag = Tag.find(tag_id) rescue nil - next if tag.nil? - url_to_edit = OrbitHelper.current_user ? "/admin/archive_files?filters[tags][]=#{tag_id}" : "" - taggings = Tagging.where(:tag_id=>tag_id).pluck(:taggable_id) - archives = archive_files.where(:id.in=>taggings).to_a - archive_files_ids = archive_files_ids - archives.map{|a| a.id} - tag_name = Tag.find(tag_id).name - else - if categories.count == 1 - url_to_edit = OrbitHelper.current_user ? "/admin/archive_files?filters[category][]=#{categories[0]}" : "" - else - url_to_edit = OrbitHelper.current_user ? "/admin/archive_files" : "" - end - archives = [] - if tags.count > 1 && archive_files_ids_count != archive_files_ids.count - tag_name = I18n.t("archive.other") - archives = archive_files.where(:id.in=>archive_files_ids).to_a - else - archives = archive_files.to_a - end - end - serial_number = 0 - if archives.count != 0 - archives = archives.sort_by{|k| [(k["is_top"] ? 0 : 1) ,(k["sort_number"].nil? ? Float::INFINITY : sort_order * k["sort_number"].to_i),-k["created_at"].to_i]}.map.with_index do |archive,idx| - statuses = archive.statuses_with_classname.collect do |status| - { - "status" => status["name"] || "", - "status-class" => "status-#{status['classname']}" - } - end - files = [] - archive.archive_file_multiples.order_by(:sort_number=>'desc').each do |file| - if file.choose_lang.include?(I18n.locale.to_s) - title = (file.file_title.blank? ? File.basename(file.file.path) : file.file_title) rescue "" - extension = file.file.file.extension.downcase rescue "" - serial_number += 1 - files << { - "file-name" => title, - "file-type" => extension, - "file-url" => "/xhr/archive/download?file=#{file.id}", - "serial_number" => serial_number - } - end - end - if archive.urls.present? - archive.urls.each_with_index do |url,i| - serial_number += 1 - files << { - "file-name" => archive.title, - "file-type" => archive.get_url_text(i), - "file-url" => url, - "serial_number" => serial_number - } - end - end - { - "archive-title" => archive.title, - "description" => archive.description, - "created_at" => archive.created_at.strftime('%Y%m%d').to_i, - "archive-url" => archive.url, - "url" => archive.url, - "statuses" => statuses, - "sort_number" => archive.sort_number, - "is_top" => (archive.is_top ? 1 : 0), - "files" => files, - "idx" => (idx + 1) - } - end - each_data_count.push(archives.length) - sorted = archives - cats << { - "category-title" => tag_name, - "archives" => sorted, - "url_to_edit" => url_to_edit - } - end - end - cats_last = cats - else - files_by_category = ArchiveFile.where(is_hidden: false,:title.nin=>["",nil]).filter_by_categories.filter_by_tags.order_by(:created_at => "desc").group_by(&:category) - each_data_count = [] - categories = files_by_category.keys - categories_sort = get_sorted_cat_with_filter(categories,'orm') - cats = categories_sort.collect do |category| - url_to_edit = OrbitHelper.user_has_cateogry?(category) ? "/admin/archive_files?filters[category][]=#{category.id.to_s}" : "" - serial_number = 0 - archives = files_by_category[category].sort_by{|k| [(k["is_top"] ? 0 : 1) ,(k["sort_number"].nil? ? Float::INFINITY : sort_order * k["sort_number"].to_i),-k["created_at"].to_i]}.collect.with_index do |archive,idx| - statuses = archive.statuses_with_classname.collect do |status| - { - "status" => status["name"] || "", - "status-class" => "status-#{status['classname']}" - } - end - files = [] - archive.archive_file_multiples.order_by(:sort_number=>'desc').each do |file| - if file.choose_lang.include?(I18n.locale.to_s) - serial_number += 1 - title = (file.file_title.blank? ? File.basename(file.file.path) : file.file_title) rescue "" - extension = file.file.file.extension.downcase rescue "" - files << { - "file-name" => title, - "file-type" => extension, - "file-url" => "/xhr/archive/download?file=#{file.id}", - "serial_number" => serial_number - } - end - end - if archive.urls.present? - archive.urls.each_with_index do |url,i| - serial_number += 1 - files << { - "file-name" => archive.title, - "file-type" => archive.get_url_text(i), - "file-url" => url, - "serial_number" => serial_number - } - end - end - { - "archive-title" => archive.title, - "description" => archive.description, - "created_at" => archive.created_at.strftime('%Y%m%d').to_i, - "archive-url" => archive.url, - "url" => archive.url, - "statuses" => statuses, - "sort_number" => archive.sort_number, - "is_top" => (archive.is_top ? 1 : 0), - "files" => files, - "idx" => (idx + 1) - } - end - each_data_count.push(archives.length) - sorted = archives - { - "category-title" => (categories.count > 1 ? category.title : ""), - "archives" => sorted, - "url_to_edit" => url_to_edit - } - end - if page_data_count != 0 - all_data_count = 0 - data_start = -1 - data_end = -1 - for i in 0...each_data_count.length - all_data_count_before = all_data_count - all_data_count += each_data_count[i] - if ( data_start == -1 && (all_data_count > (page_no - 1) * page_data_count) ) - data_start = i - data_start_first = (page_no - 1) * page_data_count - all_data_count_before - end - if ( data_end == -1 && (all_data_count > (page_no * page_data_count - 1)) ) - data_end = i - data_end_last = (page_no * page_data_count - 1) - all_data_count_before - end - end - if ( data_end == -1 && data_start != -1) - data_end = i - data_end_last = -1 - end - if data_start!=-1 && page_no>=1 - cats_last = [] - for i in data_start..data_end - if data_start != data_end - if i==data_start - cats_last << Hash[cats[i].map{|k,v| [k , (k=='archives' ? v[data_start_first..-1] : v)]}] - elsif i==data_end - cats_last << Hash[cats[i].map{|k,v| [k , (k=='archives' ? v[0..data_end_last] : v)]}] - else - cats_last << cats[i] - end - else - cats_last << Hash[cats[i].map{|k,v| [k,(k=='archives' ? v[data_start_first..data_end_last] : v)]}] - end - end - else - cats_last = [Hash[cats[0].map{|k,v| [k,'']}]] - end - @@total_pages = (all_data_count.to_f / page_data_count).ceil - else - if page_no!=1 - cats_last = [Hash[cats[0].map{|k,v| [k,'']}]] - @@total_pages = 0 - else - cats_last = cats - @@total_pages = 1 - end - end - end + if !params['title'].nil? + files_by_category = ArchiveFile.can_display.sorted.group_by(&:category) + categories = files_by_category.keys + categories_sort = get_sorted_cat_with_filter(categories,'orm') + categories_sort.each_with_index do |category, cat_idx| + url_to_edit = "" + flag = false + archives = [] + serial_number = 0 + files_by_category[category].each_with_index do |archive,idx| + if archive.title == params['title'] + flag = true + data, serial_number, idx = archive.get_frontend_data(locale, serial_number, idx) + data + end + end + if flag + cats_last << { + "category-id" => category.id, + "category-title" => category.title, + "archives" => archives, + "url_to_edit" => url_to_edit + } + end + end + else + if OrbitHelper.page_data_count > 0 + OrbitHelper.set_page_data_count 0 + page = Page.where(:page_id => params["page_id"]).first rescue nil + if !page.nil? + page.data_count = 0 + page.save + end + end + categories = params["categories"].blank? ? OrbitHelper.page_categories : params["categories"] + tags = params["tags"].blank? ? OrbitHelper.page_tags : params["tags"] + module_app = ModuleApp.where(:key=>'archive').first + if categories == ["all"] + categories = module_app.categories.pluck(:id).map(&:to_s) + end + if tags == ["all"] + tags = module_app.tags.sort_by{|tag| ((module_app.asc rescue true) ? tag.sort_number.to_i : -tag.sort_number.to_i)}.map{|tag| tag.id.to_s} + tags << nil + end + if params[:data_count].to_i <=0 + page_data_count = 0 + else + page_data_count = params[:data_count].to_i + end + if params[:page_no].nil? + page_no = 1 + else + page_no = params[:page_no].to_i + end + if (tags.count > 1 && categories.count <= 1 rescue false) + archive_files = ArchiveFile.can_display.filter_by_categories(categories).sorted + group_archive_files = {} + cats = [] + each_data_count = [] + archive_files_ids = archive_files.pluck(:id) + archive_files_ids_count = archive_files_ids.count + tags.each_with_index do |tag_id, tag_idx| + tag_name = "" + if tag_id + tag = Tag.find(tag_id) rescue nil + next if tag.nil? + url_to_edit = OrbitHelper.current_user ? "/admin/archive_files?filters[tags][]=#{tag_id}" : "" + taggings = Tagging.where(:tag_id=>tag_id).pluck(:taggable_id) + archives = archive_files.where(:id.in=>taggings).to_a + archive_files_ids = archive_files_ids - archives.map{|a| a.id} + tag_name = Tag.find(tag_id).name + else + if categories.count == 1 + url_to_edit = OrbitHelper.current_user ? "/admin/archive_files?filters[category][]=#{categories[0]}" : "" + else + url_to_edit = OrbitHelper.current_user ? "/admin/archive_files" : "" + end + archives = [] + if tags.count > 1 && archive_files_ids_count != archive_files_ids.count + tag_name = I18n.t("archive.other") + archives = archive_files.where(:id.in=>archive_files_ids).to_a + else + archives = archive_files.to_a + end + end + serial_number = 0 + if archives.count != 0 + archives = archives.map.with_index do |archive,idx| + + + end + each_data_count.push(archives.length) + sorted = archives + cats << { + "category-id" => tag_id, + "category-title" => tag_name, + "archives" => sorted, + "url_to_edit" => url_to_edit + } + end + end + cats_last = cats + else + files_by_category = ArchiveFile.can_display.filter_by_categories(categories).filter_by_tags(tags).sorted.group_by(&:category) + each_data_count = [] + categories = files_by_category.keys + categories_sort = get_sorted_cat_with_filter(categories,'orm') + cats = categories_sort.collect.with_index do |category, cat_idx| + url_to_edit = OrbitHelper.user_has_cateogry?(category) ? "/admin/archive_files?filters[category][]=#{category.id.to_s}" : "" + serial_number = 0 + archives = files_by_category[category].collect.with_index do |archive,idx| + data, serial_number, idx = archive.get_frontend_data(locale, serial_number, idx) + data + end + each_data_count.push(archives.length) + sorted = archives + { + "category-id" => category.id, + "category-title" => (categories.count > 1 ? category.title : ""), + "archives" => sorted, + "url_to_edit" => url_to_edit + } + end + if page_data_count != 0 + all_data_count = 0 + data_start = -1 + data_end = -1 + for i in 0...each_data_count.length + all_data_count_before = all_data_count + all_data_count += each_data_count[i] + if ( data_start == -1 && (all_data_count > (page_no - 1) * page_data_count) ) + data_start = i + data_start_first = (page_no - 1) * page_data_count - all_data_count_before + end + if ( data_end == -1 && (all_data_count > (page_no * page_data_count - 1)) ) + data_end = i + data_end_last = (page_no * page_data_count - 1) - all_data_count_before + end + end + if ( data_end == -1 && data_start != -1) + data_end = i + data_end_last = -1 + end + if data_start!=-1 && page_no>=1 + cats_last = [] + for i in data_start..data_end + if data_start != data_end + if i==data_start + cats_last << Hash[cats[i].map{|k,v| [k , (k=='archives' ? v[data_start_first..-1] : v)]}] + elsif i==data_end + cats_last << Hash[cats[i].map{|k,v| [k , (k=='archives' ? v[0..data_end_last] : v)]}] + else + cats_last << cats[i] + end + else + cats_last << Hash[cats[i].map{|k,v| [k,(k=='archives' ? v[data_start_first..data_end_last] : v)]}] + end + end + else + cats_last = [Hash[cats[0].map{|k,v| [k,'']}]] + end + @@total_pages = (all_data_count.to_f / page_data_count).ceil + else + if page_no!=1 + cats_last = [Hash[cats[0].map{|k,v| [k,'']}]] + @@total_pages = 0 + else + cats_last = cats + @@total_pages = 1 + end + end + end + end + if cats_last.count != 0 + cats_last.each_with_index do |h, i| + h["active_class"] = (i == 0 ? 'active' : '') + end end { "categories" => cats_last, - "extras" =>{ - "serial_number-head" => I18n.t("archive.serial_number"), - "date-head" => I18n.t("archive.updated_at"), - "title-head" => I18n.t(:name), - "description-head"=>I18n.t("archive.description"), - "file-head" =>I18n.t("archive.download_file"), - "in_class" => in_class - } + "extras" =>{ + "serial_number-head" => I18n.t("archive.serial_number"), + "date-head" => I18n.t("archive.updated_at"), + "title-head" => I18n.t(:name), + "description-head"=>I18n.t("archive.description"), + "file-head" =>I18n.t("archive.download_file"), + "in_class" => in_class + } } end @@ -336,28 +231,28 @@ class ArchivesController < ApplicationController file_id = params[:file] file = ArchiveFileMultiple.find(file_id) rescue nil if !file.nil? - file.download_count = file.download_count + 1 - file.save - @url = file.file.url - begin - @path = file.file.file.file rescue "" - @filename = @path.split("/").last - @ext = @path.split("/").last.to_s.split(".").last - if @ext == "png" || @ext == "jpg" || @ext == "bmp" || @ext == "pdf" - render "download_file",:layout=>false - else - if (current_site.accessibility_mode rescue false) - render "redirect_to_file",:layout=>false - else - send_file(@path) - end - end - rescue - redirect_to @url - end - else - render :file => "#{Rails.root}/app/views/errors/404.html", :layout => false, :status => :not_found - end + file.download_count = file.download_count + 1 + file.save + @url = file.file.url + begin + @path = file.file.file.file rescue "" + @filename = @path.split("/").last + @ext = @path.split("/").last.to_s.split(".").last + if @ext == "png" || @ext == "jpg" || @ext == "bmp" || @ext == "pdf" + render "download_file",:layout=>false + else + if (current_site.accessibility_mode rescue false) + render "redirect_to_file",:layout=>false + else + send_file(@path) + end + end + rescue + redirect_to @url + end + else + render :file => "#{Rails.root}/app/views/errors/404.html", :layout => false, :status => :not_found + end end def group_by_category @@ -375,137 +270,143 @@ class ArchivesController < ApplicationController files_by_category_tag.keys.each do |t| archives = [] archive_counts = archive_counts + 1 - files_by_category_tag[t].each_with_index do |archive,index| - a = { - "archive-title" => archive.title, - "description" => archive.description, - "archive-url" => archive.url, - "archive_url" => OrbitHelper.widget_more_url - } - - if t.count > 1 - t.each do |inner_tag| - index = ts.index{|tot| tot["tag-name"] == inner_tag.name} - if !index.nil? - break if ts[index]["archives"].count >= OrbitHelper.widget_data_count - ts[index]["archives"] << a - else - break if archives.count >= OrbitHelper.widget_data_count - archives << a + files_by_category_tag[t].each do |archive| + a = { + "archive-title" => archive.title, + "description" => archive.description, + "archive-url" => archive.url, + "archive_url" => OrbitHelper.widget_more_url + } + + if t.count > 1 + t.each do |inner_tag| + idx = ts.index{|tot| tot["tag-name"] == inner_tag.name} + if !idx.nil? + break if ts[idx]["archives"].count >= OrbitHelper.widget_data_count + ts[idx]["archives"] << a + else + break if archives.count >= OrbitHelper.widget_data_count + archives << a + end end + else + break if archives.count >= OrbitHelper.widget_data_count + archives << a end - else - break if archives.count >= OrbitHelper.widget_data_count - archives << a + end - - end - ts << { - "tag-name" => (t[0].name rescue ""), - "archives" => archives - } if !archives.blank? && archive_counts < OrbitHelper.widget_data_count + ts << { + "tag-name" => (t[0].name rescue ""), + "archives" => archives + } if !archives.blank? && archive_counts < OrbitHelper.widget_data_count end { + "category-id" => cat.id, "category-title" => cat.title, "tags" => ts } end { "categories" => cats, - "extras" => {"widget-title" => "Archives","more_url" => OrbitHelper.widget_more_url} + "extras" => {"more_url" => OrbitHelper.widget_more_url} } end - - def widget - page_data_count = OrbitHelper.widget_data_count - categories = OrbitHelper.widget_categories #data are categories' ids or 'all' - @categories = [] - if categories.first == "all" - categories = OrbitHelper.widget_module_app.categories - categories_sort = get_sorted_cat_with_filter(categories,'orm') - @categories = categories_sort.collect do |cat| - { - "title" => cat.title, - "id" => cat.id.to_s - } - end - else - categories_sort = get_sorted_cat_with_filter(categories,'id') - @categories = categories_sort.collect do |cat| - { - "title" => cat.title, - "id" => cat.id.to_s - } - end + def get_archives_count(cats_with_archives) + cats_with_archives.map{|h| (h["archives"].count rescue 0)}.sum + end + def get_anncs_for_pack_data(categories,tags) + page_data_count = OrbitHelper.widget_data_count + sort_order = (ArchiveSortOrder.first['sort_order'] rescue false) ? 1 : -1 #Order with ascending + subpart = OrbitHelper.get_current_widget + filter_cache_parent_id = subpart.id.to_s + categories.to_s + tags.to_s + page_data_count.to_s + archive_cache = ArchiveCache.where(parent_id: filter_cache_parent_id,locale: I18n.locale.to_s) + count = archive_cache.count + if count > 1 + archive_cache.limit(count-1).destroy end - cats = @categories.collect do |cat| - if ArchiveSortOrder.first.sort_order #Order with ascending - archives_all = ArchiveFile.can_display.where(:category_id => cat["id"]).filter_by_tags(OrbitHelper.widget_tags) - temp_with_nil = archives_all.in(sort_number: nil).order_by(created_at: 'desc') - temp_no_nil = archives_all.not_in(sort_number: nil).order_by(sort_number: 'asc',created_at: 'desc') - archives_temp = temp_no_nil.concat(temp_with_nil) - archives = archives_temp.collect do |archive| - if archive.archive_file_multiples.count==0 - url = (archive[:url][OrbitHelper.get_site_locale].to_s.empty? ? 'javascript:void' : archive[:url][OrbitHelper.get_site_locale]) - else - url = archive.archive_file_multiples.count > 1 ? (OrbitHelper.widget_more_url + '?title=' + archive.title.to_s) : "/xhr/archive/download?file=#{archive.archive_file_multiples.first.id}" - end - { - "archive-title" => archive.title, - "description" => archive.description, - "archive-url" => archive.url, - "archive_url" => url - } - end + if archive_cache.count == 0 + @categories = [] + categorie_ids = [] + if categories.first == "all" + categories = OrbitHelper.widget_module_app.categories + categories_sort = get_sorted_cat_with_filter(categories,'orm') + @categories = categories_sort.collect do |cat| + cat_id = cat.id.to_s + categorie_ids << cat_id + { + "title" => cat.title, + "id" => cat_id + } + end else - archives_temp = ArchiveFile.can_display.where(:category_id => cat["id"]).filter_by_tags(OrbitHelper.widget_tags).order_by(sort_number: 'desc',created_at: 'desc') - archives = archives_temp.collect do |archive| - if archive.archive_file_multiples.count==0 - url = (archive[:url][OrbitHelper.get_site_locale].to_s.empty? ? 'javascript:void' : archive[:url][OrbitHelper.get_site_locale]) - else - url = archive.archive_file_multiples.count > 1 ? (OrbitHelper.widget_more_url + '?title=' + archive.title.to_s) : "/xhr/archive/download?file=#{archive.archive_file_multiples.first.id}" - end - { - "archive-title" => archive.title, - "description" => archive.description, - "archive-url" => archive.url, - "archive_url" => url - } - end + categories_sort = get_sorted_cat_with_filter(categories,'id') + @categories = categories_sort.collect do |cat| + cat_id = cat.id.to_s + categorie_ids << cat_id + { + "title" => cat.title, + "id" => cat_id + } + end end - { - "category-title" => cat["title"], - "category-id" => cat["id"], - "archives" => archives - } + cats_with_archives = @categories.collect do |cat| + archives_sorted = ArchiveFile.can_display.where(:category_id => cat["id"]).filter_by_tags(tags).sorted + archives = archives_sorted.collect do |archive| + archive_url = archive.get_archive_url(locale, OrbitHelper.widget_more_url) + { + "archive-title" => archive.title, + "description" => archive.description, + "archive-url" => archive.url, + "archive_url" => archive_url + } + end + { + "category-title" => cat["title"], + "category-id" => cat["id"], + "archives" => archives + } + end + cats_with_archives = cats_with_archives.select{|h| h["archives"].count != 0} + ArchiveCache.create(parent_id: filter_cache_parent_id,locale: I18n.locale.to_s,filter_result: cats_with_archives) + else + c = archive_cache.first + cats_with_archives = c.filter_result end + cats_with_archives + end + def widget + ArchiveFile.check_sort_number + page_data_count = OrbitHelper.widget_data_count + categories = OrbitHelper.widget_categories #data are categories' ids or 'all' + tags = OrbitHelper.widget_tags + cats_with_archives = get_anncs_for_pack_data(categories, tags) { - "categories" => cats, - "extras" => {"widget-title" => "Archives","more_url" => (OrbitHelper.widget_more_url + '?data_count=' + page_data_count.to_s)} + "categories" => cats_with_archives, + "extras" => {"more_url" => (OrbitHelper.widget_more_url + '?data_count=' + page_data_count.to_s)} } end private - def get_sorted_cat_with_filter(categories,cat_type) - all_categories_with_nil = ArchiveCategory.all.in(sort_number: nil).uniq{|c| c.category_id }.to_a rescue [] - sort_method = ArchiveSortOrder.first.sort_order ? 'asc' : 'desc' - all_categories_no_nil = ArchiveCategory.all.not_in(sort_number: nil).order_by(sort_number: sort_method).uniq{|c| c.category_id }.to_a rescue [] - all_categories_id = all_categories_no_nil.concat(all_categories_with_nil).collect do |cat| - cat.category_id.to_s - end - if cat_type=='id' - categories_temp = ModuleApp.where(:key => "archive").first.categories - categories = categories_temp.select{|cat| categories.include? cat.id.to_s} - end - categories_sort = [] - all_categories_id.each do |cat_id| - category_selected = categories.select{|category| cat_id == category.id.to_s} - if category_selected.length!=0 - categories_sort << category_selected[0] - end - end - categories_sort - end - def self.get_total_pages - @@total_pages - end + def get_sorted_cat_with_filter(categories,cat_type) + all_categories_with_nil = ArchiveCategory.all.in(sort_number: nil).uniq{|c| c.category_id }.to_a rescue [] + sort_method = ArchiveSortOrder.first.sort_order ? 'asc' : 'desc' + all_categories_no_nil = ArchiveCategory.all.not_in(sort_number: nil).order_by(sort_number: sort_method).uniq{|c| c.category_id }.to_a rescue [] + all_categories_id = all_categories_no_nil.concat(all_categories_with_nil).collect do |cat| + cat.category_id.to_s + end + if cat_type=='id' + categories_temp = ModuleApp.where(:key => "archive").first.categories + categories = categories_temp.select{|cat| categories.include? cat.id.to_s} + end + categories_sort = [] + all_categories_id.each do |cat_id| + category_selected = categories.select{|category| cat_id == category.id.to_s} + if category_selected.length!=0 + categories_sort << category_selected[0] + end + end + categories_sort + end + def self.get_total_pages + @@total_pages + end end diff --git a/app/helpers/admin/archive_files_helper.rb b/app/helpers/admin/archive_files_helper.rb index 263353c..18dbe12 100644 --- a/app/helpers/admin/archive_files_helper.rb +++ b/app/helpers/admin/archive_files_helper.rb @@ -1,2 +1,25 @@ module Admin::ArchiveFilesHelper -end + def page_for_archive_file(archive_file) + archive_file_page = nil + pages = Page.where(:module=>'archive').select{|page| page.enabled_for.include?(I18n.locale.to_s)} + + pages.each do |page| + if page.categories.count ==1 + if (page.categories.include?(archive_file.category.id.to_s) rescue false) + archive_file_page = page + end + end + break if !archive_file_page.nil? + end + if archive_file_page.nil? + pages.each do |page| + if (page.categories.include?(archive_file.category.id.to_s) rescue false) + archive_file_page = page + end + break if !archive_file_page.nil? + end + end + archive_file_page = pages.first if archive_file_page.nil? + request.protocol+(request.host_with_port+archive_file_page.url+'/'+archive_file.to_param).gsub('//','/') rescue "#" + end +end \ No newline at end of file diff --git a/app/models/archive_cache.rb b/app/models/archive_cache.rb new file mode 100644 index 0000000..09fb654 --- /dev/null +++ b/app/models/archive_cache.rb @@ -0,0 +1,6 @@ +class ArchiveCache + include Mongoid::Document + field :parent_id + field :filter_result + field :locale,type: String,default: 'zh_tw' +end \ No newline at end of file diff --git a/app/models/archive_file.rb b/app/models/archive_file.rb index f3417c3..e18cf40 100644 --- a/app/models/archive_file.rb +++ b/app/models/archive_file.rb @@ -7,8 +7,42 @@ class ArchiveFile include OrbitModel::Status include OrbitTag::Taggable include Slug - - + require 'archive_model/cache' + unless defined?(ArchiveModel) + Object.send(:remove_const, 'ArchiveModel') rescue nil + $LOADED_FEATURES.select!{|p| !p.include? 'archive_model'} + require 'archive_model/cache' + end + include ::ArchiveModel::Cache + SubPart.class_eval { include ::ArchiveModel::Cache } + Page.class_eval { include ::ArchiveModel::Cache } + index({tmp_sort_number: 1}) + scope :sorted, ->{order(tmp_sort_number: :asc)} + attr_accessor :org_tag_ids,:org_category_id + def tags=(ids) + self.org_tag_ids = self.tag_ids + super(ids) + end + def category=(cat) + self.org_category_id = self.category_id + super(cat) + end + def tag_ids=(ids) + self.org_tag_ids = self.tag_ids + super(ids) + end + def category_id=(cat_id) + self.org_category_id = self.category_id + super(cat_id) + end + def []=(index,value) + if index.to_s=='tags' || index.to_s=='tag_ids' + self.org_tag_ids = self.tag_ids + elsif index.to_s=='category' || index.to_s=='category_id' + self.org_category_id = self.category_id + end + super(index,value) + end # include Tire::Model::Search # include Tire::Model::Callbacks @@ -16,12 +50,29 @@ class ArchiveFile # PAYMENT_TYPES = @site_valid_locales before_save do - cat = self.category rescue nil - if cat && ArchiveCategory.where(:category_id => cat.id).count==0 - ArchiveCategory.create(category_id: cat.id.to_s,sort_number: cat.sort_number) + unless @skip_callback + cat = self.category rescue nil + if cat && ArchiveCategory.where(:category_id => cat.id).count==0 + ArchiveCategory.create(category_id: cat.id.to_s,sort_number: cat.sort_number) + end + end + end + after_save do + unless @skip_callback + if self.sort_number_changed? + self.class.recalc_sort_number + elsif self.tmp_sort_number.nil? + if self.is_top + self.tmp_sort_number = (ArchiveSortOrder.first.max_sort_number.to_i + 1 rescue 1) + @skip_callback = true + self.save + @skip_callback = false + else + self.class.recalc_sort_number + end + end end end - field :title, as: :slug_title, localize: true field :description, localize: true field :urls, localize: true, type: Array @@ -33,11 +84,15 @@ class ArchiveFile field :deadline , :type => DateTime field :uid, type: String field :sort_number, type: Integer + field :tmp_sort_number, type: Integer field :rss2_sn - + field :feed_uid + field :site_feed_id + field :feed_data, :type => Hash, :default => {} # scope :can_display,where(is_hidden: false) - scope :can_display, ->{where(is_hidden: false).order_by([:is_top, :desc],[:sort_number, :asc])} - + scope :local_data, ->{where(:site_feed_id=>nil)} + scope :can_display, ->{where(is_hidden: false,:title.nin=>["",nil])} + scope :can_display_and_sorted, ->{can_display.sorted} # belongs_to :archive_file_category has_many :archive_file_multiples, :autosave => true, :dependent => :destroy @@ -46,7 +101,7 @@ class ArchiveFile # validates :title, :at_least_one => true - after_save :save_archive_file_multiples + after_save :save_archive_file_multiples, :update_tmp_sort_number before_save :add_http @@ -58,7 +113,32 @@ class ArchiveFile end end end - + def self.check_sort_number + archive_sort_order = ArchiveSortOrder.first + if archive_sort_order && archive_sort_order.need_update_sort + self.recalc_sort_number(archive_sort_order.sort_order) + end + end + def self.recalc_sort_number(sort_order=nil) + if sort_order.nil? + sort_order = (ArchiveSortOrder.first['sort_order'] rescue false) ? 1 : -1 + end + sorted_archives = self.all.to_a.sort_by{|k| [(k["is_top"] ? 0 : 1) ,(k["sort_number"].nil? ? Float::INFINITY : sort_order * k["sort_number"].to_i),-k["created_at"].to_i]} + sorted_archives.each_with_index do |a, i| + a.instance_variable_set(:@skip_callback, true) + self.where(:id=>a.id).update_all(:tmp_sort_number => i) + end + max_sort_number = (sorted_archives.count != 0) ? (sort_order == 1 ? sorted_archives.last.tmp_sort_number : sorted_archives.first.tmp_sort_number) : 0 + ArchiveSortOrder.update_all(:max_sort_number=>max_sort_number,:need_update_sort=>false) + self.create_indexes + return max_sort_number + end + def update_tmp_sort_number + return if @skip_callback + if self.new_record? || self.sort_number_changed? + self.class.recalc_sort_number + end + end def get_url_text(idx=0,org=false) url_text = self.url_texts[idx] rescue nil if org @@ -67,8 +147,293 @@ class ArchiveFile url_text.present? ? url_text : "link" end end + def get_data(more_url=nil, base_url=nil, cat_ids=nil, tag_ids=nil, locales=nil) + if locales.nil? + locales = Site.first.in_use_locales rescue I18n.available_locales + end + locale = I18n.locale if locale.nil? + base_url = Site.first.root_url if base_url.nil? + user = User.find(self.create_user_id) rescue nil + a = {} + if !user.nil? + author_translations = user.member_name_translations + locales.each do |locale| + if author_translations[locale].blank? + author_translations[locale] = user.user_name + end + end + else + author_translations = {} + end + a["author_translations"] = author_translations + if more_url.nil? + if cat_ids.nil? + cat_ids = [self.category_id] + end + if tag_ids.nil? + tag_ids = self.tag_ids + end + basic_query = {:module => 'archive',:enabled_for=>locale} + if !cat_ids.blank? + query = basic_query.merge({:categories.all => cat_ids}) + else + query = basic_query.clone + end + if !tag_ids.blank? + query = query.merge({:tags.all => tag_ids}) + end + page = Page.where(query).first || Page.where(basic_query).first + more_url = page ? page.get_url : nil + end + if more_url + a['show_url'] = "#{more_url}/#{self.to_param}" + end + a["id"] = self.uid + a["title_translations"] = self.title_translations + a["description_translations"] = {} + description_translations = self.description_translations + locales.each do |locale| + locale = locale.to_s + a["description_translations"][locale] = self.class.smart_convertor(description_translations[locale],base_url) if !description_translations[locale].blank? + end + a["created_at"] = self.created_at + a["url_translations"] = self.url_translations + a["tags"] = [] + a["category"] = {} + a["files"] = [] + a["params"] = self.to_param + self.tags.each do |tag| + if !tag_ids.include?(tag.id.to_s) + tag_ids << tag.id.to_s + end + a["tags"] << {"name_translations" => tag.name_translations} + end + cat = self.category + a["sort_number"] = nil + a["category"] = {"title_translations" => (cat.title_translations rescue {})} + self.archive_file_multiples.order_by(:sort_number=>'desc').each do |file| + if file.choose_lang.include?(I18n.locale.to_s) + title_translations = {} + locales.each do |locale| + title_translations[locale] = (file.file_title_translations[locale].blank? ? File.basename(file.file.path) : file.file_title_translations[locale]) rescue "" + end + extension = file.file.file.extension.downcase rescue "" + a["files"] << { + "file-name_translations" => title_translations, + "file-type" => extension, + "file-url" => "#{base_url}/xhr/archive/download?file=#{file.id}" + } + end + end + return a + end + def get_related_feeds + related_feeds = ArchiveFileFeed.where({:category_ids=>self.category_id.to_s, :tag_ids.in=>self.tag_ids.map(&:to_s)}).to_a + end + def update_feed_statuses(statuses) #ex: statuses = {:is_top=>1} + self.update(statuses) + end + def update_feed_data(data) + data = data.clone + feed_data_keys = ["author_translations", "tags", "category", "files", "params", "show_url"] + data["feed_data"] = {} + data["feed_uid"] = data["id"] + data.delete("id") + feed_data_keys.each do |k| + data["feed_data"][k] = data[k] + data.delete(k) + end + @skip_callback = true + # ArchiveSortOrder.update_all(:need_update_sort=>true) + self.update(data) + @skip_callback = nil + end + def self.feeds_finish_callback(action="update", args={}) + if action != "destroy" + if action == "update_all" + data = args["data"] + if data + site_feed_id = args["feed_id"] + category_id = args["category_id"] + data.each do |a| + a["category_id"] = category_id if category_id + archive_file = self.where(:feed_uid=> a["id"], :site_feed_id=>site_feed_id).first + if archive_file.nil? + archive_file = self.new(:feed_uid=> a["id"], :site_feed_id=>site_feed_id) + archive_file.instance_variable_set(:@skip_callback, true) + archive_file.save + end + archive_file.update_feed_data(a) + end + self.where(:feed_uid.nin=>data.map{|a| a["id"]}, :site_feed_id=>site_feed_id).destroy + end + end + self.recalc_sort_number + end + ArchiveCache.destroy_all + end + def get_files(locale=nil, serial_number=0) + if locale.nil? + locale = I18n.locale.to_s + end + files = [] + if self.feed_uid + files = self.feed_data["files"].map do |h| + serial_number += 1 + h = h.clone + h["file-name"] = h["file-name_translations"][locale] + h.delete("file-name_translations") + h["serial_number"] = serial_number + h + end + else + self.archive_file_multiples.order_by(:sort_number=>'desc').each do |file| + if file.choose_lang.include?(locale) + title = (file.file_title.blank? ? File.basename(file.file.path) : file.file_title) rescue "" + extension = file.file.file.extension.downcase rescue "" + serial_number += 1 + files << { + "file-name" => title, + "file-type" => extension, + "file-url" => "/xhr/archive/download?file=#{file.id}", + "serial_number" => serial_number + } + end + end + if self.urls.present? + self.urls.each_with_index do |url,i| + serial_number += 1 + files << { + "file-name" => self.title, + "file-type" => self.get_url_text(i), + "file-url" => url, + "serial_number" => serial_number + } + end + end + end + [files, serial_number] + end + def get_frontend_data(locale=nil, serial_number=0, idx=0, show_tags=false, more_url=nil) + created_at_int = self.created_at.strftime('%Y%m%d').to_i + statuses = self.statuses_with_classname.collect do |status| + { + "status" => status["name"] || "", + "status-class" => "status-#{status['classname']}" + } + end + files, serial_number = self.get_files(locale, serial_number) + data = { + "archive-file-url" => (files.count != 0 ? files[0]["file-url"] : "javascript:void"), + "archive-title" => self.title, + "description" => self.description, + "created_at" => created_at_int, + "archive-url" => self.url, + "url" => self.url, + "statuses" => statuses, + "sort_number" => self.sort_number, + "is_top" => (self.is_top ? 1 : 0), + "files" => files, + "idx" => (idx + 1) + } + if show_tags + data["tags"] = self.tags.map do |tag| + {"name"=>tag.name, "url"=>more_url.to_s + "?tags[]=#{tag.id}"} + end + end + if more_url + data["index_url"] = more_url + end + return [data, serial_number, idx] + end + def get_archive_url(locale, more_url) + archive_url = "" + if self.feed_uid + files = self.feed_data["files"] + if files.count == 0 + tmp_urls = self.urls + archive_url = ((tmp_urls.nil? || tmp_urls.count == 0) ? 'javascript:void' : (tmp_urls.count > 1 ? (more_url + '?title=' + self.title.to_s) : tmp_urls[0])) + else + archive_url = files.count > 1 ? (more_url + '?title=' + self.title.to_s) : files[0]["file-url"] + end + else + if self.archive_file_multiples.count==0 + tmp_urls = self.urls + archive_url = ((tmp_urls.nil? || tmp_urls.count == 0) ? 'javascript:void' : (tmp_urls.count > 1 ? (more_url + '?title=' + self.title.to_s) : tmp_urls[0])) + else + archive_url = self.archive_file_multiples.count > 1 ? (more_url + '?title=' + self.title.to_s) : "/xhr/archive/download?file=#{self.archive_file_multiples.first.id}" + end + end + archive_url + end + def notify_feed(type="create") + related_feeds = self.get_related_feeds.select{|feed| feed.remote_urls.count != 0} + if related_feeds.count != 0 + archive_data = self.get_data + if type == "destroy" + tmp_data = {'type'=>'destroy', 'data'=>[self.uid]} + else + tmp_data = {'type'=>type, 'data'=>[archive_data.to_json]} + end + request = Net::HTTP::Post.new('/xhr/feeds/notify_change', 'Content-Type' => 'application/json') + related_feeds.each do |feed| + tmp_data['uid'] = feed.uid + request.body = tmp_data.to_json + feed.remote_urls.each do |remote_url| + uri = URI(remote_url) + http_req = Net::HTTP.new(uri.host, uri.port) + if remote_url.include?('https') + http_req.use_ssl = true + end + response = self.class.http_request( http_req , request ) + end + end + end + end + def self.notify_feed_delete(ids) + all_feeds = ArchiveFileFeed.all.select{|feed| feed.remote_urls.count != 0} + if all_feeds.count != 0 + tmp_data = {'type'=>'destroy'} + request = Net::HTTP::Post.new('/xhr/feeds/notify_change', 'Content-Type' => 'application/json') + all_feeds.each do |feed| + feed_uid = feed.uid + feed_cache = ArchiveFileFeedCache.where(:uid=>feed_uid).first + if feed_cache + tmp_data['uid'] = feed_uid + tmp_data['data'] = ids & JSON.parse(feed_cache.content)["archives"].map{|a| a["id"]} + request.body = tmp_data.to_json + if tmp_data['data'].count != 0 + feed.remote_urls.each do |remote_url| + uri = URI(remote_url) + http_req = Net::HTTP.new(uri.host, uri.port) + if remote_url.include?('https') + http_req.use_ssl = true + end + response = self.http_request( http_req , request ) + end + end + end + end + end + end + def self.http_request(http, request) + response = http.request(request) + if response.code.to_i == 301 || response.code.to_i == 302 + location = response["location"] + new_uri = URI(location) + http = Net::HTTP.new(new_uri.host, new_uri.port) + if location.include?('https') + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + request.instance_variable_set(:@path, new_uri.path) + response = self.http_request(http, request) + end + response + end protected def add_http + return if @skip_callback in_use_locales = Site.first.in_use_locales temp_urls = {} in_use_locales.each do |locale| @@ -134,10 +499,34 @@ class ArchiveFile end def save_archive_file_multiples + return if @skip_callback self.archive_file_multiples.each do |t| if t.should_destroy t.destroy end end end + def self.smart_convertor(text,url) + doc = Nokogiri.HTML(text) + doc.search('a[href]').each do |link| + if link['href'].nil? + link.delete 'href' + elsif link['href'].start_with?('/') + link['href'] = url + link['href'] + elsif link['href'].start_with?('../') + link['href'] = url + link['href'][3..-1] + end + end + doc.search('img[src]').each do |link| + if link['src'].nil? + link.delete 'src' + elsif link['src'].start_with?('/') + link['src'] = url + link['src'] + elsif link['src'].start_with?('../') + link['src'] = url + link['src'][3..-1] + end + end + + return doc.css('body').inner_html + end end diff --git a/app/models/archive_file_feed.rb b/app/models/archive_file_feed.rb new file mode 100644 index 0000000..0ed86ff --- /dev/null +++ b/app/models/archive_file_feed.rb @@ -0,0 +1,107 @@ +class ArchiveFileFeed + include Mongoid::Document + include Mongoid::Timestamps + include Slug + + field :title, as: :slug_title, type: String, localize: true + field :tag_ids, type: Array, default: [] + field :category_ids, type: Array, default: [] + field :remote_urls, type: Array, default: [] + before_save do + ArchiveFileFeedCache.where(uid: self.uid).each do |cache| + cache.regenerate + end + end + def self.create_feed_cache(archive_file=nil,archive_file_feed=nil) + Thread.new do + if !archive_file.nil? + ArchiveFileFeed.where(:tag_ids.in => Array(archive_file.tag_ids).collect{|v| v.to_s}).each do |archive_file_feed| + uid = archive_file_feed.uid + ArchiveFileFeedCache.where(:uid => uid).each do |cache| + cache.regenerate + end + end + elsif !archive_file_feed.nil? + uid = archive_file_feed.uid + ArchiveFileFeedCache.where(:uid => uid).each do |cache| + cache.regenerate + end + end + end + end + def generate_one_cache_timeout(base_url: nil, timeout: nil) + begin + if timeout.nil? + feed_cache_content = self.generate_one_cache(base_url: base_url) + else + Timeout::timeout(timeout) do + feed_cache_content = nil + thread = Thread.new do + feed_cache_content = self.generate_one_cache(base_url: base_url) + end + (1..(timeout.to_i+1)).each do + sleep(1) + break if !feed_cache_content.nil? && !thread.alive? + end + feed_cache_content + end + end + rescue=> e + puts [e,e.backtrace] + nil + end + end + def generate_one_cache(base_url: nil) + base_url = Site.first.root_url if base_url.nil? + uid = self.uid + aff = self + if !aff.nil? + tags = aff.tag_ids + categories = aff.category_ids + if !(categories.empty? && tags.empty?) + archives_before_filter = ArchiveFile.can_display.local_data + if !tags.empty? + archives_before_filter = archives_before_filter.filter_by_tags(tags) + end + if !categories.empty? + archives_before_filter = archives_before_filter.filter_by_categories(categories,paginate=false) + end + archives_before_filter.selector = {"$and"=>[archives_before_filter.selector,{"$or"=>(I18n.available_locales.map{|v| {"title.#{v}"=>{"$nin"=>["", nil]}}})}]} + archives = archives_before_filter.sorted.to_a + else + archives = [] + end + end + all_archives = [] + tag_ids = [] + category_ids = [] + cat_ids = archives.collect{|a| a.category_id.blank? ? nil : a.category_id.to_s}.compact.uniq + tag_ids = archives.collect{|a| a.tag_ids.collect{|v| v.blank? ? nil : v.to_s}}.flatten.compact.uniq + tag_names = tag_ids.map{|tag_id| Tag.find(tag_id).name_translations rescue nil}.compact + category_titles = cat_ids.map{|cat_id| Category.find(cat_id).title_translations rescue nil}.compact + basic_query = {:module => 'archive',:enabled_for=>I18n.locale} + if !cat_ids.blank? + query = basic_query.merge({:categories.all => cat_ids}) + else + query = basic_query.clone + end + if !tag_ids.blank? + query = query.merge({:tags.all => tag_ids}) + end + page = Page.where(query).first || Page.where(basic_query).first + more_url = page ? page.get_url : nil + locales = Site.first.in_use_locales rescue I18n.available_locales + archives.each_with_index do |archive,i| + a = archive.get_data(more_url, base_url, cat_ids, tag_ids, locales) + all_archives << a + end + archives = { + "archives" => all_archives, + "tags" => tag_names, + "categories" => category_titles + }.to_json + ArchiveFileFeedCache.where(uid: uid).destroy + feed_cache = ArchiveFileFeedCache.create(uid: uid,content: archives) + archives + end +end \ No newline at end of file diff --git a/app/models/archive_file_feed_cache.rb b/app/models/archive_file_feed_cache.rb new file mode 100644 index 0000000..73a7c9d --- /dev/null +++ b/app/models/archive_file_feed_cache.rb @@ -0,0 +1,27 @@ +class ArchiveFileFeedCache + include Mongoid::Document + include Mongoid::Timestamps + + field :content, type: String, default: '' + field :uid + def self.regenerate_all + caches = self.all.to_a + time_now = Time.now + caches.each do |cache| + if cache.invalid_date && cache.invalid_date <= time_now + cache.destroy + else + cache.regenerate + end + end + uids = ArchiveFileFeed.all.pluck(:uid) - caches.collect(&:uid) + ArchiveFileFeed.where(:uid.in=> uids).each do |feed| + feed.generate_one_cache() + end + end + def regenerate + uid = self.uid + ArchiveFileFeed.where(uid: uid).each{|v| v.generate_one_cache} rescue nil + self.destroy + end +end \ No newline at end of file diff --git a/app/models/archive_sort_order.rb b/app/models/archive_sort_order.rb index 4e27dfe..6c06005 100644 --- a/app/models/archive_sort_order.rb +++ b/app/models/archive_sort_order.rb @@ -1,8 +1,10 @@ class ArchiveSortOrder include Mongoid::Document - field :sort_order, :type => Boolean # true => desc , false => asc + field :sort_order, :type => Boolean # true => asc , false => desc + field :need_update_sort, :type => Boolean, :default => true + field :max_sort_number, :type => Integer, :default => 0 after_initialize do |record| - if(!record.new_record? && record.sort_order.class != FalseClass && record.sort_order.class != TrueClass) + if(!record.new_record? && record.sort_order.nil?) record.sort_order = true record.save elsif(!record.new_record?) @@ -17,15 +19,31 @@ class ArchiveSortOrder end end after_save do |record| - @module_app = ModuleApp.where(:key=>'archive').first - if !@module_app.nil? - @sort_order = record.sort_order rescue nil - if !@asc.nil? - if !@module_app.attributes[:asc].nil? - @module_app.asc = @sort_order - @module_app.save + unless record.instance_variable_get(:@skip_callback) + if record.sort_order_changed? + @module_app = ModuleApp.where(:key=>'archive').first + if !@module_app.nil? + @sort_order = record.sort_order rescue nil + if !@asc.nil? + if !@module_app.attributes[:asc].nil? + @module_app.asc = @sort_order + @module_app.save + end + end + end + record.instance_variable_set(:@skip_callback, true) + record.update(:need_update_sort=>true) + if record.need_update_sort + Thread.new do + sort_order = record.sort_order ? 1 : -1 + ArchiveFile.recalc_sort_number(sort_order) + record.instance_variable_set(:@skip_callback, false) + end + end end end - end + end + def get_default_order + self.sort_order ? 0 : self.max_sort_number end end \ No newline at end of file diff --git a/app/views/admin/archive_files/_edit_feed_form.html.erb b/app/views/admin/archive_files/_edit_feed_form.html.erb new file mode 100644 index 0000000..859bbf1 --- /dev/null +++ b/app/views/admin/archive_files/_edit_feed_form.html.erb @@ -0,0 +1,61 @@ +<%= form_for @archive_feed, url: admin_archive_file_updatefeed_path(:id => @archive_feed.id), html: {class: "form-horizontal main-forms"} do |f| %> +
+ <% @site_in_use_locales.each do |locale| %> + <%= f.fields_for :title_translations do |f| %> +
+ +
+ <%= f.text_field locale, data: {"fv-validation" => "required;","fv-messages" => "Cannot be empty.;"}, value: (@archive_feed.title_translations[locale.to_s] rescue nil) %> +
+
+ <% end %> + <% end %> +
+
+

<%=t(:tags)%>

+
+
+ <% @module_app.tags.each do |tag| %> + + <% end %> +
+
+
+
+

<%=t(:category)%>

+
+
+ <% @module_app.categories.each do |category| %> + + <% end %> +
+
+
+
+<% end %> + + \ No newline at end of file diff --git a/app/views/admin/archive_files/_feed.html.erb b/app/views/admin/archive_files/_feed.html.erb new file mode 100644 index 0000000..530aaef --- /dev/null +++ b/app/views/admin/archive_files/_feed.html.erb @@ -0,0 +1,83 @@ + + + <%= feed.title %> +
+ +
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + + RSS Feed + + + JSON Feed + + \ No newline at end of file diff --git a/app/views/admin/archive_files/_feed_form.html.erb b/app/views/admin/archive_files/_feed_form.html.erb new file mode 100644 index 0000000..1e2ce69 --- /dev/null +++ b/app/views/admin/archive_files/_feed_form.html.erb @@ -0,0 +1,61 @@ +<%= form_for @archive_feed, url: admin_archive_file_createfeed_path, html: {class: "form-horizontal main-forms"} do |f| %> +
+ <% @site_in_use_locales.each do |locale| %> + <%= f.fields_for :title_translations do |f| %> +
+ +
+ <%= f.text_field locale, data: {"fv-validation" => "required;","fv-messages" => "Cannot be empty.;"}, value: (@archive_feed.title_translations[locale.to_s] rescue nil) %> +
+
+ <% end %> + <% end %> +
+
+

<%=t(:tags)%>

+
+
+ <% @module_app.tags.each do |tag| %> + + <% end %> +
+
+
+
+

<%=t(:category)%>

+
+
+ <% @module_app.categories.each do |category| %> + + <% end %> +
+
+
+
+<% end %> + + \ No newline at end of file diff --git a/app/views/admin/archive_files/_form.html.erb b/app/views/admin/archive_files/_form.html.erb index 1daf794..e7ce4e6 100644 --- a/app/views/admin/archive_files/_form.html.erb +++ b/app/views/admin/archive_files/_form.html.erb @@ -86,7 +86,8 @@
- <%= f.text_field :sort_number %> + <% extra_data = f.object.new_record? ? {placeholder: f.object.sort_number, title: f.object.sort_number, value: ''} : {} %> + <%= f.number_field :sort_number, extra_data %>
diff --git a/app/views/admin/archive_files/_index.html.erb b/app/views/admin/archive_files/_index.html.erb index 918fae2..38ea911 100644 --- a/app/views/admin/archive_files/_index.html.erb +++ b/app/views/admin/archive_files/_index.html.erb @@ -1,10 +1,96 @@ + <% @table_fields.each do |f| %> <% if f == "archive.downloaded_times" %> <%= thead(f,true,false) %> @@ -17,6 +103,8 @@ <% @archives.each do |archive| %> + <% is_editable = can_edit_or_delete?(archive) %> + diff --git a/modules/archive/archive_index11.html.erb b/modules/archive/archive_index11.html.erb index 1ba6aa7..b4efc6a 100644 --- a/modules/archive/archive_index11.html.erb +++ b/modules/archive/archive_index11.html.erb @@ -23,7 +23,7 @@ {{file-name}} diff --git a/modules/archive/archive_index12.html.erb b/modules/archive/archive_index12.html.erb index 7f1f618..a078919 100644 --- a/modules/archive/archive_index12.html.erb +++ b/modules/archive/archive_index12.html.erb @@ -25,8 +25,8 @@
- {{file-name}} - {{file-type}} + {{file-name}} + {{file-type}}
diff --git a/modules/archive/archive_index13.html.erb b/modules/archive/archive_index13.html.erb new file mode 100644 index 0000000..9339ea5 --- /dev/null +++ b/modules/archive/archive_index13.html.erb @@ -0,0 +1,41 @@ +
+

+ {{page-title}} +

+ +
+
+ + {{link_to_edit}} +
+
+
+ \ No newline at end of file diff --git a/modules/archive/archive_index2.html.erb b/modules/archive/archive_index2.html.erb index 70fe62e..7982a22 100644 --- a/modules/archive/archive_index2.html.erb +++ b/modules/archive/archive_index2.html.erb @@ -24,7 +24,7 @@
{{file-name}} - {{file-type}} + {{file-type}}
diff --git a/modules/archive/archive_index3.html.erb b/modules/archive/archive_index3.html.erb index ad5e1ad..21fb229 100644 --- a/modules/archive/archive_index3.html.erb +++ b/modules/archive/archive_index3.html.erb @@ -13,7 +13,7 @@
{{file-name}} - {{file-type}} + {{file-type}}
diff --git a/modules/archive/archive_index4.html.erb b/modules/archive/archive_index4.html.erb index 48ebcf4..a73366f 100644 --- a/modules/archive/archive_index4.html.erb +++ b/modules/archive/archive_index4.html.erb @@ -22,7 +22,7 @@
{{file-name}} - {{file-type}} + {{file-type}}
diff --git a/modules/archive/archive_index5.html.erb b/modules/archive/archive_index5.html.erb index 5263026..76ce40d 100644 --- a/modules/archive/archive_index5.html.erb +++ b/modules/archive/archive_index5.html.erb @@ -21,7 +21,7 @@
- {{file-type}} + {{file-type}}
diff --git a/modules/archive/archive_index6.html.erb b/modules/archive/archive_index6.html.erb index 2c46edf..b2b343d 100644 --- a/modules/archive/archive_index6.html.erb +++ b/modules/archive/archive_index6.html.erb @@ -26,7 +26,7 @@
diff --git a/modules/archive/archive_index7.html.erb b/modules/archive/archive_index7.html.erb index 097115b..4ccc941 100644 --- a/modules/archive/archive_index7.html.erb +++ b/modules/archive/archive_index7.html.erb @@ -23,7 +23,7 @@
{{description}}
- {{file-type}} + {{file-type}}
diff --git a/modules/archive/archive_index8.html.erb b/modules/archive/archive_index8.html.erb index 35b2e0b..48a14a3 100644 --- a/modules/archive/archive_index8.html.erb +++ b/modules/archive/archive_index8.html.erb @@ -28,7 +28,7 @@
{{description}}
diff --git a/modules/archive/archive_index9.html.erb b/modules/archive/archive_index9.html.erb index 06c96f2..b721385 100644 --- a/modules/archive/archive_index9.html.erb +++ b/modules/archive/archive_index9.html.erb @@ -18,7 +18,7 @@ {{file-name}} diff --git a/modules/archive/info.json b/modules/archive/info.json index 0971445..44cc7e7 100644 --- a/modules/archive/info.json +++ b/modules/archive/info.json @@ -95,7 +95,15 @@ "en" : "12. Accordion list - with drop down (widget-title, category, filename, download link)" }, "thumbnail" : "ar2.png" - } + }, + { + "filename": "archive_index13", + "name": { + "zh_tw": "13. 頁籤式 ( 模組標題, 類別標題, 標題, 檔案名稱, 下載連結, 描述 )", + "en": "13. Tab list (widget-title, category, title, filename, download link, description)" + }, + "thumbnail": "ar-tab1.png" + } ], "widgets" : [ { diff --git a/modules/archive/thumbs/ar-tab1.png b/modules/archive/thumbs/ar-tab1.png new file mode 100644 index 0000000000000000000000000000000000000000..2f887da80bc0707fe64e8d60ebdbaf9978bc857f GIT binary patch literal 40955 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 literal 0 HcmV?d00001
<% if is_editable %><% end %> <%= archive.status_for_table %> <%= archive.category.title rescue "" %> @@ -28,7 +116,7 @@ <%= archive.title %>
diff --git a/modules/archive/archive_index10.html.erb b/modules/archive/archive_index10.html.erb index c585b12..f56f01b 100644 --- a/modules/archive/archive_index10.html.erb +++ b/modules/archive/archive_index10.html.erb @@ -22,7 +22,7 @@
- {{file-type}} + {{file-type}}
- {{file-type}} + {{file-type}}
- {{file-type}} + {{file-type}}