mysociety/alaveteli

View on GitHub
app/controllers/public_body_controller.rb

Summary

Maintainability
D
1 day
Test Coverage
# -*- encoding : utf-8 -*-
# app/controllers/public_body_controller.rb:
# Show information about a public body.
#
# Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved.
# Email: hello@mysociety.org; WWW: http://www.mysociety.org/

require 'tempfile'

class PublicBodyController < ApplicationController

  MAX_RESULTS = 500
  # TODO: tidy this up with better error messages, and a more standard infrastructure for the redirect to canonical URL
  def show
    long_cache
    @page = get_search_page_from_params
    requests_per_page = 25

    # Later pages are very expensive to load
    if @page > MAX_RESULTS / requests_per_page
      raise ActiveRecord::RecordNotFound.new("Sorry. No pages after #{MAX_RESULTS / requests_per_page}.")
    end

    if MySociety::Format.simplify_url_part(params[:url_name], 'body') != params[:url_name]
      redirect_to :url_name =>  MySociety::Format.simplify_url_part(params[:url_name], 'body'), :status => :moved_permanently
      return
    end

    @locale = AlaveteliLocalization.locale

    AlaveteliLocalization.with_locale(@locale) do
      @public_body = PublicBody.find_by_url_name_with_historic(params[:url_name])
      raise ActiveRecord::RecordNotFound.new("None found") if @public_body.nil?

      if @public_body.url_name.nil?
        redirect_to :back
        return
      end

      # If found by historic name, or alternate locale name, redirect to new name
      if  @public_body.url_name != params[:url_name]
        redirect_to :url_name => @public_body.url_name
        return
      end

      set_last_body(@public_body)

      @number_of_visible_requests = @public_body.info_requests.is_searchable.count

      @searched_to_send_request = false
      referrer = request.env['HTTP_REFERER']

      if !referrer.nil? && referrer.match(%r{^#{frontpage_url}search/.*/bodies$})
        @searched_to_send_request = true
      end

      @view = params[:view]

      query = InfoRequestEvent.make_query_from_params(params.merge(:latest_status => @view))
      query += " requested_from:#{@public_body.url_name}"

      # Use search query for this so can collapse and paginate easily
      # TODO: really should just use SQL query here rather than Xapian.
      sortby = "described"
      begin
        @xapian_requests = perform_search([InfoRequestEvent], query, sortby, 'request_collapse', requests_per_page)
        if (@page > 1)
          @page_desc = " (page #{ @page })"
        else
          @page_desc = ""
        end
      rescue
        @xapian_requests = nil
      end

      flash.keep(:search_params)

      @track_thing = TrackThing.create_track_for_public_body(@public_body)

      if @user
        @existing_track = TrackThing.find_existing(@user, @track_thing)
      end

      @follower_count = TrackThing.where(:public_body_id => @public_body.id).count

      @feed_autodetect = [ { :url => do_track_url(@track_thing, 'feed'),
                             :title => @track_thing.params[:title_in_rss],
                             :has_json => true } ]

      respond_to do |format|
        format.html { @has_json = true; render :template => "public_body/show"}
        format.json { render :json => @public_body.json_for_api }
      end

    end
  end

  def view_email
    @public_body = PublicBody.find_by_url_name_with_historic(params[:url_name])
    raise ActiveRecord::RecordNotFound.new("None found") if @public_body.nil?

    AlaveteliLocalization.with_locale(AlaveteliLocalization.locale) do
      if params[:submitted_view_email]
        if verify_recaptcha
          flash.discard(:error)
          render :template => "public_body/view_email"
          return
        end
        flash.now[:error] = _('There was an error with the reCAPTCHA. ' \
                              'Please try again.')
      end
      render :template => "public_body/view_email_captcha"
    end
  end

  def list
    long_cache
    # TODO: move some of these tag SQL queries into has_tag_string.rb

    like_query = params[:public_body_query]
    like_query = "" if like_query.nil?
    like_query = "%#{like_query}%"

    @tag = params[:tag]

    @country_code = AlaveteliConfiguration.iso_country_code
    @locale = AlaveteliLocalization.locale
    underscore_locale = AlaveteliLocalization.locale
    underscore_default_locale = AlaveteliLocalization.default_locale

    where_condition = "public_bodies.id <> #{PublicBody.internal_admin_body.id}"
    where_parameters = []

    first_letter = false

    base_tag_condition = " AND (SELECT count(*) FROM has_tag_string_tags" \
      " WHERE has_tag_string_tags.model_id = public_bodies.id" \
      " AND has_tag_string_tags.model = 'PublicBody'"

    # Restrict the public bodies shown according to the tag
    # parameter supplied in the URL:
    if @tag.nil? || @tag == 'all'
      @tag = 'all'
    elsif @tag == 'other'
      category_list = PublicBodyCategory.get.tags.map{ |c| %Q('#{ c }') }.join(",")
      where_condition += base_tag_condition + " AND has_tag_string_tags.name in (#{category_list})) = 0"
    elsif @tag.scan(/./mu).size == 1
      @tag = Unicode.upcase(@tag)
      # The first letter queries have to be done on
      # translations, so just indicate to add that later:
      first_letter = true
    elsif @tag.include?(':')
      name, value = HasTagString::HasTagStringTag.split_tag_into_name_value(@tag)
      where_condition += base_tag_condition + " AND has_tag_string_tags.name = ? AND has_tag_string_tags.value = ?) > 0"
      where_parameters.concat [name, value]
    else
      where_condition += base_tag_condition + " AND has_tag_string_tags.name = ?) > 0"
      where_parameters.concat [@tag]
    end

    AlaveteliLocalization.with_locale(@locale) do

      if AlaveteliConfiguration::public_body_list_fallback_to_default_locale
        # Unfortunately, when we might fall back to the
        # default locale, this is a rather complex query:
        if DatabaseCollation.supports?(underscore_locale)
          select_sql = %Q(SELECT public_bodies.*, COALESCE(current_locale.name, default_locale.name) COLLATE "#{ underscore_locale }" AS display_name)
        else
          select_sql = %Q(SELECT public_bodies.*, COALESCE(current_locale.name, default_locale.name) AS display_name)
        end

        query =  %Q{
          #{ select_sql }
          FROM public_bodies
          LEFT OUTER JOIN public_body_translations as current_locale
            ON (public_bodies.id = current_locale.public_body_id
              AND current_locale.locale = ? AND #{ get_public_body_list_translated_condition('current_locale', first_letter) })
          LEFT OUTER JOIN public_body_translations as default_locale
            ON (public_bodies.id = default_locale.public_body_id
              AND default_locale.locale = ? AND #{ get_public_body_list_translated_condition('default_locale', first_letter) })
          WHERE #{ where_condition } AND COALESCE(current_locale.name, default_locale.name) IS NOT NULL
          ORDER BY display_name
        }
        @sql = [query, underscore_locale, like_query, like_query, like_query]
        @sql.push @tag if first_letter
        @sql += [underscore_default_locale, like_query, like_query, like_query]
        @sql.push @tag if first_letter
        @sql += where_parameters
        @public_bodies = PublicBody.paginate_by_sql(
          @sql,
          :page => params[:page],
          :per_page => 100)
      else
        # The simpler case where we're just searching in the current locale:
        where_condition = get_public_body_list_translated_condition('public_body_translations', first_letter, true) +
          ' AND ' + where_condition
        where_sql = [where_condition, like_query, like_query, like_query]
        where_sql.push @tag if first_letter
        where_sql += [underscore_locale] + where_parameters

        if DatabaseCollation.supports?(underscore_locale)
          @public_bodies = PublicBody.where(where_sql).
            joins(:translations).
              order(%Q(public_body_translations.name COLLATE "#{ underscore_locale }")).
                paginate(:page => params[:page], :per_page => 100)
        else
            @public_bodies = PublicBody.where(where_sql).
              joins(:translations).
                order('public_body_translations.name').
                  paginate(:page => params[:page], :per_page => 100)
        end
      end

    @description =
      if @tag == 'all'
        n_('Found {{count}} public authority',
           'Found {{count}} public authorities',
           @public_bodies.total_entries,
           :count => @public_bodies.total_entries)
      elsif @tag.size == 1
        n_('Found {{count}} public authority beginning with ' \
           '‘{{first_letter}}’',
           'Found {{count}} public authorities beginning with ' \
           '‘{{first_letter}}’',
           @public_bodies.total_entries,
           :count => @public_bodies.total_entries,
           :first_letter => @tag)
      else
        category_name = PublicBodyCategory.get.by_tag[@tag]
        if category_name.nil?
          n_('Found {{count}} public authority matching the tag ' \
             '‘{{tag_name}}’',
             'Found {{count}} public authorities matching the tag ' \
             '‘{{tag_name}}’',
             @public_bodies.total_entries,
             :count => @public_bodies.total_entries,
             :tag_name => @tag)
        else
          n_('Found {{count}} public authority in the category ' \
             '‘{{category_name}}’',
             'Found {{count}} public authorities in the category ' \
             '‘{{category_name}}’',
             @public_bodies.total_entries,
             :count => @public_bodies.total_entries,
             :category_name => category_name)
        end
      end

      respond_to do |format|
        format.html { render :template => 'public_body/list' }
      end
    end
  end

  # Used so URLs like /local/islington work, for use e.g. writing to a local paper.
  def list_redirect
    @tag = params[:tag]
    redirect_to list_public_bodies_url(:tag => @tag)
  end

  # GET /body/all-authorities.csv
  #
  # Returns all public bodies (except for the internal admin authority) as CSV
  def list_all_csv
    # FIXME: this is just using the download directory for zip
    # archives, since we know that is allowed for X-Sendfile and
    # the filename can't clash with the numeric subdirectory names
    # used for the zips.  However, really there should be a
    # generically named downloads directory that contains all
    # kinds of downloadable assets.
    download_directory = File.join(InfoRequest.download_zip_dir, 'download')
    FileUtils.mkdir_p(download_directory)
    output_leafname = 'all-authorities.csv'
    output_filename = File.join(download_directory, output_leafname)
    # Create a temporary file in the same directory, so we can
    # rename it atomically to the intended filename:
    tmp = Tempfile.new(output_leafname, download_directory)
    tmp.close

    # Create the CSV
    csv = PublicBodyCSV.new
    PublicBody.includes(:translations, :tags).visible.find_each do |public_body|
      next if public_body.site_administration?
      csv << public_body
    end

    # Export all the public bodies to that temporary path, make it readable,
    # and rename it
    File.open(tmp.path, 'w') { |file| file.write(csv.generate) }
    FileUtils.chmod(0644, tmp.path)
    File.rename(tmp.path, output_filename)

    # Send the file
    send_file(output_filename,
              :type => 'text/csv; charset=utf-8; header=present',
              :filename => 'all-authorities.csv',
              :disposition =>'attachment',
              :encoding => 'utf8')
  end

  # Type ahead search
  def search_typeahead
    query = params[:query]
    flash[:search_params] = params.slice(:query, :bodies, :page)
    @xapian_requests = typeahead_search(query, :model => PublicBody)
    render :partial => "public_body/search_ahead"
  end

  private

  def get_public_body_list_translated_condition(table, first_letter=false, locale=nil)
    result = "(upper(#{table}.name) LIKE upper(?)" \
      " OR upper(#{table}.notes) LIKE upper(?)" \
        " OR upper(#{table}.short_name) LIKE upper(?))"
        if first_letter
          result += " AND #{table}.first_letter = ?"
        end
        if locale
          result += " AND #{table}.locale = ?"
        end
        result
  end

end