MiraitSystems/enju_trunk

View on GitHub
app/controllers/manifestations_controller.rb

Summary

Maintainability
F
3 wks
Test Coverage
# -* encoding: utf-8 -*-
class ManifestationsController < ApplicationController
  authorize_function
  add_breadcrumb "I18n.t('breadcrumb.search_manifestations')", 'manifestations_path', :only => [:index] #, :unless => proc{params}
#  add_breadcrumb "I18n.t('breadcrumb.search_manifestations')", 'manifestations_path(params)', :only => [:index], :if => proc{params}
  add_breadcrumb "I18n.t('page.showing', :model => I18n.t('activerecord.models.manifestation'))", 'manifestation_path(params[:id])', :only => [:show]
  add_breadcrumb "I18n.t('page.new', :model => I18n.t('activerecord.models.manifestation'))", 'new_manifestation_path', :only => [:new, :create]
  add_breadcrumb "I18n.t('page.edit', :model => I18n.t('activerecord.models.manifestation'))", 'edit_manifestation_path(params[:id])', :only => [:edit, :update]

  load_and_authorize_resource :except => [:index, :show_nacsis, :create_from_nacsis, :output_show, :output_pdf, :search_manifestation, :numbering]
  authorize_resource :only => :index

  before_filter :authenticate_user!, :only => :edit
  before_filter :get_agent
  before_filter :get_series_statement, :only => [:index, :new, :edit, :output_excelx]
  before_filter :prepare_options, :only => [:new, :edit]
  before_filter :get_version, :only => [:show, :output_show, :output_pdf]
  after_filter :solr_commit, :only => [:create, :up, :outputdate, :destroy]
  after_filter :convert_charset, :only => :index

  helper_method :get_manifestation, :get_subject, :get_classification
  helper_method :get_libraries

  include EnjuOai::OaiController if defined?(EnjuOai)
  include EnjuSearchLog if defined?(EnjuSearchLog)
  include ApplicationHelper
  include ManifestationsHelper
  include FormInputUtils

  class Error < RuntimeError; end
  class InvalidSruOperationError < Error; end
  class UnknownFileTypeError < Error; end

  rescue_from InvalidSruOperationError do |ex|
    render :template => 'manifestations/explain', :layout => false
  end

  rescue_from UnknownFileTypeError do |ex|
    render_404_invalid_format
  end

  class NacsisCatSearch
    # sunspot_solrのSearchオブジェクトとの互換層

    include FormInputUtils

    def initialize(dbs = [:all])
      @orig_dbs = @dbs = dbs
      @db_opts = {}
      @dbs.each {|db| @db_opts[db] = {} }
      @cond = {}

      @results = nil
    end
    attr_accessor :results

    # 検索を実行する
    # 検索条件に問題があった場合にはnilを返す
    def execute
      return nil unless valid?

      if @dbs.blank?
        @results = {}

      else
        cond = @cond.merge(dbs: @dbs)
        @dbs.each do |db|
          next if @db_opts[db].blank?

          cond[:opts] ||= {}
          cond[:opts][db] ||= {}
          @db_opts[db].each_pair do |k, v|
            cond[:opts][db][k] = v
          end
        end
        @results = NacsisCat.search(cond)
      end
      self
    end

    def total; @results.total end
    def collation; nil end

    def setup_paginate!(db, page, per_page)
      @db_opts[db] ||= {}
      @db_opts[db][:page] = normalize_integer(page)
      @db_opts[db][:per_page] = normalize_integer(per_page)
    end

    def filter_by_record_type!(form_input)
      return if @dbs == [:all]
      return if form_input.blank? # DB指定がなければ生成時の指定に従って検索する

      db_param = [form_input].flatten
      db_names = db_param.map {|x| normalize_query_string(x).to_sym }

      @dbs = @dbs&db_names
    end

    def filter_by_ncid!(form_input)
      filter_by_one_word(:id, form_input)
    end
    def filter_by_isbn!(form_input)
      filter_by_one_word(:isbn, form_input)
    end
    def filter_by_issn!(form_input)
      filter_by_one_word(:issn, form_input)
    end
    def filter_by_ndc!(form_input)
      filter_by_one_word(:ndc, form_input)
    end
    def filter_by_edition_display_value!(form_input)
      filter_by_one_word(:edition_display_value, form_input)
    end
    def filter_by_volume_number_string!(form_input)
      filter_by_one_word(:volume_number_string, form_input)
    end
    def filter_by_issue_number_string!(form_input)
      filter_by_one_word(:issue_number_string, form_input)
    end
    def filter_by_serial_number_string!(form_input)
      filter_by_one_word(:serial_number_string, form_input)
    end

    def filter_by_query!(form_input, inverse = false)
      filter_by_some_words(:query, form_input, inverse)
    end
    def filter_by_title!(form_input, inverse = false)
      filter_by_some_words(:title, form_input, inverse)
    end
    def filter_by_creator!(form_input, inverse = false)
      filter_by_some_words(:author, form_input, inverse)
    end
    def filter_by_publisher!(form_input, inverse = false)
      filter_by_some_words(:publisher, form_input, inverse)
    end
    def filter_by_subject!(form_input, inverse = false)
      filter_by_some_words(:subject, form_input, inverse)
    end
    def filter_by_classification!(form_input, inverse = false)
      filter_by_some_words(:classification, form_input, inverse)
    end

    private

    def valid?
      true
    end

    def filter_by_one_word(name, form_input)
      query = each_query_word(form_input, false)
      return if query.blank?

      @cond[name] = query.map {|word| unquote_query_word(word) }.join(' ')
    end

    def filter_by_some_words(name, form_input, inverse)
      query = each_query_word(form_input, false)
      return if query.blank?

      words = query.map {|word| unquote_query_word(word) }
      if inverse
        @cond[:except] ||= {}
        @cond[:except][name] = words
      else
        @cond[name] = words
      end
    end
  end

  class SearchFactory
    def initialize(options, params)
      @options = options
      @params = params

      @logger = ::Rails.logger

      setup!
    end
    attr_reader :options, :params, :logger

    def facet_fields
      []
    end

    # 検索オブジェクトにfacetの設定を加える
    def setup_facet!(search)
    end

    # 検索オブジェクトにページネイトの設定を加える
    def setup_paginate!(search, page, per_page)
    end

    # 検索オブジェクトに「もしかして」の設定を加える
    def setup_collation!(search, form_input)
    end

    private

    def setup!
    end
  end

  class LocalSearchFactory < SearchFactory
    FACET_FIELDS = [
      :reservable, :carrier_type, :language, :library, :manifestation_type,
      :missing_issue, :in_process, :circulation_status_in_process,
      :circulation_status_in_factory, :no_item
    ]

    class Container
      def initialize(options, params)
        @options = options
        @params = params
        @search = {}
      end

      def [](key)
        @search[key]
      end

      def []=(key, value)
        @search[key] = value
      end

      def facet_fields
        FACET_FIELDS
      end

      def execute
        [:all, :book, :article, :serial].map do |key|
          @search[key].try(:execute)
        end
      end

      def setup_collation!(query)
        options = @options
        @search[:all].build do
          spellcheck :collate => 3, :q => query if options[:html_mode]
        end
      end

      def setup_facet!
        [:all, :book].each do |key|
          if @search[key]
            @search[key].build do
              facet_fields.each {|f| facet f }
            end
          end
        end
      end

      def setup_paginate!
        @search.each_pair do |key, s|
          if key == :article
            setup_paginate_internal!(s, @options[:page_article], @options[:per_page])
          elsif key == :serial
            setup_paginate_internal!(s, @options[:page_serial], @options[:per_page])
          elsif key == :session
            setup_paginate_internal!(s, @options[:page_session], @options[:per_page_session])
          else
            # :all or :book
            setup_paginate_internal!(s, @options[:page], @options[:per_page])
          end
        end
      end

      private

        def setup_paginate_internal!(search, page, per_page)
          if @options[:sru_mode]
            search.query.start_record(@params[:startRecord] || 1, @params[:maximumRecords] || 200)
          else
            search.build do
              paginate :page => page, :per_page => per_page
            end
          end
        end
    end

    def initialize(options, params, query, with_filter, without_filter, sort)
      @query = query
      @with_filter = with_filter
      @without_filter = without_filter
      @sort = sort

      super(options, params)
    end
    attr_reader :query, :sort

    def new_search
      container = Container.new(options, params)

      # 全種の書誌からの横断検索用
      container[:all] = new_search_internal(:all)

      # session[:manifestation_ids]更新のための検索用
      # FIXME?
      # session[:manifestation_ids]は検索結果の書誌情報を次々と見るのに使われている
      # (manifestations/index→manifestations/show→manifestations/show→...)。
      # よって文献とその他を分ける場合には、このデータも分けて取りまわす必要があるはず。
      container[:session] = new_search_internal(:all)

      if options[:split_by_type]
        # 一般書誌のみの検索用
        container[:book] = new_search_internal(:book)
        if options[:with_article]
          # 資料書誌のみの検索用
          container[:article] = new_search_internal(:article)
        end
        if options[:with_serial]
          # 雑誌書誌のみの検索用
          container[:serial] = new_search_internal(:serial)
        end
      end

      container
    end

    # 新しい検索オブジェクトを生成する。
    #  * manifestation_type - 検索対象とする書誌のタイプ(:all、:book、:article)を指定する。
    def new_search_internal(manifestation_type = :all)
      search = Sunspot.new_search(Manifestation)

      Manifestation.build_search_for_manifestations_list(search, @query, @with_filter, @without_filter)

      unless options[:add_mode]
        includes = [
          :carrier_type, :required_role, :items, :creators, :contributors,
          :publishers,
        ]
        includes << :bookmarks if defined?(EnjuBookmark)
        search.data_accessor_for(Manifestation).include = includes
      end

      search.build do
        if options[:oai_mode]
          order_by :updated_at, :desc
        else
          order_by sort[:sort_by], sort[:order]
          order_by :created_at, :desc
        end

        case manifestation_type
        when :book
          with(:is_article).equal_to false
        when :article
          with(:is_article).equal_to true
        else # :all
          # noop
        end
      end

      if options[:html_mode] && params[:missing_issue].nil?
        search.data_accessor_for(Manifestation).select = [
          :id, :original_title, :title_transcription, :required_role_id,
          :manifestation_type_id, :carrier_type_id, :access_address,
          :volume_number_string, :issue_number_string, :serial_number_string, :serial_number,
          :edition_display_value, :updated_at,
          :date_of_publication, :pub_date, :periodical_master,
          :carrier_type_id, :created_at, :note, :missing_issue, :article_title,
          :start_page, :end_page, :exinfo_1, :exinfo_6, :identifier
        ]
      end

      search
    end
    private :new_search_internal
  end

  class NacsisCatSearchFactory < SearchFactory
    class Container
      def initialize(options, search)
        @options = options
        @search = search
      end
      attr_reader :search

      def [](key)
        # 複数DBが指定された検索であっても
        # ゲートウェイへのアクセスは1回だけにする仕様であるため、
        # LocalSearchFactory::Containerとは異なって
        # 書誌の種別ごとの検索オブジェクトを持たない。
        # そのため、ここでは共通の@searchを常に返している。
        #
        # しかし、このままでは共通の@searchを介した
        # 複数回アクセスが発生してしまう可能性があるので
        # memoizeすななど何らかの対策が必要となる。
        # (ただし、2013-11-12時点では
        # 各種幅検索をともなう機能はローカル検索のみに
        # 対応しており、上述の問題が顕在化することはない。)
        @search
      end

      def facet_fields
        []
      end

      def execute
        results = @search.execute.results

        [:all, :book, :article, :serial].map do |key|
          NacsisCatSearch.new.tap do |x|
            if results.include?(key)
              x.results = results[key]
            else
              x.results = NacsisCat::ResultArray.new(nil)
            end
          end
        end
      end

      def setup_collation!(query)
        # noop
      end

      def setup_facet!
        # noop
      end

      def setup_paginate!
        @search.setup_paginate!(:all,     @options[:page],         @options[:per_page])
        @search.setup_paginate!(:book,    @options[:page],         @options[:per_page])
        @search.setup_paginate!(:article, @options[:page_article], @options[:per_page])
        @search.setup_paginate!(:serial,  @options[:page_serial],  @options[:per_page])
        @search.setup_paginate!(:session, @options[:page_session], @options[:per_page_session])
      end
    end

    def new_search
      dbs = []
      if @options[:nacsis_search_each]
        dbs << :book
        if @options[:with_article]
          dbs << :article
        end
        if @options[:with_serial]
          dbs << :serial
        end
      else
        dbs << :all
      end

      search = NacsisCatSearch.new(dbs)

      search.filter_by_record_type!(params[:manifestation_type])

      [
        :isbn, :issn, :ndc, :ncid,
        :edition_display_value, :volume_number_string, :issue_number_string, :serial_number_string,
      ].each do |name|
        search.__send__(:"filter_by_#{name}!", params[name])
      end

      [:query, :title, :creator, :publisher, :subject, :classification].each do |name|
        search.__send__(:"filter_by_#{name}!", params[name])
        search.__send__(:"filter_by_#{name}!", params[:"except_#{name}"], true)
      end

      Container.new(@options, search)
    end
  end

  # GET /manifestations
  # GET /manifestations.json
  def index
    set_reserve_user
    search_opts = make_index_plan # 検索動作の方針を抽出する

    @seconds = Benchmark.realtime do
      next if @oai && @oai[:need_not_to_search]
      do_oai_get_record_process(search_opts) and return
      do_direct_mode_process(search_opts) and return

      # indexアクションで使用する
      # 主要インスタンス変数の処期化

      @count = {}
      set_reservable
      get_manifestation
      get_subject
      get_classification
      set_in_process
      @index_agent = get_index_agent

      @sort_plan_id = search_opts[:sort_plan]
      @sort_plan = {}
      (1..10).each do |id|
        @sort_plan[t(Manifestation::SORT_PLANS[id]["sort"], :sort_by => t(Manifestation::SORT_PLANS[id]["sort_by"]))] = id
      end

      @per_page = search_opts[:per_page]
      @all_manifestations = params[:all_manifestations] if params[:all_manifestations]

      @libraries = Library.real.all
      @search_engines = Rails.cache.fetch('search_engine_all') { SearchEngine.all }
      @location_symbol_size = Keycode.where(:name => 'item.location_symbol')

      if params[:bookbinder_id]
        @binder = Item.find(params[:bookbinder_id]) rescue nil
        @all_manifestations = params[:all_manifestations] = true
      end

      if params[:removed_from].present? || params[:removed_to].present? || params[:removed]
        @removed = true
      end

      if defined?(EnjuTrunkTheme)
        if params[:theme_id]
          @theme = Theme.find(params[:theme_id]) rescue nil
          @all_manifestations = params[:all_manifestations] = false
        end
      end

      if params[:basket_id]
        @basket = @current_basket # ignore params[:basket_id] and get current_basket with current_user
        @all_manifestations = params[:all_manifestations] = true
      end

      @query = params[:query] # フォームで入力されたメインの検索語を保存する

      # 検索オブジェクトのfactoryを生成する
      #
      # NOTE:
      # 検索システムに合わせた検索条件の生成などはfactoryにおいて実装する。
      # ただしlocal検索については過去の経緯から特別扱いとなっており、
      # 検索条件生成コードのほとんどがコントローラに実装されている。
      if search_opts[:index] == :nacsis
        factory = NacsisCatSearchFactory.new(search_opts, params)

      else
        if search_opts[:sru_mode]
          sru = Sru.new(params)
          query = sru.cql.to_sunspot
          sort = sru.sort_by
        elsif search_opts[:openurl_mode]
          openurl = Openurl.new(params)
          query = openurl.query_text
          sort = search_result_order(params[:sort_by], params[:order])
        else
          if search_opts[:solr_query_mode]
            query = params[:solr_query]
          else
            query, highlight = make_query_string_and_hl_pattern
            @highlight = /(#{Regexp.union(highlight)})/
          end
          @solr_query = query # フォーム入力から生成したSolr検索式
          sort = search_result_order(params[:sort_plan])
        end
        logger.debug "  SOLR Query string:<#{@solr_query}>"

        with_filter, without_filter = make_query_filter(search_opts)
        factory = LocalSearchFactory.new(search_opts, params, @solr_query, with_filter, without_filter, sort)
      end
      # 検索オブジェクトの生成と検索の実行

      search = factory.new_search

      do_file_output_proccess(search_opts, search) and return

      search.setup_collation!(@query)
      search.setup_facet!
      search.setup_paginate! unless request.xhr?

      begin
        search_all_result, search_book_result,
          search_article_result, search_serial_result = search.execute
      rescue Exception => e
        flash[:message] = t('manifestation.invalid_query')
        logger.error "query error: #{e} (#{e.class})"
        e.backtrace.each {|bt| logger.debug "\t#{bt}" }
        return
      end
      update_search_sessions(search_opts, search)
      do_tag_cloud_process(search_opts) and return

      # 主にビューのためのインスタンス変数を設定する

      sum = 0
      @manifestations_all = []
      [
        ['', search_all_result, :page],
        ['_book', search_book_result, :page],
        ['_article', search_article_result, :page_article],
        ['_serial', search_serial_result, :page_serial],
      ].each do |ivsfx, sr, po|
        next unless sr

        @count[:"query_result#{ivsfx}"] = sr.total
        sum += sr.total

        ary = Kaminari.paginate_array(
          sr.results,
          :total_count => total_search_result_count(sr)
        ).page(search_opts[po]).per(search_opts[:per_page])
        @manifestations_all << ary

        instance_variable_set(:"@manifestations#{ivsfx}", ary)

        if search_opts[:index] == :nacsis && (ivsfx == '_book' || ivsfx == '_serial')
          session.delete("nacsis_cat#{ivsfx}_ids")
          ncid_ary = []
          ary.each do |nacsis_cat|
            ncid_ary << nacsis_cat.try(:ncid)
          end
          session["nacsis_cat#{ivsfx}_ids"] = ncid_ary unless ncid_ary.empty?
        end
      end
      session[:params] = params if search_opts[:index] == :nacsis

      @count[:query_result] = sum
      @collation = search_all_result.collation if @count[:query_result] == 0

      if current_user.nil? or !current_user.has_role?('Librarian')
        save_search_history(@solr_query, @manifestations.limit_value, @count[:query_result], current_user)
      end

      if @manifestations_all.blank?
        # 分割表示していない場合
        @manifestations_all << @manifestations
      end

      if search_opts[:html_mode]
        s = search_opts[:split_by_type] && !search_opts[:with_article] ? search_book_result : search_all_result
        search.facet_fields.each do |field|
          instance_variable_set(:"@#{field}_facet", s.facet(field).rows)
        end
      end

      # TODO: 検索結果が少ない場合にも表示させる
      #
      # NOTE:
      # 大本のコード(enju_trunkではないenju)をそのまま残したため
      # @solr_query(solr用の検索式)から推奨タグを導出しているが、
      # フォームで入力された検索語から導出したほうが適切ということはないか?
      if search_opts[:index] == :local &&
          search_all_result.results.blank? &&
          defined?(EnjuBookmark) &&
          @solr_query.respond_to?(:suggest_tags)
        @suggested_tag = @solr_query.suggest_tags.first
      end
    end
    store_location # before_filter ではファセット検索のURLを記憶してしまう

    respond_to do |format|
      if params[:opac]
        if @manifestations.size > 0
          format.html { render :template => 'opac/manifestations/index', :layout => 'opac' }
        else
          flash[:notice] = t('page.no_record_found')
          format.html { render :template => 'opac/search', :layout => 'opac' }
        end
      end
      format.html
      format.mobile
      format.xml  { render :xml => @manifestations }
      format.sru  { render :layout => false }
      format.rss  { render :layout => false }
      format.csv  { render :layout => false }
      format.rdf  { render :layout => false }
      format.atom
      format.oai {
        if @manifestations.blank?
          @oai[:errors] << 'noRecordsMatch'
        else
          from_and_until_times = set_from_and_until(Manifestation, params[:from], params[:until])
          from_time = from_and_until_times[:from] || Manifestation.last.updated_at
          until_time = from_and_until_times[:until] || Manifestation.first.updated_at
          set_resumption_token(params[:resumptionToken], from_time, until_time)
        end

        case params[:verb]
        when 'Identify'
          render :template => 'manifestations/identify'
        when 'ListMetadataFormats'
          render :template => 'manifestations/list_metadata_formats'
        when 'ListSets'
          @series_statements = SeriesStatement.select([:id, :original_title])
          render :template => 'manifestations/list_sets'
        when 'ListIdentifiers'
          render :template => 'manifestations/list_identifiers'
        when 'ListRecords'
          render :template => 'manifestations/list_records'
        end
      }
      format.mods
      format.json { render :json => @manifestations }
      format.js {
        case params[:verb]
        when 'Exchange'
          render 'exchange_manifestations/manifestations'
        else
          render 'binding_items/manifestations'
        end
      }
    end
  #rescue QueryError => e
  #  render :template => 'manifestations/error.xml', :layout => false
  #  Rails.logger.info "#{Time.zone.now}\t#{query}\t\t#{current_user.try(:username)}\t#{e}"
  #  return
  end

  def output_excelx
    index
  end

  # GET /manifestations/1
  # GET /manifestations/1.json
  def show
    @location_symbol_size = Keycode.where(:name => 'item.location_symbol')

    if params[:isbn].present?
      if @manifestation_redirect = Manifestation.find_by_isbn(params[:isbn])
        redirect_to @manifestation_redirect
        return
      else
        raise ActiveRecord::RecordNotFound if @manifestation.nil?
      end
    else
      if @version
        @manifestation = @manifestation.versions.find(@version).item if @version
      #else
      #  @manifestation = Manifestation.find(params[:id], :include => [:creators, :contributors, :publishers, :items])
      end
    end

    case params[:mode]
    when 'send_email'
      if user_signed_in?
        Notifier.delay.manifestation_info(current_user, @manifestation)
        flash[:notice] = t('page.sent_email')
        redirect_to @manifestation
        return
      else
        access_denied
      end
    end

    return if render_mode(params[:mode])

    if Setting.operation
      @reserved_count = Reserve.waiting.where(:manifestation_id => @manifestation.id, :checked_out_at => nil).count
      @reserve = current_user.reserves.where(:manifestation_id => @manifestation.id).last if user_signed_in?
    end

    store_location

    if @manifestation.attachment.path
      if Setting.uploaded_file.storage == :s3
        data = open(@manifestation.attachment.url) {|io| io.read }.force_encoding('UTF-8')
      else
        file = @manifestation.attachment.path
      end
    end

    if @manifestation.bookbinder
      @binder = @manifestation.items.where(:bookbinder => true).first rescue nil
    end

    if @manifestation.periodical_master?
      if params[:opac]
        redirect_to series_statement_manifestations_url(@manifestation.series_statement, :opac => true)
      elsif params[:all_manifestations]
        redirect_to series_statement_manifestations_url(@manifestation.series_statement, :all_manifestations => true)
      else
        redirect_to series_statement_manifestations_url(@manifestation.series_statement)
      end
      return
#    else
#      if @manifestation.series_statement && @manifestation.nacsis_identifier
#        redirect_to series_statement_manifestations_url(@manifestation.series_statement)
#        return
#      end
    end


    respond_to do |format|
      format.html { render :template => 'opac/manifestations/show', :layout => 'opac' } if params[:opac]
      format.html # show.html.erb
      format.mobile
      format.xml  {
        case params[:mode]
        when 'related'
          render :template => 'manifestations/related'
        else
          render :xml => @manifestation
        end
      }
      format.rdf
      format.oai
      format.mods
      format.json { render :json => @manifestation }
      #format.atom { render :template => 'manifestations/oai_ore' }
      #format.js
      format.download {
        if @manifestation.attachment.path
          if Setting.uploaded_file.storage == :s3
            send_data @manifestation.attachment.data, :filename => @manifestation.attachment_file_name.encode("cp932"), :type => 'application/octet-stream'
          else
            send_file file, :filename => @manifestation.attachment_file_name.encode("cp932"), :type => 'application/octet-stream'
          end
        else
          render :template => 'page/404', :status => 404
        end
      }
    end
  end

  # GET /manifestations/new
  # GET /manifestations/new.json
  def new
    @select_theme_tags = Manifestation.struct_theme_selects if defined?(EnjuTrunkTheme)
    original_manifestation = Manifestation.where(:id => params[:manifestation_id]).first
    if original_manifestation # GET /manifestations/new?manifestation_id=1
      @manifestation = original_manifestation.dup

      @classifications = get_classification_values(original_manifestation)

      @manifestation.isbn = nil if SystemConfiguration.get("manifestation.isbn_unique")
      @manifestation.series_statement = original_manifestation.series_statement unless @manifestation.series_statement
      @keep_themes = original_manifestation.themes.collect(&:id).flatten.join(',') if defined?(EnjuTrunkTheme)
      if original_manifestation.manifestation_exinfos.present?
        original_manifestation.manifestation_exinfos.map{ |m| eval("@manifestation.#{m.name} = '#{m.value}'") rescue nil }
      end
      if original_manifestation.manifestation_exinfos.present?
        original_manifestation.manifestation_exinfos.map{ |m| eval("@manifestation.#{m.name}_id = '#{m.value}'") rescue nil }
      end
      if original_manifestation.manifestation_extexts.present?
        original_manifestation.manifestation_extexts.map{ |m| eval("@manifestation.#{m.name} = '#{m.value}'") rescue nil }
      end

      @creators = original_manifestation.try(:creates).present? ? original_manifestation.creates.order(:position) : [{}]
      @contributors = original_manifestation.try(:realizes).present? ? original_manifestation.realizes.order(:position) : [{}]
      @publishers = original_manifestation.try(:produces).present? ? original_manifestation.produces.order(:position) : [{}]
      @subjects = original_manifestation.try(:subjects).present? ? original_manifestation.subjects.order(:position) : [{}]

      @work_has_titles = original_manifestation.try(:work_has_titles).present? ? original_manifestation.work_has_titles.inject([]){|wt,t| wt << t.dup} : []
      @work_has_titles << WorkHasTitle.new if @work_has_titles.blank?
      @work_has_languages = original_manifestation.try(:work_has_languages).present? ? original_manifestation.work_has_languages.inject([]){|wl,l| wl << l.dup} : []
      @work_has_languages << WorkHasLanguage.new if @work_has_languages.blank?

      if SystemConfiguration.get('manifestation.use_identifiers')
        @identifier_types = IdentifierType.find(:all, :select => "id, display_name, name", :order => "position") || []
        @identifiers = original_manifestation.try(:identifiers).present? ? original_manifestation.identifiers.inject([]){|ia,i| ia << i.dup} : []
        @identifiers << Identifier.new if @identifiers.blank?
      end

    elsif @series_statement # GET /series_statements/1/manifestations/new
      @manifestation = @series_statement.new_manifestation
      @work_has_languages = @manifestation.work_has_languages.present? ? @manifestation.work_has_languages : [{}]
      @classifications = get_classification_values(@manifestation)
    end

    @creators = @manifestation.try(:creates).present? ? @manifestation.creates : [{}] unless @creators
    @contributors = @manifestation.try(:realizes).present? ? @manifestation.realizes : [{}] unless @contributors
    @publishers = @manifestation.try(:produces).present? ? @manifestation.produces : [{}] unless @publishers
    @subjects = @manifestation.try(:subjects).present? ? @manifestation.subjects.order(:position) : [{}] unless @subjects

    @classifications = get_classification_values(@manifestation) if @classifications.blank?

    @manifestation.set_next_number(@manifestation.volume_number, @manifestation.issue_number) if params[:mode] == 'new_issue'

    @original_manifestation = original_manifestation if params[:mode] == 'add'

    if SystemConfiguration.get("manifestation.has_one_item") == true
      @manifestation.items.build
    end

    respond_to do |format|
      format.html # new.html.erb
      format.json { render :json => @manifestation }
    end
  end

  # GET /manifestations/1/edit
  def edit
    unless current_user.has_role?('Librarian')
      unless params[:mode] == 'tag_edit'
        access_denied
      end
    end
    @original_manifestation = Manifestation.where(:id => params[:manifestation_id]).first
    @manifestation.series_statement = @series_statement if @series_statement

    @creators = @manifestation.try(:creates).present? ? @manifestation.creates.order(:position) : [{}] unless @creators
    @contributors = @manifestation.try(:realizes).present? ? @manifestation.realizes.order(:position) : [{}] unless @contributors
    @publishers = @manifestation.try(:produces).present? ? @manifestation.produces.order(:position) : [{}] unless @publishers
    @subjects = @manifestation.try(:subjects).present? ? @manifestation.subjects.order(:position) : [{}] unless @subjects
    @classifications = get_classification_values(@manifestation)

    if defined?(EnjuBookmark)
      if params[:mode] == 'tag_edit'
        @bookmark = current_user.bookmarks.where(:manifestation_id => @manifestation.id).first if @manifestation rescue nil
        render :partial => 'manifestations/tag_edit', :locals => {:manifestation => @manifestation}
      end
      store_location unless params[:mode] == 'tag_edit'
    end
    if defined?(EnjuTrunkTheme)
      @select_theme_tags = Manifestation.struct_theme_selects
      @keep_themes = @manifestation.themes.collect(&:id).flatten.join(',')
    end

    if SystemConfiguration.get("manifestation.has_one_item") == true
      @manifestation.items.build if @manifestation.items.blank?
    end

  end

  # POST /manifestations
  # POST /manifestations.json
  def create
    @manifestation = Manifestation.new(params[:manifestation])
    @original_manifestation = Manifestation.where(:id => params[:manifestation_id]).first
    if @manifestation.respond_to?(:post_to_scribd)
      @manifestation.post_to_scribd = true if params[:manifestation][:post_to_scribd] == "1"
    end
    unless @manifestation.original_title?
      @manifestation.original_title = @manifestation.attachment_file_name
    end
    if params[:manifestation][:series_statement_id]
      series_statement = SeriesStatement.find(params[:manifestation][:series_statement_id])
      @manifestation.series_statement = series_statement if  series_statement
    end

    # agents
    @creators = params[:creators]
    @contributors = params[:contributors]
    @publishers = params[:publishers]

    # subjects
    @subjects = params[:subjects]
    @manifestation.subjects = create_subject_values(@subjects);

    # classifications
    @classifications = params[:classifications]
    @manifestation.classifications = create_classification_values(@classifications);

    @theme = params[:manifestation][:theme] if defined?(EnjuTrunkTheme)

    respond_to do |format|
      if @manifestation.save
        Manifestation.transaction do
          if @original_manifestation
            @manifestation.derived_manifestations << @original_manifestation
          end
          if @manifestation.series_statement and @manifestation.series_statement.periodical
            Manifestation.find(@manifestation.series_statement.root_manifestation_id).index
          end

          @manifestation.creates = create_creator_values(@creators)
          @manifestation.realizes = create_contributor_values(@contributors)
          @manifestation.produces = create_publisher_values(@publishers)

          @manifestation.themes = Theme.add_themes(@theme) unless @theme.blank? if defined?(EnjuTrunkTheme)
        end

        format.html { redirect_to @manifestation, :notice => t('controller.successfully_created', :model => t('activerecord.models.manifestation')) }
        format.json { render :json => @manifestation, :status => :created, :location => @manifestation }
      else
        prepare_options

        format.html { render :action => "new" }
        format.json { render :json => @manifestation.errors, :status => :unprocessable_entity }
        if defined?(EnjuTrunkTheme)
          @select_theme_tags = Manifestation.struct_theme_selects
          @keep_themes = @theme
        end
      end
    end
  end

  def numbering
    manifestation_identifier = params[:type].present? ? Numbering.do_numbering(params[:type]) : nil
    render :json => {:success => 1, :manifestation_identifier => manifestation_identifier}
  end

  # PUT /manifestations/1
  # PUT /manifestations/1.json
  def update
    if params[:manifestation][:identifiers_attributes]
      params[:manifestation][:identifiers_attributes].each do |key, identifier_attributes|
        if identifier_attributes[:body].blank?
          params[:manifestation][:identifiers_attributes]["#{key}"][:_destroy] = 1
        end
      end
    end

    # agents
    @creators = params[:creators]
    @contributors = params[:contributors]
    @publishers = params[:publishers]

    # subjects
    @subjects = params[:subjects]
    @manifestation.subjects = create_subject_values(@subjects);

    # classifications
    @classifications = params[:classifications]
    @manifestation.classifications = create_classification_values(@classifications);

    @theme = params[:manifestation][:theme] if defined?(EnjuTrunkTheme)

    # titles
    if SystemConfiguration.get('manifestation.use_titles')
      params[:manifestation][:work_has_titles_attributes].each_with_index do |wf_attributes|
        title = @manifestation.manifestation_titles.try(:[], wf_attributes[0].to_i) rescue nil
        if title
          title.title = wf_attributes[1]['title']
          title.title_transcription = wf_attributes[1][:title_transcription]
          title.title_alternative = wf_attributes[1][:title_alternative]
          @manifestation.work_has_titles[wf_attributes[0].to_i].title = title
        end
      end
    end

    respond_to do |format|
      if @manifestation.update_attributes(params[:manifestation])
        if @manifestation.series_statement and @manifestation.series_statement.periodical
          Manifestation.find(@manifestation.series_statement.root_manifestation_id).index
        end

        @manifestation.creates = create_creator_values(@creators)
        @manifestation.realizes = create_contributor_values(@contributors)
        @manifestation.produces = create_publisher_values(@publishers)

        if defined?(EnjuTrunkTheme)
          @manifestation.themes.destroy_all
          @manifestation.themes = Theme.add_themes(@theme)
        end

        if SystemConfiguration.get('manifestation.use_titles')
          @manifestation.manifestation_titles.each do |title|
            title.save if title.changed?
          end
        end

        format.html { redirect_to @manifestation, :notice => t('controller.successfully_updated', :model => t('activerecord.models.manifestation')) }
        format.json { head :no_content }
      else
        prepare_options

        format.html { render :action => "edit" }
        format.json { render :json => @manifestation.errors, :status => :unprocessable_entity }
        if defined?(EnjuTrunkTheme)
          @select_theme_tags = Manifestation.struct_theme_selects
          @keep_themes = @theme
        end
      end
    end
  end

  # DELETE /manifestations/1
  # DELETE /manifestations/1.json
  def destroy
    if SystemConfiguration.get("manifestation.has_one_item")
      @manifestation.items.each{ |item| item.mark_for_destruction }
    end
    @manifestation.destroy
    flash[:message] = t('controller.successfully_deleted', :model => t('activerecord.models.manifestation'))

    respond_to do |format|
      format.html { redirect_to manifestations_url }
      format.json { head :no_content }
    end
  end

  def output_show
    @manifestation = Manifestation.find(params[:id])
    data = Manifestation.get_manifestation_locate(@manifestation, current_user)
    send_data data.generate, :filename => Setting.manifestation_locate_print.filename
  end

  def output_pdf
    output_show
  end

  # GET /manifestations/nacsis/A001
  def show_nacsis
    case normalize_query_string(params[:manifestation_type])
    when 'book'
      db = :book
    when 'serial'
      db = :serial
    else
      # 想定されないDB
      raise ActiveRecord::RecordNotFound
    end

    search = NacsisCatSearch.new([db])
    search.filter_by_ncid!(params[:ncid])
    retval = search.execute
    raise ActiveRecord::RecordNotFound unless retval
    raise ActiveRecord::RecordNotFound unless retval.results[db].present?

    @nacsis_cat = retval.results[db].first

    db = @nacsis_cat.serial? ? :shold : :bhold
    search = NacsisCatSearch.new([db])
    search.filter_by_ncid!(params[:ncid])
    retval = search.execute
    @items = retval.try(:results).try(:[], db)

    respond_to do |format|
      format.html
    end
  end

  def search_manifestation
    unless request.xhr? or params[:original_title].blank?
      Rails.logger.info "not ajax request on search_manifestation"
      render text: "" # TODO
      return
    end
    manifestations = Manifestation.search.build do
      with(:original_title).equal_to params[:original_title]
      with(:periodical).equal_to false
    end.execute!.results
    manifestations = manifestations.delete_if{ |m| m.id == params[:manifestation_id].to_i } if params[:manifestation_id]
    unless manifestations.present?
      render :json => { success: 0, manifestation_urls: nil }
      return
    end
    manifestation_urls = []
    manifestations.each do |m|
      str = "#{t('activerecord.attributes.manifestation.identifier')}:"
      str += m.identifier.to_s
      manifestation_urls << ApplicationController.helpers.link_to(str, manifestation_path(m))
    end
    render :json => { success: 1, manifestation_urls: manifestation_urls }
  end

  # POST /manifestations/create_from_nacsis?ncid=<NCID>&manifestation_type=book
  def create_from_nacsis
    ncid = params['ncid']
    type = params['manifestation_type'] || 'book'

    if type == 'book'
      created_record = Manifestation.where(:nacsis_identifier => ncid).first
      created_record = NacsisCat.create_manifestation_from_ncid(ncid) if created_record.nil?
    else
      created_record = SeriesStatement.where(:nacsis_series_statementid => ncid).first
      created_record = NacsisCat.create_series_statement_from_ncid(ncid) if created_record.nil?
    end

    respond_to do |format|
      format.html do
        if created_record.nil?
          redirect_to nacsis_manifestations_path(ncid: ncid, manifestation_type: type)
        elsif created_record.new_record?
          redirect_to created_record,
            notice: t('controller.successfully_created', :model => t('activerecord.models.manifestation'))
        else
          redirect_to created_record
        end
      end
    end
  end

  def upload_to_nacsis
    result = NacsisCat.upload_info_to_nacsis(params[:work_id], params[:db_type], params[:command])

    case params[:db_type]
    when 'BOOK'
      if params[:series_id]
        redirect_url = series_statement_manifestations_url(params[:series_id], :all_manifestations => true)
      else
        redirect_url = manifestation_url(params[:work_id])
      end
      model_str = t('page.nacsis_book')
    when 'SERIAL'
      redirect_url = series_statement_manifestations_url(params[:work_id], :all_manifestations => true)
      model_str = t('page.nacsis_serial')
    end

    if result[:return_code] == '200'
      model_str = t('external_catalog.nacsis') + model_str + t('resource_import_textfile.book')
      case params[:command]
      when 'insert'
        flash[:notice] = t('controller.successfully_created', :model => model_str)
      when 'update'
        flash[:notice] = t('controller.successfully_updated', :model => model_str)
      end
      flash[:notice] += " #{t('activerecord.attributes.nacsis_user_request.ncid')} : #{result[:result_id]}"
    else
      flash[:notice] = "#{t('resource_import_nacsisfiles.upload_failed')} CODE = #{result[:return_code]} (#{result[:return_phrase]})"
    end

    respond_to do |format|
      format.html { redirect_to redirect_url }
    end
  end

  private

  # solrに送信するqパラメータ文字列を構成する
  # TODO: integerやstringもqfに含める
  # TODO: このメソッドをfactoryに移動する
  def make_query_string_and_hl_pattern
    qwords = []
    highlight = []

    #
    # basic search
    #

    string_fields_for_query = [ # fulltext検索に対応するstring型フィールドのリスト
      :title, :contributor,
      :exinfo_1, :exinfo_6,
      :subject, :isbn, :issn, :identifier,
      # 登録内容がroot_of_series?==trueの場合に相違
      :creator, :publisher,
      # 対応するstring型インデックスがない
      # :fulltext,
      # :article_title, :series_title,
      # :note, :description,
      # :aulast, :aufirst
      # :atitle, :btitle, :jtitle,
      # :extext_1, :extext_2, :extext_3, :extext_4, :extext_5,
    ]

    query = params[:query].to_s
    query = '' if query == '[* TO *]'

    if query.present?
      qws = []
      if params[:query_merge] == 'startwith'
        qws << adhoc_text_field_query(
          "#{escape_query_string(query, true)}*",
          Manifestation, string_fields_for_query)
      else
        each_query_word(normalize_query_string(query)) do |qw|
          highlight << /#{highlight_pattern(qw)}/
          qws << generate_adhoc_string_query_text(qw, Manifestation, string_fields_for_query) do |t|
            qw.size == 1 ? "#{t}*" : nil
          end
        end
      end

      if qws.size == 1
        qwords << qws
      elsif params[:query_merge] == 'all' || params[:query_merge] != 'any' && SystemConfiguration.get("search.use_and")
        qwords << qws.join(' AND ')
      else
        qwords << '(' + qws.join(' OR ') + ')'
      end
    end

    # recent manifestations
    qwords << "created_at_d:[NOW-1MONTH TO NOW] AND except_recent_b:false" if params[:mode] == 'recent'

    #
    # advanced search
    #

    # exact match / start-with match
    special_match = []
    [:title, :creator].each do |key|
      value = params[key]
      merge_type = params[:"#{key}_merge"]
      next unless value.present? && (merge_type == 'exact' || merge_type == 'startwith')
      special_match << key

      case key
      when :title
        field = 'title_sm'
      when :creator
        field = 'creator_sm'
        value = value.gsub(/\s/, '') # インデックス登録時の値に合わせて空白を除去しておく
      end
      highlight << /\A#{highlight_pattern(value)}\z/

      if merge_type == 'exact'
        qw = %Q["#{escape_query_string(value)}"]
      else
        qw = %Q[#{escape_query_string(value, true)}*]
      end
      qwords << %Q[#{field}:#{qw}]
    end

    # classification
    if SystemConfiguration.get("manifestation.search.use_classification_type") &&
       SystemConfiguration.get("manifestation.search.use_select2_for_classification")
      # 選択式(SELECT2)のコード
      cls_params = {}
      (params[:classifications] || []).each do |cls_hash|
        cls_type_id = cls_hash['classification_type_id']
        cls_id = cls_hash['classification_id']
        next if cls_type_id.blank? || cls_id.blank?
        cls_params[cls_type_id] ||= []
        cls_params[cls_type_id] << cls_id
      end

      cls_params.each do |cls_type_id, cls_ids|
        cls_word = {}
        Classification.where(id: cls_ids).
          where(classification_type_id: cls_type_id).each do |cls|
            # Manifestationのsearchableブロックに合わせる
            cls_word[cls.id.to_s] = "#{cls.classification_type.name}-#{cls.classification_identifier}"
          end
        qws = cls_ids.map {|cls_id| cls_word[cls_id] || 'unknown' }
        if qws.size == 1
          qwords << "classification_sm:#{qws.first}"
        else
          qwords << "classification_sm:(#{qws.join(' OR ')})"
        end
      end
    else
      # text入力版のコード
      cls_params = {}
      (params[:classifications] || []).each do |cls_hash|
        cls_type_id = cls_hash['classification_type_id']
        cls_type_id = 0 unless SystemConfiguration.get("manifestation.search.use_classification_type")
        cls_identifier = cls_hash['classification_identifier']
        next if cls_type_id.blank? || cls_identifier.blank?
        cls_params[cls_type_id] ||= []
        cls_params[cls_type_id] << cls_identifier
      end

      cls_params.each do |cls_type_id, cls_identifiers|
        cls_word = []
        if SystemConfiguration.get("manifestation.search.use_classification_type")
          cls_type_name = ClassificationType.where(id: cls_type_id).pluck(:name)
          # Manifestationのsearchableブロック>に合わせる
          cls_identifiers.each do |cls_identifier|
            cls_word << "#{cls_type_name.first}-#{cls_identifier}*"
          end
        else
          cls_identifiers.each do |cls_identifier|
            cls_word << "*-#{cls_identifier}*"
          end
        end
        if cls_word.size == 1
          qwords << "classification_sm:#{cls_word.first}"
        else
          qwords << "classification_sm:(#{cls_word.join(' OR ')})"
        end
      end
    end

    # other_identifier
    if params[:other_identifier]
      identifier_type = IdentifierType.find(params[:other_identifier][:identifier_type_id]) rescue nil
      if identifier_type
        unless params[:other_identifier][:identifier].blank?
          if Manifestation::ISBN_IDENTIFIER_TYPE_IDS.include?(identifier_type.id)
            param_string = Lisbn.new(params[:other_identifier][:identifier]).isbn13 || params[:other_identifier][:identifier]
          else
            param_string = params[:other_identifier][:identifier]
          end
          qwords << "other_identifier_sm:#{identifier_type.name}-#{param_string}"
        end
      end
    end
    [
      [:tag, 'tag_sm'],
      [:title, 'title_text', 'title_sm'],
      [:creator, 'creator_text', 'creator_sm'],
      [:contributor, 'contributor_text', 'contributor_sm'],
      [:isbn, 'isbn_sm'],
      [:issn, 'issn_sm'],
      [:ndc, 'ndc_sm'],
      [:edition_display_value, 'edition_display_value_sm'],
      [:volume_number_string, 'volume_number_string_sm'],
      [:issue_number_string, 'issue_number_string_sm'],
      [:serial_number_string, 'serial_number_string_sm'],
      [:lccn, 'lccn_s'],
      [:nbn, 'nbn_s'],
      [:publisher, 'publisher_text', 'publisher_sm'],
      [:item_identifier, 'item_identifier_sm'],
      [:call_number, 'call_number_sm'],
      [:except_query, nil],
      [:except_title, 'title_text', 'title_sm'],
      [:except_creator, 'creator_text', 'creator_sm'],
      [:except_publisher, 'publisher_text', 'publisher_sm'],
      [:identifier, 'identifier_sm'],
      [:subject_text, 'subject_text', 'subject_sm'],
    ].each do |key, field, onechar_field|
      next if special_match.include?(key)

      value = params[key]
      next if value.blank?

      qcs = []
      qws = []
      hls = []

      merge_type = params[:"#{key}_merge"]
      flg = /\Aexcept_/ =~ key.to_s ? '-' : ''
      each_query_word(value) do |word|
        hls << word if flg.blank?
        if word.size == 1 && onechar_field
          # 1文字だけの検索語を部分一致とみなす
          qcs << "#{flg}*#{word}*"
        else
          qws << "#{flg}#{word}"
        end
      end

      qw = []
      [
        [qcs, onechar_field || field],
        [qws, field],
      ].each do |q, f|
        next if q.blank?

        tag = f ? "#{f}:" : ''
        if q.size == 1
          qw << "#{tag}(#{q.first})"
        elsif merge_type == 'any'
          qw << "#{tag}(#{q.join(' OR ')})"
        else
          qw << "#{tag}(#{q.join(' AND ')})"
        end
      end
      qwords.push qw.join(' AND ')

      if (key == :title || key == :creator) &&
          flg != '-' && hls.present?
        highlight.concat hls.map {|t| /#{highlight_pattern(t)}/ }
      end
    end

    # range
    [
      [:number_of_pages_at_least, :number_of_pages_at_most,
        :num_range_query, 'number_of_pages'],
      [:pub_date_from, :pub_date_to,
        :date_range_query, 'pub_date'],
      [:acquired_from, :acquired_to,
        :date_range_query, 'acquired_at'],
      [:removed_from, :removed_to,
        :date_range_query, 'removed_at'],
    ].each do |p1, p2, conv_method, field_base|
      next unless params[p1] || params[p2]
      q = __send__(conv_method, field_base, params[p1], params[p2])
      qwords << q if q
    end

    # 詳細検索からの資料区分 ファセット選択時は無効とする
    if params[:manifestation_types].present? && params[:manifestation_type].blank?
      types_ary = []
      manifestation_types = params[:manifestation_types]
      if manifestation_types.class == ActiveSupport::HashWithIndifferentAccess
        manifestation_types.each_key do |key|
          manifestation_type = ManifestationType.find(key) rescue nil
          types_ary << manifestation_type.name if manifestation_type.present?
        end
      else
        manifestation_types.each do |key|
          manifestation_type = ManifestationType.find(key) rescue nil
          types_ary << manifestation_type.name if manifestation_type.present?
        end
      end
      qwords << "manifestation_type_sm:(" + types_ary.join(" OR ") + ")" if types_ary.present?
    end

    # merge basic and advanced
    op = SystemConfiguration.get("advanced_search.use_and") ? 'AND' : 'OR'
    [qwords.join(" #{op} "), highlight]
  end

  def highlight_pattern(str)
    str = $2 if /\A(['"])(.*)\1\z/ =~ str
    str.split(/\s+/).map {|s| Regexp.quote(s) }.join('(?>\\s+)')
  end

  # solr searchのためのfilter指定を構成する
  # TODO: このメソッドをfactoryに移動する
  def make_query_filter(options)
    with = []
    without = []

    #
    # params['mode']に関係なく設定するフィルタ
    #

    with << [
      :required_role_id, :less_than_or_equal_to,
      (current_user.try(:role) || Role.default_role).id
    ]

    # hide manifestaion without available item for Guest and User
    if SystemConfiguration.get('manifestation.search.hide_without_accessible_item')
      unless ((SystemConfiguration.get('manifestation.show_all') &&
        current_user.try(:role).try(:id).try(:>=, Role.where(:name => 'Librarian').first.id)) || @all_manifestations)
        current_role_id = (current_user.try(:role) || Role.default_role).id
        with << [:has_available_items, :equal_to, "#{current_role_id}_has_available_items"]
      end
    end

    if @removed
      with << [:has_removed, :equal_to, true]
    else
      if !params[:missing_issue] &&
        (@all_manifestations.blank? or !@all_manifestations == true) &&
        !SystemConfiguration.get("manifestation.show_all")
      end
    end
    without << [:hide, :equal_to, true]

    without << [:id, :equal_to, @binder.manifestation.id] if @binder

    unless params[:with_periodical_item]
      unless @binder
        with << [:periodical, :equal_to, false] if options[:add_mode].blank? and @series_statement.blank? and @basket.blank?
      end
    end
    with << [:periodical_master, :equal_to, false] if options[:add_mode]
    return [with, without] if options[:add_mode]

    #
    # params['mode']が'add'でないときだけ設定するフィルタ
    #
    with << [:reservable, :equal_to, @reservable] unless @reservable.nil?
    with << [:periodical_master, :equal_to, false] if @series_statement or @basket
    with << [:carrier_type, :equal_to, params[:carrier_type]] if params[:carrier_type]
    with << [:missing_issue, :equal_to, params[:missing_issue]] if params[:missing_issue]
    with << [:in_process, :equal_to, @in_process] unless @in_process.nil?
    with << [:manifestation_type, :equal_to, params[:manifestation_type]] if params[:manifestation_type]
    with << [:circulation_status_in_process, :equal_to, params[:circulation_status_in_process]] if params[:circulation_status_in_process]
    with << [:circulation_status_in_factory, :equal_to, params[:circulation_status_in_factory]] if params[:circulation_status_in_factory]
    [
      [:publisher_ids, @agent],
      [:creator_ids, @index_agent[:creator]],
      [:contributor_ids, @index_agent[:contributor]],
      [:publisher_ids, @index_agent[:publisher]],
      [:original_manifestation_ids, @manifestation],
      [:subject_ids, @subject],
      [:classification_ids, @classification],
      [:bookbinder_id, @binder],
      [:series_statement_id, @series_statement],
    ].each do |field, record|
      with << [field, :equal_to, record.id] if record
    end

    if params[:subject]
      subject = Subject.where(term: params[:subject]).first
      with << [:subject, :equal_to, subject.try(:term)]
    end

    unless params[:library].blank?
      params[:library].split.uniq.each do |library|
        with << [:library, :equal_to, library]
      end
    end

    unless params[:language].blank?
      params[:language].compact.uniq.each do |language|
        with << [:language, :equal_to, language]
      end
    end

    if defined?(EnjuTrunkTheme)
      if @theme
        with << [:id, :any_of, @theme.manifestations.collect(&:id)]
      end
    end

    if @basket
      with << [:id, :any_of, @basket.manifestations.collect(&:id)]
    end
    [with, without]
  end

  def search_result_order(sort_id)
    sort = {}
    sort_id = SystemConfiguration.get("manifestation.search_sort") || 1 if sort_id.nil?
    sort_id = sort_id.to_i
    # TODO: ページ数や大きさでの並べ替え
    case sort_id
    when 1, 2
      sort[:sort_by] = 'date_of_publication'
    when 3, 4
      sort[:sort_by] = 'created_at'
    when 5, 6
      sort[:sort_by] = 'original_title'
    when 7, 8
      sort[:sort_by] = 'author'
    when 9, 10
      sort[:sort_by] = 'carrier_type'
    end

    order_id = sort_id % 2
    if order_id == 1
      sort[:order] = 'desc'
    elsif order_id == 0
      sort[:order] = 'asc'
    end

    sort
  end

  def render_mode(mode)
    case mode
    when 'barcode'
      barcode = Barby::QrCode.new(@manifestation.id)
      send_data(barcode.to_svg, :disposition => 'inline', :type => 'image/svg+xml')
    when 'holding'
      render :partial => 'manifestations/show_holding', :locals => {:manifestation => @manifestation}
    when 'tag_edit'
      if defined?(EnjuBookmark)
        render :partial => 'manifestations/tag_edit', :locals => {:manifestation => @manifestation}
      end
    when 'tag_list'
      if defined?(EnjuBookmark)
        render :partial => 'manifestations/tag_list', :locals => {:manifestation => @manifestation}
      end
    when 'show_index'
      render :partial => 'manifestations/show_index', :locals => {:manifestation => @manifestation}
    when 'show_creators'
      render :partial => 'manifestations/show_creators', :locals => {:manifestation => @manifestation}
    when 'show_all_creators'
      render :partial => 'manifestations/show_creators', :locals => {:manifestation => @manifestation}
    when 'pickup'
      render :partial => 'manifestations/pickup', :locals => {:manifestation => @manifestation}
    when 'calil_list'
      render :partial => 'manifestations/calil_list', :locals => {:manifestation => @manifestation}
    else
      false
    end
  end

  def prepare_options
    @subject_types = SubjectType.all
    @carrier_types = CarrierType.all
    @sub_carrier_types = SubCarrierType.all
    @manifestation_types = ManifestationType.all
    @roles = Role.all
    @languages = Language.all_cache
    @language_types = LanguageType.all
    @countries = Country.all
    @frequencies = Frequency.all
    @nii_types = NiiType.all if defined?(NiiType)
    @create_types = CreateType.where(display: true).select("id, name, display_name")
    @realize_types = RealizeType.where(display: true).select("id, name, display_name")
    @produce_types = ProduceType.where(display: true).select ("id, name, display_name")
    @default_language = Language.where(:iso_639_1 => @locale).first
    @title_types = TitleType.find(:all, :select => "id, display_name, name", :order => "position")
    @work_has_titles = @manifestation.work_has_titles
    @work_has_titles << WorkHasTitle.new if @work_has_titles.blank?
    @work_has_languages = @manifestation.work_has_languages
    @work_has_languages << WorkHasLanguage.new if @work_has_languages.blank?
    @numberings = Numbering.get_manifestation_numbering
    if SystemConfiguration.get('manifestation.use_identifiers')
      @identifier_types = IdentifierType.find(:all, :select => "id, display_name, name", :order => "position") || []
      @identifiers = @manifestation.identifiers
      @identifiers << Identifier.new if @identifiers.blank?
    end
    @use_licenses = UseLicense.all if defined? EnjuTrunkOrder
    @classification_types = ClassificationType.order("position").all

    # 書誌と所蔵を1:1で管理 編集のためのデータを準備する
    if SystemConfiguration.get("manifestation.has_one_item") == true
      @libraries = Library.real
      @libraries.delete_if {|l| l.shelves.empty?}
      if @manifestation.items.present?
        @item = SystemConfiguration.get('manifestation.manage_item_rank') ? @manifestation.items.sort_rank.first : @manifestation.items.first
        @library = @item.shelf.library rescue nil
      else
        @item = Item.new
        @library = Library.real.first(:order => :position, :include => :shelves)
      end
      @shelf_categories = Shelf.try(:categories) rescue nil
      if @shelf_categories
        @shelves = []
        @shelves << @item.shelf if @item
      else
        @shelves = @library.shelves
      end
      @checkout_types = CheckoutType.all
      @accept_types = AcceptType.all
      @circulation_statuses = CirculationStatus.all
      @circulation_statuses.reject!{|cs| cs.name == "Removed"}
      @retention_periods = RetentionPeriod.all
      @bookstores = Bookstore.all
      @use_restrictions = UseRestriction.available
      @roles = Role.all
      @claim_types = ClaimType.all
      @item_numberings = []
      Numbering.where(:numbering_type => 'item').each do |numbering|
        @item_numberings << Manifestation::SELECT2_OBJ.new(numbering.name, numbering.name, numbering.display_name)
      end
      @use_restriction_id = @item.use_restriction.present? ? @item.use_restriction.id : nil
      if SystemConfiguration.get('manifestation.use_item_has_operator')
        @countoperators = ItemHasOperator.count(:conditions => ["item_id = ?", @item])
        if @item.item_has_operators.blank?
          @item.item_has_operators << ItemHasOperator.new(:operated_at => Date.today.to_date, :library_id => @library)
        end
      end
    end

  end

  def save_search_history(query, offset = 0, total = 0, user = nil)
    check_dsbl if LibraryGroup.site_config.use_dsbl
    if SystemConfiguration.get("write_search_log_to_file")
      write_search_log(query, total, user)
    else
      SearchHistory.create(:query => query, :user => user, :start_record => offset + 1, :maximum_records => nil, :number_of_records => total)
    end
  end

  def write_search_log(query, total, user)
    SEARCH_LOGGER.info "#{Time.zone.now}\t#{query}\t#{total}\t#{user.try(:username)}\t#{params[:format]}"
  end

  def set_reserve_user
    if current_user.try(:has_role?, 'Librarian') && params[:user_id]
      @reserve_user = User.find(params[:user_id]) rescue current_user
    else
      @reserve_user = current_user
    end
  end

  def set_reservable
    case params[:reservable].to_s
    when 'true'
      @reservable = true
    when 'false'
      @reservable = false
    else
      @reservable = nil
    end
  end

  def set_in_process
    case params[:in_process].to_s
    when 'true'
      @in_process = true
    when 'false'
      @in_process = false
    else
      @in_process = nil
    end
  end

  # "*_sm"フィールドに対する自然数の範囲条件を指定する
  # solrクエリー文字列を返す
  def num_range_query(field_base, num_from, num_to)
    n1 = n2 = nil
    num_regex = /\A0*[1-9]\d*/

    n1 = $&.to_i if num_regex =~ num_from
    n2 = $&.to_i if num_regex =~ num_to

    n1, n2 = n2, n1 if n1 && n2 && n1 > n2

    block = proc {|n| n < 1 }
    r_begin = (n1.nil? || block.call(n1)) ? '*' : n1.to_s
    r_end   = (n2.nil? || block.call(n2)) ? '*' : n2.to_s

    return nil if r_begin == '*' && r_end == '*'
    "#{field_base}_sm:[#{r_begin} TO #{r_end}]"
  end

  # "*_sm"フィールドに対する日時範囲条件を指定する
  # solrクエリー文字列を返す。
  def date_range_query(field_base, date_from, date_to)
    r_begin, r_end = construct_time_range(date_from, date_to)

    return nil if r_begin.blank? && r_end.blank?
    "#{field_base}_sm:[#{r_begin.try(:iso8601) || '*'} TO #{r_end.try(:iso8601) || '*'}]"
  end

  def next_page_number_for_oai_search
    return 1 unless params[:resumptionToken]

    current_token = get_resumption_token(params[:resumptionToken])
    unless current_token
      @oai[:errors] << 'badResumptionToken'
      return 1
    end

    per_page = 200 # OAI-PMHのデフォルトの件数
    (current_token[:cursor].to_i + per_page).div(per_page) + 1
  end

  def total_search_result_count(result)
    max_count = SystemConfiguration.get("max_number_of_results")
    total = result.total
    total > max_count ? max_count : total
  end

  # indexアクションのおおまかな動作を決める
  # いくつかのパラメータの検査と整理を行う。
  def make_index_plan
    search_opts = {
      :index => :local,
    }

    if params[:mode] == 'add'
      search_opts[:add_mode] = true
      access_denied unless current_user.has_role?('Librarian')
      @add = true
    end
    if params[:format] == 'csv'
      search_opts[:csv_mode] = true

    elsif params[:format] == 'oai'
      search_opts[:oai_mode] = true
      @oai = check_oai_params(params)

    elsif params[:format] == 'sru'
      search_opts[:sru_mode] = true
      raise InvalidSruOperationError unless params[:operation] == 'searchRetrieve'

    elsif params[:api] == 'openurl'
      search_opts[:openurl_mode] = true

    elsif defined?(EnjuBookmark) && params[:view] == 'tag_cloud'
      search_opts[:tag_cloud_mode] = true
    elsif params[:output]
      search_opts[:output_mode] = true
      search_opts[:output_type] =
        case params[:format_type]
        when 'excelx'  then :excelx
        when 'tsv'     then :tsv
        when 'pdf'     then :pdf
        when 'request' then :request
        when 'label'   then :label
        end
      raise UnknownFileTypeError unless search_opts[:output_type]
      search_opts[:output_cols] = params[:cols]

    elsif params[:format].blank? || params[:format] == 'html'
      search_opts[:html_mode] = true
      if params[:index] == 'nacsis'
        # NOTE: 検索ソースをlocal以外にできるのはformatがhtmlのときだけの限定。
        search_opts[:index] = :nacsis
      end
      if search_opts[:index] == :local &&
          params[:solr_query].present?
        search_opts[:solr_query_mode] = true
      end

      if params[:item_identifier].present? &&
            params[:item_identifier] !~ /\*/ ||
          (SystemConfiguration.get('manifestation.isbn_unique') &&
            params[:isbn].present? && params[:isbn] !~ /\*/) ||
              (params[:identifier].present? && params[:identifier] !~ /\*/)
        search_opts[:direct_mode] = true unless params[:binding_items_flg]
      end

      # split option (local)
      search_opts[:split_by_type] = SystemConfiguration.get('manifestations.split_by_type')
      if search_opts[:split_by_type] && search_opts[:index] == :local
        if params[:without_article]
          search_opts[:with_article] = false
        else
          search_opts[:with_article] = !SystemConfiguration.isWebOPAC || clinet_is_special_ip?
        end
      end

      # split option (nacsis)
      search_opts[:nacsis_search_each] = SystemConfiguration.get('nacsis.search_each')
      if search_opts[:nacsis_search_each] && search_opts[:index] == :nacsis
        search_opts[:with_serial] = true
      end
    end

    # prepare: per_page
    if search_opts[:csv_mode]
      per_page = 65534
    elsif per_pages
      per_page = per_pages[0]
    end
    per_page = cookies[:per_page] if cookies[:per_page] # XXX: セッションデータに格納してはダメ?
    per_page = params[:per_page] if params[:per_page]#Manifestation.per_page

    cookies.permanent[:per_page] = { :value => per_page } # XXX: セッションデータに格納してはダメ?
    search_opts[:per_page] = per_page

    # prepare: page
    if search_opts[:oai_mode]
      search_opts[:page] = next_page_number_for_oai_search
    else
      search_opts[:page] = params[:page].try(:to_i) || 1
      search_opts[:page_article] = params[:page_article].try(:to_i) || 1
      search_opts[:page_serial] = params[:page_serial].try(:to_i) || 1
    end
    search_opts[:page_session] = 1
    search_opts[:per_page_session] = SystemConfiguration.get("max_number_of_results")

    # prepare: sort
    sort_plan = SystemConfiguration.get("manifestation.search_sort") || 1
    sort_plan = cookies[:sort_plan] if cookies[:sort_plan] # XXX: セッションデータに格納してはダメ?
    sort_plan = params[:sort_plan] if params[:sort_plan] #Manifestation.sort_plan
    cookies.permanent[:sort_plan] = { :value => sort_plan } # XXX: セッションデータに格納してはダメ?
    search_opts[:sort_plan] = sort_plan

    search_opts
  end

  # indexアクションにおける検索関係のセッションデータを更新する。
  #
  #  * search_opts - 検索条件
  #  * search_container - 検索用のコンテナ(factory.new_searchで得られるもの)
  def update_search_sessions(search_opts, search_container)
    return unless search_opts[:index] == :local # FIXME: 非local検索のときにも動作するようにする

    search = search_container[:session]

    if session[:search_params]
      unless search.query.to_params == session[:search_params]
        clear_search_sessions
      end
    else
      clear_search_sessions
      session[:params] = params
      session[:search_params] = search.query.to_params
      session[:query] = search_opts[:solr_query_mode] ? @solr_query : @query
    end

    unless session[:manifestation_ids]
      # FIXME?
      # session[:manifestation_ids]は検索結果の書誌情報を次々と見るのに使われている
      # (manifestations/index→manifestations/show→manifestations/show→...)。
      # よって文献とその他を分ける場合には、このデータも分けて取りまわす必要があるはず。
      manifestation_ids = search.
        execute.raw_results.map {|r| r.primary_key.to_i }
      session[:manifestation_ids] = manifestation_ids
    end
  end

  # indexアクションにおけるOAI GetRecordへの応答出力を行う。
  # renderしたらtrueを返す。
  #
  #  * search_opts - 検索条件
  def do_oai_get_record_process(search_opts)
    unless search_opts[:oai_mode] &&
        params[:verb] == 'GetRecord' && params[:identifier]
      return false
    end

    begin
      @manifestation = Manifestation.find_by_oai_identifier(params[:identifier])
      render :template => 'manifestations/show', :formats => :oai, :layout => false
    rescue ActiveRecord::RecordNotFound
      @oai[:errors] << "idDoesNotExist"
      render :template => 'manifestations/index', :formats => :oai, :layout => false
    end

    true
  end
  #
  # indexアクションにおける直接参照への応答出力を行う。
  # renderしたらtrueを返す。
  #
  #  * search_opts - 検索条件
  def do_direct_mode_process(search_opts)
    return false unless search_opts[:direct_mode]

    manifestation = nil

    if params[:item_identifier].present?
      is = Item.where(:item_identifier => params[:item_identifier])
      manifestation = is.first.manifestation if is.size == 1
    end

    if SystemConfiguration.get("manifestation.isbn_unique") &&
        params[:isbn].present?
      ms = Manifestation.where(:isbn => params[:isbn])
      manifestation = ms.first if ms.size == 1
    end

    if params[:identifier].present?
      manifestation = Manifestation.where(:identifier => params[:identifier]).try(:first)
    end

    if manifestation
      unless params[:binding_items_flg]
        redirect_to manifestation
        return true
      end
    end

    false
  end

  # indexアクションにおける各種形式のファイルでの出力を行う。
  # ファイル送信するか、バックグラウンド処理をした旨の通知を行ったらtrueを返す。
  #
  #  * search_opts - 検索条件
  #  * search - 検索に用いるオブジェクト(Sunspotなど)
  def do_file_output_proccess(search_opts, search)
    return false unless search_opts[:index] == :local
    return false unless search_opts[:output_mode]

    # TODO: 第一引数にparamsまたは生成した検索語、フィルタ指定を渡すようにして、バックグラウンドファイル生成で一時ファイルを作らなくて済むようにする
    summary = @query.present? ? "#{@query} " : ""
    summary += advanced_search_condition_summary
    Manifestation.generate_manifestation_list(search[:all], search_opts[:output_type], current_user, summary, search_opts[:output_cols]) do |output|
      send_opts = {
        :filename => output.filename,
        :type => output.mime_type || 'application/octet-stream',
      }
      case output.result_type
      when :path
        send_file output.path, send_opts
      when :data
        send_data output.data, send_opts
      when :delayed
        flash[:message] = t('manifestation.output_job_queued', :job_name => output.job_name)
        redirect_to manifestations_path(params.dup.tap {|h| h.delete_if {|k, v| /\Aoutput/ =~ k || /\Acol/ =~ k} })
      else
        msg = "unknown result type: #{output.result_type.inspect} (bug?)"
        logger.error msg
        raise msg
      end
    end

    true
  end

  # indexアクションにおけるタグクラウド用の出力をする。
  # renderしたらtrueを返す。
  #
  #  * search_opts - 検索条件
  def do_tag_cloud_process(search_opts)
    unless search_opts[:tag_cloud_mode] && session[:manifestation_ids]
      return false
    end

    bookmark_ids = Bookmark.where(:manifestation_id => session[:manifestation_ids]).limit(1000).select(:id).collect(&:id)
    @tags = Tag.bookmarked(bookmark_ids)
    render :partial => 'manifestations/tag_cloud'
    #session[:manifestation_ids] = nil

    true
  end

  #
  # create classification vaules
  #
  def create_classification_values(add_classifications)
    classifications = []
    (add_classifications || []).each do |add_classification|
      next if add_classification[:classification_id].blank?
      classification = Classification.where(:id => add_classification[:classification_id]).first
      classifications << classification if classification.present?
    end
    return classifications
  end

  #
  # get classification vaules
  #
  def get_classification_values(manifestation)
    classifications = []
    manifestation_has_classifications = manifestation.manifestation_has_classifications.order(:position)
    if manifestation_has_classifications.present?
      manifestation_has_classifications.each do |manifestation_has_classification|
        classifications << {:classification_type_id => manifestation_has_classification.classification.classification_type_id,
                            :classification_id => manifestation_has_classification.classification_id}
      end
    else
      classifications << {}
    end
    return classifications
  end

end