diff options
129 files changed, 2585 insertions, 688 deletions
diff --git a/.gitignore b/.gitignore index 527bccb44..45865fd02 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,7 @@ TAGS  /public/download  /public/*theme  /vendor/bundle -.bundle
\ No newline at end of file +.bundle +bin/ +config/aliases +.sass-cache @@ -16,6 +16,7 @@ gem 'json', '~> 1.5.1'  gem 'mahoro'  gem 'memcache-client', :require => 'memcache'  gem 'locale', '>= 2.0.5' +gem 'net-purge'  gem 'rack', '~> 1.1.0'  gem 'rdoc', '~> 2.4.3'  gem 'recaptcha', '~> 0.3.1', :require => 'recaptcha/rails' @@ -28,7 +29,8 @@ gem 'ruby-msg', '~> 1.5.0'  gem 'test-unit', '~> 1.2.3' if RUBY_VERSION.to_f >= 1.9  gem 'vpim'  gem 'will_paginate', '~> 2.3.11' -gem 'xapian-full' +# when 1.2.9 is released by the maintainer, we can stop using this fork: +gem 'xapian-full', '~> 1.2.9', :git => 'git://github.com/sebbacon/xapian-full.git'  gem 'xml-simple'  gem 'zip' @@ -36,3 +38,8 @@ group :test do    gem 'fakeweb'    gem 'rspec-rails', '~> 1.3.4'  end + +group :develop do +  gem 'ruby-debug' +  gem 'annotate' +end diff --git a/Gemfile.lock b/Gemfile.lock index 8cf57fc79..084ce19f3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,9 @@ +GIT +  remote: git://github.com/sebbacon/xapian-full.git +  revision: 8f0f827d5964b28daa72c756e40caabfa2981fd0 +  specs: +    xapian-full (1.2.9) +  GEM    remote: http://rubygems.org/    specs: @@ -11,14 +17,19 @@ GEM      activeresource (2.3.14)        activesupport (= 2.3.14)      activesupport (2.3.14) +    annotate (2.4.0) +    columnize (0.3.6)      fakeweb (1.3.0)      fast_gettext (0.6.1)      gettext (2.1.0)        locale (>= 2.0.5)      json (1.5.4) +    linecache (0.46) +      rbx-require-relative (> 0.0.4)      locale (2.0.5)      mahoro (0.3)      memcache-client (1.8.5) +    net-purge (0.1.0)      pg (0.11.0)      rack (1.1.0)      rails (2.3.14) @@ -29,6 +40,7 @@ GEM        activesupport (= 2.3.14)        rake (>= 0.8.3)      rake (0.9.2) +    rbx-require-relative (0.0.9)      rdoc (2.4.3)      recaptcha (0.3.1)      rmagick (2.13.1) @@ -38,13 +50,17 @@ GEM      rspec-rails (1.3.4)        rack (>= 1.0.0)        rspec (~> 1.3.1) +    ruby-debug (0.10.4) +      columnize (>= 0.1) +      ruby-debug-base (~> 0.10.4.0) +    ruby-debug-base (0.10.4) +      linecache (>= 0.3)      ruby-msg (1.5.0)        ruby-ole (>= 1.2.8)        vpim (>= 0.360)      ruby-ole (1.2.11.2)      vpim (0.695)      will_paginate (2.3.16) -    xapian-full (1.2.3)      xml-simple (1.1.0)      zip (2.0.2) @@ -52,6 +68,7 @@ PLATFORMS    ruby  DEPENDENCIES +  annotate    fakeweb    fast_gettext (>= 0.6.0)    gettext (>= 1.9.3) @@ -59,6 +76,7 @@ DEPENDENCIES    locale (>= 2.0.5)    mahoro    memcache-client +  net-purge    pg    rack (~> 1.1.0)    rails (= 2.3.14) @@ -68,9 +86,10 @@ DEPENDENCIES    routing-filter (~> 0.2.4)    rspec (~> 1.3.2)    rspec-rails (~> 1.3.4) +  ruby-debug    ruby-msg (~> 1.5.0)    vpim    will_paginate (~> 2.3.11) -  xapian-full +  xapian-full (~> 1.2.9)!    xml-simple    zip @@ -3,7 +3,7 @@  This is an open source project to create a standard, internationalised  platform for making Freedom of Information (FOI) requests in different  countries around the world. The software started off life as -[WhatDoTheyKnow](http://github.com), a website produced by +[WhatDoTheyKnow](http://www.whatdotheyknow.com), a website produced by  [mySociety](http://mysociety.org) for making FOI requests in the UK.  We hope that by joining forces between teams across the world, we can diff --git a/app/controllers/admin_censor_rule_controller.rb b/app/controllers/admin_censor_rule_controller.rb index 2c0c7ca4e..52df8dfc1 100644 --- a/app/controllers/admin_censor_rule_controller.rb +++ b/app/controllers/admin_censor_rule_controller.rb @@ -65,7 +65,7 @@ class AdminCensorRuleController < AdminController              render :action => 'edit'          end      end -     +      def destroy          censor_rule = CensorRule.find(params[:censor_rule_id])          info_request = censor_rule.info_request diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index adb506b91..884d7e540 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -36,6 +36,8 @@ class AdminController < ApplicationController          # also force a search reindexing (so changed text reflected in search)          info_request.reindex_request_events +        # and remove from varnsi +        info_request.purge_in_cache      end      # Expire cached attachment files for a user @@ -44,23 +46,40 @@ class AdminController < ApplicationController              expire_for_request(info_request)          end      end -	private -	def authenticate -            config_username = MySociety::Config.get('ADMIN_USERNAME', '') -            config_password = MySociety::Config.get('ADMIN_PASSWORD', '') -            if !config_username.empty? && !config_password.empty? -                authenticate_or_request_with_http_basic do |user_name, password| -                    if user_name == config_username && password == config_password -                        session[:using_admin] = 1 -                        request.env['REMOTE_USER'] = user_name -                    else -                        request_http_basic_authentication +    private + +    def authenticate +        if MySociety::Config.get('SKIP_ADMIN_AUTH', false) +            session[:using_admin] = 1 +            return +        else +            if session[:using_admin].nil? +                if params[:emergency].nil? +                    if authenticated?( +                                      :web => _("To log into the administrative interface"), +                                      :email => _("Then you can log into the administrative interface"), +                                      :email_subject => _("Log into the admin interface"), +                                      :user_name => "a superuser") +                        if !@user.nil? && @user.admin_level == "super" +                            session[:using_admin] = 1 +                            request.env['REMOTE_USER'] = @user.url_name +                        end +                    end +                else +                    config_username = MySociety::Config.get('ADMIN_USERNAME', '') +                    config_password = MySociety::Config.get('ADMIN_PASSWORD', '') +                    authenticate_or_request_with_http_basic do |user_name, password| +                        if user_name == config_username && password == config_password +                            session[:using_admin] = 1 +                            request.env['REMOTE_USER'] = user_name +                        else +                            request_http_basic_authentication +                        end                      end                  end -            else -                session[:using_admin] = 1              end -	end +        end +    end  end diff --git a/app/controllers/admin_general_controller.rb b/app/controllers/admin_general_controller.rb index 0b7e9bec0..c83ae0f37 100644 --- a/app/controllers/admin_general_controller.rb +++ b/app/controllers/admin_general_controller.rb @@ -30,8 +30,9 @@ class AdminGeneralController < AdminController          # Tasks to do          @requires_admin_requests = InfoRequest.find(:all, :select => '*, ' + InfoRequest.last_event_time_clause + ' as last_event_time', :conditions => ["described_state = 'requires_admin'"], :order => "last_event_time")          @error_message_requests = InfoRequest.find(:all, :select => '*, ' + InfoRequest.last_event_time_clause + ' as last_event_time', :conditions => ["described_state = 'error_message'"], :order => "last_event_time") +        @attention_requests = InfoRequest.find(:all, :select => '*, ' + InfoRequest.last_event_time_clause + ' as last_event_time', :conditions => ["described_state = 'attention_requested'"], :order => "last_event_time")          @blank_contacts = PublicBody.find(:all, :conditions => ["request_email = ''"], :order => "updated_at") -        @old_unclassified = InfoRequest.find_old_unclassified(:limit => 20,  +        @old_unclassified = InfoRequest.find_old_unclassified(:limit => 20,                                                                         :conditions => ["prominence = 'normal'"])          @holding_pen_messages = InfoRequest.holding_pen_request.incoming_messages      end @@ -80,9 +81,10 @@ class AdminGeneralController < AdminController      def debug          @current_commit = `git log -1 --format="%H"`          @current_branch = `git branch | grep "\*" | awk '{print $2}'` +        @current_version = `git describe --always --tags`          repo = `git remote show origin -n | grep Fetch | awk '{print $3}' | sed -re 's/.*:(.*).git/\\1/'`          @github_origin = "https://github.com/#{repo.strip}/tree/" -        @request_env = request.env  +        @request_env = request.env      end  end diff --git a/app/controllers/admin_public_body_controller.rb b/app/controllers/admin_public_body_controller.rb index bf7c07905..be733ab7d 100644 --- a/app/controllers/admin_public_body_controller.rb +++ b/app/controllers/admin_public_body_controller.rb @@ -16,7 +16,7 @@ class AdminPublicBodyController < AdminController      def _lookup_query_internal          @locale = self.locale_from_params() -        PublicBody.with_locale(@locale) do  +        PublicBody.with_locale(@locale) do              @query = params[:query]              if @query == ""                  @query = nil @@ -26,13 +26,13 @@ class AdminPublicBodyController < AdminController                  @page = nil              end              @public_bodies = PublicBody.paginate :order => "public_body_translations.name", :page => @page, :per_page => 100, -                :conditions =>  @query.nil? ? "public_body_translations.locale = '#{@locale}'" :  -                                ["(lower(public_body_translations.name) like lower('%'||?||'%') or  -                                 lower(public_body_translations.short_name) like lower('%'||?||'%') or  +                :conditions =>  @query.nil? ? "public_body_translations.locale = '#{@locale}'" : +                                ["(lower(public_body_translations.name) like lower('%'||?||'%') or +                                 lower(public_body_translations.short_name) like lower('%'||?||'%') or                                   lower(public_body_translations.request_email) like lower('%'||?||'%' )) AND (public_body_translations.locale = '#{@locale}')", @query, @query, @query],                :joins => :translations          end -        @public_bodies_by_tag = PublicBody.find_by_tag(@query)  +        @public_bodies_by_tag = PublicBody.find_by_tag(@query)      end      def list @@ -62,11 +62,11 @@ class AdminPublicBodyController < AdminController      def missing_scheme          # There might be a way to do this in ActiveRecord, but I can't find it          @public_bodies = PublicBody.find_by_sql(" -            SELECT a.id, a.name, a.url_name, COUNT(*) AS howmany  -              FROM public_bodies a JOIN info_requests r ON a.id = r.public_body_id  -             WHERE a.publication_scheme = ''  -             GROUP BY a.id, a.name, a.url_name  -             ORDER BY howmany DESC  +            SELECT a.id, a.name, a.url_name, COUNT(*) AS howmany +              FROM public_bodies a JOIN info_requests r ON a.id = r.public_body_id +             WHERE a.publication_scheme = '' +             GROUP BY a.id, a.name, a.url_name +             ORDER BY howmany DESC               LIMIT 20          ")          @stats = { @@ -77,7 +77,7 @@ class AdminPublicBodyController < AdminController      def show          @locale = self.locale_from_params() -        PublicBody.with_locale(@locale) do  +        PublicBody.with_locale(@locale) do              @public_body = PublicBody.find(params[:id])              render          end @@ -87,7 +87,7 @@ class AdminPublicBodyController < AdminController          @public_body = PublicBody.new          render      end -     +      def create          PublicBody.with_locale(I18n.default_locale) do              params[:public_body][:last_edit_editor] = admin_http_auth_user() @@ -103,7 +103,7 @@ class AdminPublicBodyController < AdminController      def edit          @public_body = PublicBody.find(params[:id]) -        @public_body.last_edit_comment = ""         +        @public_body.last_edit_comment = ""          render      end @@ -122,7 +122,7 @@ class AdminPublicBodyController < AdminController      def destroy          @locale = self.locale_from_params() -        PublicBody.with_locale(@locale) do  +        PublicBody.with_locale(@locale) do              public_body = PublicBody.find(params[:id])              if public_body.info_requests.size > 0 @@ -147,7 +147,7 @@ class AdminPublicBodyController < AdminController              else                  raise "internal error, unknown button label"              end -             +              # Try with dry run first              csv_contents = params[:csv_file].read              en = PublicBody.import_csv(csv_contents, params[:tag], params[:tag_behaviour], true, admin_http_auth_user(), I18n.available_locales) @@ -174,7 +174,7 @@ class AdminPublicBodyController < AdminController              @errors = ""              @notes = ""          end -         +      end      private diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb index e5de4f8b7..3c700c567 100644 --- a/app/controllers/admin_request_controller.rb +++ b/app/controllers/admin_request_controller.rb @@ -60,10 +60,10 @@ class AdminRequestController < AdminController          if @info_request.valid?              @info_request.save! -            @info_request.log_event("edit",  -                { :editor => admin_http_auth_user(),  -                    :old_title => old_title, :title => @info_request.title,  -                    :old_prominence => old_prominence, :prominence => @info_request.prominence,  +            @info_request.log_event("edit", +                { :editor => admin_http_auth_user(), +                    :old_title => old_title, :title => @info_request.title, +                    :old_prominence => old_prominence, :prominence => @info_request.prominence,                      :old_described_state => old_described_state, :described_state => @info_request.described_state,                      :old_awaiting_description => old_awaiting_description, :awaiting_description => @info_request.awaiting_description,                      :old_allow_new_responses_from => old_allow_new_responses_from, :allow_new_responses_from => @info_request.allow_new_responses_from, @@ -75,7 +75,7 @@ class AdminRequestController < AdminController          else              render :action => 'edit'          end -    end  +    end      def fully_destroy          @info_request = InfoRequest.find(params[:id]) @@ -99,28 +99,28 @@ class AdminRequestController < AdminController          outgoing_message_id = @outgoing_message.id          @outgoing_message.fully_destroy -        @outgoing_message.info_request.log_event("destroy_outgoing",  +        @outgoing_message.info_request.log_event("destroy_outgoing",              { :editor => admin_http_auth_user(), :deleted_outgoing_message_id => outgoing_message_id })          flash[:notice] = 'Outgoing message successfully destroyed.'          redirect_to request_admin_url(@info_request) -    end  +    end      def update_outgoing          @outgoing_message = OutgoingMessage.find(params[:id])          old_body = @outgoing_message.body -        if @outgoing_message.update_attributes(params[:outgoing_message])  -            @outgoing_message.info_request.log_event("edit_outgoing",  -                { :outgoing_message_id => @outgoing_message.id, :editor => admin_http_auth_user(),  +        if @outgoing_message.update_attributes(params[:outgoing_message]) +            @outgoing_message.info_request.log_event("edit_outgoing", +                { :outgoing_message_id => @outgoing_message.id, :editor => admin_http_auth_user(),                      :old_body => old_body, :body => @outgoing_message.body })              flash[:notice] = 'Outgoing message successfully updated.'              redirect_to request_admin_url(@outgoing_message.info_request)          else              render :action => 'edit_outgoing'          end -    end  +    end      def edit_comment          @comment = Comment.find(params[:id]) @@ -133,9 +133,9 @@ class AdminRequestController < AdminController          old_visible = @comment.visible          @comment.visible = params[:comment][:visible] == "true" ? true : false -        if @comment.update_attributes(params[:comment])  -            @comment.info_request.log_event("edit_comment",  -                { :comment_id => @comment.id, :editor => admin_http_auth_user(),  +        if @comment.update_attributes(params[:comment]) +            @comment.info_request.log_event("edit_comment", +                { :comment_id => @comment.id, :editor => admin_http_auth_user(),                      :old_body => old_body, :body => @comment.body,                      :old_visible => old_visible, :visible => @comment.visible,                  }) @@ -144,7 +144,7 @@ class AdminRequestController < AdminController          else              render :action => 'edit_comment'          end -    end  +    end      def destroy_incoming @@ -153,12 +153,12 @@ class AdminRequestController < AdminController          incoming_message_id = @incoming_message.id          @incoming_message.fully_destroy -        @incoming_message.info_request.log_event("destroy_incoming",  +        @incoming_message.info_request.log_event("destroy_incoming",              { :editor => admin_http_auth_user(), :deleted_incoming_message_id => incoming_message_id })          flash[:notice] = 'Incoming message successfully destroyed.'          redirect_to request_admin_url(@info_request) -    end  +    end      def redeliver_incoming          incoming_message = IncomingMessage.find(params[:redeliver_incoming_message_id]) @@ -181,10 +181,10 @@ class AdminRequestController < AdminController          incoming_message_id = incoming_message.id          incoming_message.fully_destroy -        incoming_message.info_request.log_event("redeliver_incoming", {  -                :editor => admin_http_auth_user(),  -                :destination_request => destination_request.id,  -                :deleted_incoming_message_id => incoming_message_id  +        incoming_message.info_request.log_event("redeliver_incoming", { +                :editor => admin_http_auth_user(), +                :destination_request => destination_request.id, +                :deleted_incoming_message_id => incoming_message_id          })          flash[:notice] = "Message has been moved to this request" @@ -202,10 +202,10 @@ class AdminRequestController < AdminController              else                  info_request.user = destination_user                  info_request.save! -                info_request.log_event("move_request", {  -                        :editor => admin_http_auth_user(),  -                        :old_user_url_name => old_user.url_name,  -                        :user_url_name => destination_user.url_name  +                info_request.log_event("move_request", { +                        :editor => admin_http_auth_user(), +                        :old_user_url_name => old_user.url_name, +                        :user_url_name => destination_user.url_name                  })                  info_request.reindex_request_events @@ -220,10 +220,10 @@ class AdminRequestController < AdminController              else                  info_request.public_body = destination_public_body                  info_request.save! -                info_request.log_event("move_request", {  -                        :editor => admin_http_auth_user(),  -                        :old_public_body_url_name => old_public_body.url_name,  -                        :public_body_url_name => destination_public_body.url_name  +                info_request.log_event("move_request", { +                        :editor => admin_http_auth_user(), +                        :old_public_body_url_name => old_public_body.url_name, +                        :public_body_url_name => destination_public_body.url_name                  })                  info_request.reindex_request_events @@ -288,10 +288,10 @@ class AdminRequestController < AdminController              if domain.nil?                  @public_bodies = []              else -                @public_bodies = PublicBody.find(:all, :order => "name",  +                @public_bodies = PublicBody.find(:all, :order => "name",                      :conditions => [ "lower(request_email) like lower('%'||?||'%')", domain ])              end -             +              # 2. Match the email address in the message without matching the hash              @info_requests =  InfoRequest.guess_by_incoming_email(@raw_email.incoming_message) diff --git a/app/controllers/admin_user_controller.rb b/app/controllers/admin_user_controller.rb index b2c084739..4059ac0bb 100644 --- a/app/controllers/admin_user_controller.rb +++ b/app/controllers/admin_user_controller.rb @@ -15,7 +15,7 @@ class AdminUserController < AdminController      def list          @query = params[:query]          @admin_users = User.paginate :order => "name", :page => params[:page], :per_page => 100, -            :conditions =>  @query.nil? ? nil : ["lower(name) like lower('%'||?||'%') or  +            :conditions =>  @query.nil? ? nil : ["lower(name) like lower('%'||?||'%') or                               lower(email) like lower('%'||?||'%')", @query, @query]      end @@ -28,7 +28,7 @@ class AdminUserController < AdminController          # Don't use @user as that is any logged in user          @admin_user = User.find(params[:id])      end -     +      def show_bounce_message          @admin_user = User.find(params[:id])      end @@ -54,7 +54,7 @@ class AdminUserController < AdminController          else              render :action => 'edit'          end -    end  +    end      def destroy_track          track_thing = TrackThing.find(params[:track_id].to_i) @@ -62,7 +62,7 @@ class AdminUserController < AdminController          flash[:notice] = 'Track destroyed'          redirect_to user_admin_url(track_thing.tracking_user)      end -     +      def clear_bounce          user = User.find(params[:id])          user.email_bounced_at = nil @@ -74,10 +74,9 @@ class AdminUserController < AdminController      def login_as          @admin_user = User.find(params[:id]) # check user does exist -        post_redirect = PostRedirect.new( :uri => main_url(user_url(@admin_user)), :user_id => @admin_user.id) +        post_redirect = PostRedirect.new( :uri => main_url(user_url(@admin_user)), :user_id => @admin_user.id, :circumstance => "login_as" )          post_redirect.save!          url = main_url(confirm_url(:email_token => post_redirect.email_token, :only_path => true)) -        session[:user_id] = nil # Log out current (usually admin) user, so we get logged in as the other user          redirect_to url      end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 434f12a49..e305e90f4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*-  # controllers/application.rb:  # Parent class of all controllers in FOI site. Filters added to this controller  # apply to all controllers in the application. Likewise, all the methods added @@ -19,7 +20,7 @@ class ApplicationController < ActionController::Base      # Send notification email on exceptions      include ExceptionNotification::Notifiable -     +      # Note: a filter stops the chain if it redirects or renders something      before_filter :authentication_check      before_filter :set_gettext_locale @@ -33,7 +34,7 @@ class ApplicationController < ActionController::Base      def set_vary_header          response.headers['Vary'] = 'Cookie'      end -     +      helper_method :anonymous_cache, :short_cache, :medium_cache, :long_cache      def anonymous_cache(time)          if session[:user_id].nil? @@ -117,8 +118,20 @@ class ApplicationController < ActionController::Base      # Override default error handler, for production sites.      def rescue_action_in_public(exception) +        # Call `set_view_paths` from the theme, if it exists. +        # Normally, this is called by the theme itself in a +        # :before_filter, but when there's an error, this doesn't +        # happen.  By calling it here, we can ensure error pages are +        # still styled according to the theme. +        begin +            set_view_paths +        rescue NameError => e +            if !(e.message =~ /undefined local variable or method `set_view_paths'/) +                raise +            end +        end          # Make sure expiry time for session is set (before_filters are -        # otherwise missed by this override)  +        # otherwise missed by this override)          session_remember_me          case exception          when ActiveRecord::RecordNotFound, ActionController::UnknownAction, ActionController::RoutingError @@ -140,19 +153,19 @@ class ApplicationController < ActionController::Base      alias original_rescue_action_locally rescue_action_locally      def rescue_action_locally(exception)          # Make sure expiry time for session is set (before_filters are -        # otherwise missed by this override)  +        # otherwise missed by this override)          session_remember_me          # Display default, detailed error for developers          original_rescue_action_locally(exception)      end -       +      def local_request?          false      end -    # Called from test code, is a mimic of User.confirm, for use in following email -    # links when in controller tests (since we don't have full integration tests that +    # Called from test code, is a mimic of UserController.confirm, for use in following email +    # links when in controller tests (though we also have full integration tests that      # can work over multiple controllers)      def test_code_redirect_by_email_token(token, controller_example_group)          post_redirect = PostRedirect.find_by_email_token(token) @@ -178,7 +191,7 @@ class ApplicationController < ActionController::Base      end      def foi_fragment_cache_path(param) -        path = File.join(RAILS_ROOT, 'cache', 'views', foi_fragment_cache_part_path(param)) +        path = File.join(Rails.root, 'cache', 'views', foi_fragment_cache_part_path(param))          max_file_length = 255 - 35 # we subtract 35 because tempfile                                     # adds on a variable number of                                     # characters @@ -207,7 +220,7 @@ class ApplicationController < ActionController::Base          end      end -    # get the local locale  +    # get the local locale      def locale_from_params(*args)        if params[:show_locale]          params[:show_locale] @@ -224,15 +237,15 @@ class ApplicationController < ActionController::Base              post_redirect = PostRedirect.new(:uri => request.request_uri, :post_params => params,                  :reason_params => reason_params)              post_redirect.save! -            # 'modal' controls whether the sign-in form will be displayed in the typical full-blown  -            # page or on its own, useful for pop-ups             +            # 'modal' controls whether the sign-in form will be displayed in the typical full-blown +            # page or on its own, useful for pop-ups              redirect_to signin_url(:token => post_redirect.token, :modal => params[:modal])              return false          end          return true      end -    def authenticated_as_user?(user, reason_params)  +    def authenticated_as_user?(user, reason_params)          reason_params[:user_name] = user.name          reason_params[:user_url] = show_user_url(:url_name => user.url_name)          if session[:user_id] @@ -274,6 +287,8 @@ class ApplicationController < ActionController::Base          # XXX what is the built in Ruby URI munging function that can do this          # choice of & vs. ? more elegantly than this dumb if statement?          if uri.include?("?") +            # XXX This looks odd. What would a fragment identifier be doing server-side? +            #     But it also looks harmless, so I’ll leave it just in case.              if uri.include?("#")                  uri.sub!("#", "&post_redirect=1#")              else @@ -305,7 +320,7 @@ class ApplicationController < ActionController::Base          end      end -    #  +    #      def check_read_only          read_only = MySociety::Config.get('READ_ONLY', '')          if !read_only.empty? @@ -334,7 +349,7 @@ class ApplicationController < ActionController::Base          @http_auth_user = admin_http_auth_user      end -    # Convert URL name for sort by order, to Xapian query  +    # Convert URL name for sort by order, to Xapian query      def order_to_sort_by(sortby)          if sortby.nil?              return [nil, nil] @@ -350,7 +365,7 @@ class ApplicationController < ActionController::Base      end      # Function for search -    def perform_search(models, query, sortby, collapse, per_page = 25, this_page = nil)  +    def perform_search(models, query, sortby, collapse, per_page = 25, this_page = nil)          @query = query          @sortby = sortby @@ -386,7 +401,7 @@ class ApplicationController < ActionController::Base                  collapse = 'request_collapse'              end              options = { -                :offset => (@page - 1) * @per_page,  +                :offset => (@page - 1) * @per_page,                  :limit => @per_page,                  :sort_by_prefix => nil,                  :sort_by_ascending => true, @@ -405,7 +420,7 @@ class ApplicationController < ActionController::Base                  if e.message =~ /^QueryParserError: Wildcard/                      # Wildcard expands to too many terms                      logger.info "Wildcard query '#{query.strip + '*'}' caused: #{e.message}" -                     +                      user_query =  ActsAsXapian.query_parser.parse_query(                                                 query,                                                 Xapian::QueryParser::FLAG_LOVEHATE | @@ -434,8 +449,8 @@ class ApplicationController < ActionController::Base      def param_exists(item)          return params[item] && !params[item].empty? -    end     -     +    end +      def get_request_variety_from_params          query = ""          sortby = "newest" @@ -460,7 +475,7 @@ class ApplicationController < ActionController::Base      def get_status_from_params          query = "" -        if params[:latest_status]  +        if params[:latest_status]              statuses = []              if params[:latest_status].class == String                  params[:latest_status] = [params[:latest_status]] @@ -511,7 +526,7 @@ class ApplicationController < ActionController::Base          query = ""          tags = []          if param_exists(:tags) -            params[:tags].split().each do |tag|  +            params[:tags].split().each do |tag|                  tags << "tag:#{tag}"              end          end @@ -520,7 +535,7 @@ class ApplicationController < ActionController::Base          end          return query      end -     +      def make_query_from_params          query = params[:query] || "" if query.nil?          query += get_date_range_from_params @@ -541,16 +556,6 @@ class ApplicationController < ActionController::Base          return country      end -    def quietly_try_to_open(url) -        begin  -            result = open(url).read.strip -        rescue OpenURI::HTTPError, SocketError, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTUNREACH -            logger.warn("Unable to open third-party URL #{url}") -            result = "" -        end -        return result -    end -          # URL generating functions are needed by all controllers (for redirects),      # views (for links) and mailers (for use in emails), so include them into      # all of all. diff --git a/app/controllers/comment_controller.rb b/app/controllers/comment_controller.rb index 86d5b0a06..d9cd002dd 100644 --- a/app/controllers/comment_controller.rb +++ b/app/controllers/comment_controller.rb @@ -9,14 +9,14 @@  class CommentController < ApplicationController      before_filter :check_read_only, :only => [ :new ]      protect_from_forgery :only => [ :new ] -     +      def new          if params[:type] == 'request'              @info_request = InfoRequest.find_by_url_title(params[:url_title])              @track_thing = TrackThing.create_track_for_request(@info_request)              if params[:comment]                  @comment = Comment.new(params[:comment].merge({ -                    :comment_type => 'request',  +                    :comment_type => 'request',                      :user => @user                  }))              end @@ -38,7 +38,7 @@ class CommentController < ApplicationController              # Default to subscribing to request when first viewing form              params[:subscribe_to_request] = true          end -         +          # See if values were valid or not          if !params[:comment] || !@existing_comment.nil? || !@comment.valid? || params[:reedit]              render :action => 'new' diff --git a/app/controllers/general_controller.rb b/app/controllers/general_controller.rb index 82b1b8629..6e89a2832 100644 --- a/app/controllers/general_controller.rb +++ b/app/controllers/general_controller.rb @@ -28,19 +28,19 @@ class GeneralController < ApplicationController              @locale = self.locale_from_params()              locale_condition = 'public_body_translations.locale = ?'              conditions = [locale_condition, @locale] -            PublicBody.with_locale(@locale) do  +            PublicBody.with_locale(@locale) do                  if body_short_names.empty?                      # This is too slow -                    @popular_bodies = PublicBody.find(:all,  -                        :select => "public_bodies.*, (select count(*) from info_requests where info_requests.public_body_id = public_bodies.id) as c",  -                        :order => "c desc",  +                    @popular_bodies = PublicBody.find(:all, +                        :select => "public_bodies.*, (select count(*) from info_requests where info_requests.public_body_id = public_bodies.id) as c", +                        :order => "c desc",                          :limit => 32,                          :conditions => conditions,                          :joins => :translations                      )                  else                      conditions[0] += " and public_bodies.url_name in (" + body_short_names + ")" -                    @popular_bodies = PublicBody.find(:all,  +                    @popular_bodies = PublicBody.find(:all,                           :conditions => conditions,                           :joins => :translations)                  end @@ -52,7 +52,7 @@ class GeneralController < ApplicationController                  max_count = 5                  xapian_object = perform_search([InfoRequestEvent], query, sortby, 'request_title_collapse', max_count)                  @request_events = xapian_object.results.map { |r| r[:model] } -                 +                  # If there are not yet enough successful requests, fill out the list with                  # other requests                  if @request_events.count < max_count @@ -97,7 +97,7 @@ class GeneralController < ApplicationController              query_parts = @query.split("/")              if !['bodies', 'requests', 'users', 'all'].include?(query_parts[-1])                  redirect_to search_url([@query, "all"], params) -            else                 +            else                  redirect_to search_url(@query, params)              end          end @@ -236,4 +236,4 @@ class GeneralController < ApplicationController  end -  + diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index b08438b52..e3b77271e 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -9,7 +9,7 @@  class HelpController < ApplicationController      # we don't even have a control subroutine for most help pages, just see their templates -     +      before_filter :long_cache      def unhappy @@ -61,7 +61,7 @@ class HelpController < ApplicationController                      @last_request, @last_body                  )                  flash[:notice] = _("Your message has been sent. Thank you for getting in touch! We'll get back to you soon.") -                redirect_to frontpage_url  +                redirect_to frontpage_url                  return              end @@ -69,7 +69,7 @@ class HelpController < ApplicationController                  @contact.errors.clear              end          end -          +      end  end diff --git a/app/controllers/holiday_controller.rb b/app/controllers/holiday_controller.rb index 916ff54c8..7f62aa26d 100644 --- a/app/controllers/holiday_controller.rb +++ b/app/controllers/holiday_controller.rb @@ -1,5 +1,5 @@  # app/controllers/holiday_controller.rb: -# Calculate dates  +# Calculate dates  #  # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved.  # Email: francis@mysociety.org; WWW: http://www.mysociety.org/ @@ -16,7 +16,7 @@ class HolidayController < ApplicationController              @request_date = Date.strptime(params[:holiday]) or raise "Invalid date"              @due_date = Holiday.due_date_from(@request_date, 20)              @skipped = Holiday.all( -                :conditions => [ 'day >= ? AND day <= ?',  +                :conditions => [ 'day >= ? AND day <= ?',                      @request_date.strftime("%F"), @due_date.strftime("%F")                  ]              ).collect { |h| h.day }.sort diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb index 00d1cc1e0..95d936e54 100644 --- a/app/controllers/public_body_controller.rb +++ b/app/controllers/public_body_controller.rb @@ -14,23 +14,23 @@ class PublicBodyController < ApplicationController      def show          long_cache          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  +            redirect_to :url_name =>  MySociety::Format.simplify_url_part(params[:url_name], 'body'), :status => :moved_permanently              return          end          @locale = self.locale_from_params() -        PublicBody.with_locale(@locale) do  +        PublicBody.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  +            end              # If found by historic name, or alternate locale name, redirect to new name              if  @public_body.url_name != params[:url_name] -                redirect_to show_public_body_url(:url_name => @public_body.url_name)  +                redirect_to show_public_body_url(:url_name => @public_body.url_name)                  return              end -                +              set_last_body(@public_body)              top_url = main_url("/") @@ -50,8 +50,8 @@ class PublicBodyController < ApplicationController              begin                  @xapian_requests = perform_search([InfoRequestEvent], query, sortby, 'request_collapse')                  if (@page > 1) -                    @page_desc = " (page " + @page.to_s + ")"  -                else     +                    @page_desc = " (page " + @page.to_s + ")" +                else                      @page_desc = ""                  end              rescue @@ -65,7 +65,7 @@ class PublicBodyController < ApplicationController                  format.html { @has_json = true; render :template => "public_body/show"}                  format.json { render :json => @public_body.json_for_api }              end -             +          end      end @@ -93,8 +93,8 @@ class PublicBodyController < ApplicationController          @tag = params[:tag]          @locale = self.locale_from_params()          default_locale = I18n.default_locale.to_s -        locale_condition = "(upper(public_body_translations.name) LIKE upper(?)  -                            OR upper(public_body_translations.notes) LIKE upper (?))  +        locale_condition = "(upper(public_body_translations.name) LIKE upper(?) +                            OR upper(public_body_translations.notes) LIKE upper (?))                              AND public_body_translations.locale = ?                              AND public_bodies.id <> #{PublicBody.internal_admin_body.id}"          if @tag.nil? or @tag == "all" @@ -152,10 +152,10 @@ class PublicBodyController < ApplicationController          report = StringIO.new          CSV::Writer.generate(report, ',') do |title|              title << [ -                    'Name',  +                    'Name',                      'Short name',                      # deliberately not including 'Request email' -                    'URL name',  +                    'URL name',                      'Tags',                      'Home page',                      'Publication scheme', @@ -164,12 +164,12 @@ class PublicBodyController < ApplicationController                      'Version',              ]              public_bodies.each do |public_body| -                title << [  -                    public_body.name,  -                    public_body.short_name,  +                title << [ +                    public_body.name, +                    public_body.short_name,                      # DO NOT include request_email (we don't want to make it                      # easy to spam all authorities with requests) -                    public_body.url_name,  +                    public_body.url_name,                      public_body.tag_string,                      public_body.calculated_home_page,                      public_body.publication_scheme, @@ -181,7 +181,7 @@ class PublicBodyController < ApplicationController          end          report.rewind          send_data(report.read, :type=> 'text/csv; charset=utf-8; header=present', -                  :filename => 'all-authorities.csv',  +                  :filename => 'all-authorities.csv',                    :disposition =>'attachment', :encoding => 'utf8')      end diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index 7ca081c04..94fbcde29 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -17,7 +17,7 @@ class RequestController < ApplicationController      MAX_RESULTS = 500      PER_PAGE = 25 -     +      @@custom_states_loaded = false      begin          if ENV["RAILS_ENV"] != "test" @@ -45,11 +45,11 @@ class RequestController < ApplicationController          end          medium_cache      end -     +      def show          medium_cache          @locale = self.locale_from_params() -        PublicBody.with_locale(@locale) do   +        PublicBody.with_locale(@locale) do              # Look up by old style numeric identifiers              if params[:url_title].match(/^[0-9]+$/) @@ -58,7 +58,7 @@ class RequestController < ApplicationController                  return              end -            # Look up by new style text names  +            # Look up by new style text names              @info_request = InfoRequest.find_by_url_title(params[:url_title])              if @info_request.nil?                  raise ActiveRecord::RecordNotFound.new("Request not found") @@ -70,7 +70,7 @@ class RequestController < ApplicationController                  render :template => 'request/hidden', :status => 410 # gone                  return              end -            +              # Other parameters              @info_request_events = @info_request.info_request_events              @status = @info_request.calculate_status @@ -78,7 +78,7 @@ class RequestController < ApplicationController              @update_status = params[:update_status] ? true : false              @old_unclassified = @info_request.is_old_unclassified? && !authenticated_user.nil?              @is_owning_user = @info_request.is_owning_user?(authenticated_user) -             +              if @update_status                  return if !@is_owning_user && !authenticated_as_user?(@info_request.user,                          :web => _("To update the status of this FOI request"), @@ -86,7 +86,7 @@ class RequestController < ApplicationController                          :email_subject => _("Update the status of your request to ") + @info_request.public_body.name                      )              end -             +              @last_info_request_event_id = @info_request.last_event_id_needing_description              @new_responses_count = @info_request.events_needing_description.select {|i| i.event_type == 'response'}.size @@ -96,14 +96,14 @@ class RequestController < ApplicationController              behavior_cache :tag => ['similar', @info_request.id] do                  begin                      limit = 10 -                    @xapian_similar = ::ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events,  +                    @xapian_similar = ::ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events,                        :limit => limit, :collapse_by_prefix => 'request_collapse')                      @xapian_similar_more = (@xapian_similar.matches_estimated > limit)                  rescue                      @xapian_similar = nil                  end              end -          +              # Track corresponding to this page              @track_thing = TrackThing.create_track_for_request(@info_request)              @feed_autodetect = [ { :url => do_track_url(@track_thing, 'feed'), :title => @track_thing.params[:title_in_rss], :has_json => true } ] @@ -123,7 +123,7 @@ class RequestController < ApplicationController          @info_request = InfoRequest.find_by_url_title(params[:url_title])          if @info_request.nil?              raise ActiveRecord::RecordNotFound.new("Request not found") -        else             +        else              if !@info_request.user_can_view?(authenticated_user)                  render :template => 'request/hidden', :status => 410 # gone                  return @@ -139,16 +139,16 @@ class RequestController < ApplicationController          @page = (params[:page] || "1").to_i          @info_request = InfoRequest.find_by_url_title(params[:url_title])          raise ActiveRecord::RecordNotFound.new("Request not found") if @info_request.nil? -         +          if !@info_request.user_can_view?(authenticated_user)              render :template => 'request/hidden', :status => 410 # gone              return          end -        @xapian_object = ::ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events,  +        @xapian_object = ::ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events,              :offset => (@page - 1) * @per_page, :limit => @per_page, :collapse_by_prefix => 'request_collapse') -         +          if (@page > 1) -            @page_desc = " (page " + @page.to_s + ")"  +            @page_desc = " (page " + @page.to_s + ")"          else              @page_desc = ""          end @@ -161,7 +161,7 @@ class RequestController < ApplicationController          if @view == "recent"              return redirect_to request_list_all_path(:action => "list", :view => "all", :page => @page), :status => :moved_permanently          end -         +          # Later pages are very expensive to load          if @page > MAX_RESULTS / PER_PAGE              raise ActiveRecord::RecordNotFound.new("Sorry. No pages after #{MAX_RESULTS / PER_PAGE}.") @@ -171,14 +171,14 @@ class RequestController < ApplicationController          query = make_query_from_params          @title = _("View and search requests")          sortby = "newest" -        @cache_tag = Digest::MD5.hexdigest(query + @page.to_s)  +        @cache_tag = Digest::MD5.hexdigest(query + @page.to_s + I18n.locale.to_s)          behavior_cache :tag => [@cache_tag] do              xapian_object = perform_search([InfoRequestEvent], query, sortby, 'request_collapse')              @list_results = xapian_object.results.map { |r| r[:model] }              @matches_estimated = xapian_object.matches_estimated              @show_no_more_than = (@matches_estimated > MAX_RESULTS) ? MAX_RESULTS : @matches_estimated          end -         +          @title = @title + " (page " + @page.to_s + ")" if (@page > 1)          @track_thing = TrackThing.create_track_for_search_query(query)          @feed_autodetect = [ { :url => do_track_url(@track_thing, 'feed'), :title => @track_thing.params[:title_in_rss], :has_json => true } ] @@ -202,7 +202,7 @@ class RequestController < ApplicationController          # get_undescribed_requests also allows one day since the response          # arrived.          if !@user.nil? && params[:submitted_new_request].nil? && !@user.can_leave_requests_undescribed? -            @undescribed_requests = @user.get_undescribed_requests  +            @undescribed_requests = @user.get_undescribed_requests              if @undescribed_requests.size > 1                  render :action => 'new_please_describe'                  return @@ -248,7 +248,7 @@ class RequestController < ApplicationController              elsif params[:public_body_id]                  params[:info_request][:public_body_id] = params[:public_body_id]              end -            if !params[:info_request][:public_body_id]  +            if !params[:info_request][:public_body_id]                  # compulsory to have a body by here, or go to front page which is start of process                  redirect_to frontpage_url                  return @@ -266,7 +266,7 @@ class RequestController < ApplicationController              params[:outgoing_message][:info_request] = @info_request              @outgoing_message = OutgoingMessage.new(params[:outgoing_message])              @outgoing_message.set_signature_name(@user.name) if !@user.nil? -             +              if @info_request.public_body.is_requestable?                  render :action => 'new'              else @@ -289,8 +289,8 @@ class RequestController < ApplicationController          # Create both FOI request and the first request message          @info_request = InfoRequest.new(params[:info_request]) -        @outgoing_message = OutgoingMessage.new(params[:outgoing_message].merge({  -            :status => 'ready',  +        @outgoing_message = OutgoingMessage.new(params[:outgoing_message].merge({ +            :status => 'ready',              :message_type => 'initial_request'          }))          @info_request.outgoing_messages << @outgoing_message @@ -315,7 +315,7 @@ class RequestController < ApplicationController          if params[:preview].to_i == 1              message = ""              if @outgoing_message.contains_email? -                if @user.nil?  +                if @user.nil?                      message += _("<p>You do not need to include your email in the request in order to get a reply, as we will ask for it on the next screen (<a href=\"%s\">details</a>).</p>") % [help_privacy_path+"#email_address"];                  else                      message += _("<p>You do not need to include your email in the request in order to get a reply (<a href=\"%s\">details</a>).</p>") % [help_privacy_path+"#email_address"]; @@ -361,7 +361,7 @@ class RequestController < ApplicationController          flash[:notice] = _("<p>Your {{law_used_full}} request has been <strong>sent on its way</strong>!</p>              <p><strong>We will email you</strong> when there is a response, or after {{late_number_of_days}} working days if the authority still hasn't              replied by then.</p> -            <p>If you write about this request (for example in a forum or a blog) please link to this page, and add an  +            <p>If you write about this request (for example in a forum or a blog) please link to this page, and add an              annotation below telling people about your writing.</p>",:law_used_full=>@info_request.law_used_full,              :late_number_of_days => MySociety::Config.get('REPLY_LATE_AFTER_DAYS', 20))          redirect_to show_new_request_path(:url_title => @info_request.url_title) @@ -378,10 +378,10 @@ class RequestController < ApplicationController              return          end -        @is_owning_user = @info_request.is_owning_user?(authenticated_user)        +        @is_owning_user = @info_request.is_owning_user?(authenticated_user)          @last_info_request_event_id = @info_request.last_event_id_needing_description          @old_unclassified = @info_request.is_old_unclassified? && !authenticated_user.nil? -         +          # Check authenticated, and parameters set. We check is_owning_user          # to get admin overrides (see is_owning_user? above)          if !@old_unclassified && !@is_owning_user && !authenticated_as_user?(@info_request.user, @@ -408,7 +408,7 @@ class RequestController < ApplicationController          # Make the state change          old_described_state = @info_request.described_state          @info_request.set_described_state(params[:incoming_message][:described_state]) -         +          # If you're not the *actual* requester owner. e.g. you are playing the          # classification game, or you're doing this just because you are an          # admin user (not because you also own the request). @@ -417,24 +417,24 @@ class RequestController < ApplicationController              # don't log if you were the requester XXX This is presumably so you              # don't score for classifying your own requests. Could instead              # always log and filter at display time. -            @info_request.log_event("status_update",  -                { :user_id => authenticated_user.id,  -                  :old_described_state => old_described_state,  +            @info_request.log_event("status_update", +                { :user_id => authenticated_user.id, +                  :old_described_state => old_described_state,                    :described_state => @info_request.described_state,                  }) -             +              # Don't give advice on what to do next, as it isn't their request              RequestMailer.deliver_old_unclassified_updated(@info_request) -            if session[:request_game]  +            if session[:request_game]                  flash[:notice] = _('Thank you for updating the status of the request \'<a href="{{url}}">{{info_request_title}}</a>\'. There are some more requests below for you to classify.',:info_request_title=>CGI.escapeHTML(@info_request.title), :url=>CGI.escapeHTML(request_url(@info_request))) -                redirect_to play_url  +                redirect_to play_url              else                  flash[:notice] = _('Thank you for updating this request!')                  redirect_to request_url(@info_request)              end              return          end -         +          # Display advice for requester on what to do next, as appropriate          if @info_request.calculate_status == 'waiting_response'              flash[:notice] = _("<p>Thank you! Hopefully your wait isn't too long.</p> <p>By law, you should get a response promptly, and normally before the end of <strong> @@ -450,14 +450,14 @@ class RequestController < ApplicationController              flash[:notice] = _("<p>Thank you! Here are some ideas on what to do next:</p>              <ul>              <li>To send your request to another authority, first copy the text of your request below, then <a href=\"{{find_authority_url}}\">find the other authority</a>.</li> -            <li>If you would like to contest the authority's claim that they do not hold the information, here is  +            <li>If you would like to contest the authority's claim that they do not hold the information, here is              <a href=\"{{complain_url}}\">how to complain</a>.              </li>              <li>We have <a href=\"{{other_means_url}}\">suggestions</a>              on other means to answer your question.              </li> -            </ul>",  -            :find_authority_url => "/new",  +            </ul>", +            :find_authority_url => "/new",              :complain_url => CGI.escapeHTML(unhappy_url(@info_request)),              :other_means_url => CGI.escapeHTML(unhappy_url(@info_request)) + "#other_means")              redirect_to request_url(@info_request) @@ -496,7 +496,7 @@ class RequestController < ApplicationController          end      end -    # Used for links from polymorphic URLs e.g. in Atom feeds - just redirect to  +    # Used for links from polymorphic URLs e.g. in Atom feeds - just redirect to      # proper URL for the message the event refers to      def show_request_event          @info_request_event = InfoRequestEvent.find(params[:info_request_event_id]) @@ -506,8 +506,8 @@ class RequestController < ApplicationController              redirect_to outgoing_message_url(@info_request_event.outgoing_message), :status => :moved_permanently          else              # XXX maybe there are better URLs for some events than this -            redirect_to request_url(@info_request_event.info_request), :status => :moved_permanently  -        end  +            redirect_to request_url(@info_request_event.info_request), :status => :moved_permanently +        end      end      # Show an individual incoming message, and allow followup @@ -551,8 +551,8 @@ class RequestController < ApplicationController          if params_outgoing_message.nil?              params_outgoing_message = {}          end -        params_outgoing_message.merge!({  -            :status => 'ready',  +        params_outgoing_message.merge!({ +            :status => 'ready',              :message_type => 'followup',              :incoming_message_followup => @incoming_message,              :info_request_id => @info_request.id @@ -576,7 +576,7 @@ class RequestController < ApplicationController              render :template => 'request/hidden', :status => 410 # gone              return          end -  +          # Check address is good          if !OutgoingMailer.is_followupable?(@info_request, @incoming_message)              raise "unexpected followupable inconsistency" if @info_request.public_body.is_requestable? @@ -589,7 +589,7 @@ class RequestController < ApplicationController          # to make sure they're the right user first, before they start writing a          # message and wasting their time if they are not the requester.          if !authenticated_as_user?(@info_request.user, -                :web => @incoming_message.nil? ?  +                :web => @incoming_message.nil? ?                      _("To send a follow up message to ") + @info_request.public_body.name :                      _("To reply to ") + @info_request.public_body.name,                  :email => @incoming_message.nil? ? @@ -654,6 +654,19 @@ class RequestController < ApplicationController          end      end +    def report_request +        info_request = InfoRequest.find_by_url_title(params[:url_title]) +        if !info_request.attention_requested +            info_request.set_described_state('attention_requested') +            info_request.attention_requested = true # tells us if attention has ever been requested +            info_request.save! +            flash[:notice] = _("This request has been reported for administrator attention") +        else +            flash[:notice] = _("This request has already been reported for administrator attention") +        end +        redirect_to request_url(info_request) +    end +      # special caching code so mime types are handled right      around_filter :cache_attachments, :only => [ :get_attachment, :get_attachment_as_html ]      def cache_attachments @@ -687,7 +700,7 @@ class RequestController < ApplicationController          # Prevent spam to magic request address. Note that the binary          # subsitution method used depends on the content type -        @incoming_message.binary_mask_stuff!(@attachment.body, @attachment.content_type)  +        @incoming_message.binary_mask_stuff!(@attachment.body, @attachment.content_type)          # we don't use @attachment.content_type here, as we want same mime type when cached in cache_attachments above          response.content_type = AlaveteliFileTypes.filename_to_mimetype(params[:file_name].join("/")) || 'application/octet-stream' @@ -715,7 +728,7 @@ class RequestController < ApplicationController          html.sub!("<prefix-here>", view_html_prefix)          html.sub!("<attachment-url-here>", CGI.escape(@attachment_url)) -        @incoming_message.html_mask_stuff!(html)  +        @incoming_message.html_mask_stuff!(html)          response.content_type = 'text/html'          render :text => html      end @@ -740,7 +753,7 @@ class RequestController < ApplicationController          else              @original_filename = @filename          end -        +          # check permissions          raise "internal error, pre-auth filter should have caught this" if !@info_request.user_can_view?(authenticated_user)          @attachment = IncomingMessage.get_attachment_by_url_part_number(@incoming_message.get_attachments_for_display, @part_number) @@ -757,7 +770,7 @@ class RequestController < ApplicationController      # FOI officers can upload a response      def upload_response          @locale = self.locale_from_params() -        PublicBody.with_locale(@locale) do  +        PublicBody.with_locale(@locale) do              @info_request = InfoRequest.find_by_url_title(params[:url_title])              @reason_params = { @@ -827,7 +840,7 @@ class RequestController < ApplicationController                  updated = Digest::SHA1.hexdigest(info_request.get_last_event.created_at.to_i.to_s + info_request.updated_at.to_i.to_s)                  @url_path = "/download/#{updated[0..1]}/#{updated}/#{params[:url_title]}.zip"                  file_path = File.join(File.dirname(__FILE__), '../../cache/zips', @url_path) -                if !File.exists?(file_path)  +                if !File.exists?(file_path)                      FileUtils.mkdir_p(File.dirname(file_path))                      Zip::ZipFile.open(file_path, Zip::ZipFile::CREATE) { |zipfile|                          convert_command = MySociety::Config.get("HTML_TO_PDF_COMMAND") @@ -858,7 +871,7 @@ class RequestController < ApplicationController                                  f.puts(output)                              }                          end -                        for message in info_request.incoming_messages                 +                        for message in info_request.incoming_messages                              attachments = message.get_attachments_for_display                              for attachment in attachments                                  filename = "#{attachment.url_part_number}_#{attachment.display_filename}" diff --git a/app/controllers/request_game_controller.rb b/app/controllers/request_game_controller.rb index 8a84575bb..904c44759 100644 --- a/app/controllers/request_game_controller.rb +++ b/app/controllers/request_game_controller.rb @@ -7,7 +7,7 @@  # $Id: request_game_controller.rb,v 1.9 2009-10-19 22:06:54 francis Exp $  class RequestGameController < ApplicationController -     +      def play          session[:request_game] = Time.now @@ -20,7 +20,7 @@ class RequestGameController < ApplicationController          @requests = old.sort_by{ rand }.slice(0..2)          if @missing == 0 -            flash[:notice] = _('<p>All done! Thank you very much for your help.</p><p>There are <a href="{{helpus_url}}">more things you can do</a> to help {{site_name}}.</p>',  +            flash[:notice] = _('<p>All done! Thank you very much for your help.</p><p>There are <a href="{{helpus_url}}">more things you can do</a> to help {{site_name}}.</p>',                  :helpus_url => help_credits_path+"#helpus",                  :site_name => site_name)          end @@ -38,7 +38,7 @@ class RequestGameController < ApplicationController          url_title = params[:url_title]          if !authenticated?(                  :web => _("To play the request categorisation game"), -                :email => _("Then you can play the request categorisation game."),  +                :email => _("Then you can play the request categorisation game."),                  :email_subject => _("Play the request categorisation game")              )              # do nothing - as "authenticated?" has done the redirect to signin page for us diff --git a/app/controllers/track_controller.rb b/app/controllers/track_controller.rb index d858ab233..312cedc6c 100644 --- a/app/controllers/track_controller.rb +++ b/app/controllers/track_controller.rb @@ -50,11 +50,15 @@ class TrackController < ApplicationController          raise ActiveRecord::RecordNotFound.new("None found") if @public_body.nil?          # If found by historic name, or alternate locale name, redirect to new name          if  @public_body.url_name != params[:url_name] -            redirect_to track_public_body_url(:url_name => @public_body.url_name, :feed => params[:feed]) +            redirect_to track_public_body_url(:url_name => @public_body.url_name, :feed => params[:feed], :event_type => params[:event_type])              return          end -        @track_thing = TrackThing.create_track_for_public_body(@public_body) +        if params[:event_type] +            @track_thing = TrackThing.create_track_for_public_body(@public_body, params[:event_type]) +        else +            @track_thing = TrackThing.create_track_for_public_body(@public_body) +        end          return atom_feed_internal if params[:feed] == 'feed' @@ -94,7 +98,23 @@ class TrackController < ApplicationController          return atom_feed_internal if params[:feed] == 'feed'          if self.track_set -            redirect_to search_url(@query) +            if @query.scan("variety").length == 1 +                # we're making a track for a simple filter, for which +                # there's an expression in the UI (rather than relying +                # on index:value strings in the query) +                if @query =~ /variety:user/ +                    postfix = "users" +                    @query.sub!("variety:user", "") +                elsif @query =~ /variety:authority/ +                    postfix = "bodies" +                    @query.sub!("variety:authority", "") +                elsif @query =~ /variety:sent/ +                    postfix = "requests" +                    @query.sub!("variety:sent", "") +                end +                @query.strip! +            end +            redirect_to search_url([@query, postfix])          end      end diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index 403cb9684..76c56c442 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -9,7 +9,7 @@  class UserController < ApplicationController      layout :select_layout -     +      protect_from_forgery :only => [ :contact,                                      :set_profile_photo,                                      :signchangeemail, @@ -33,7 +33,7 @@ class UserController < ApplicationController              @show_profile = false              @show_requests = true          end -         +          @display_user = User.find(:first, :conditions => [ "url_name = ? and email_confirmed = ?", params[:url_name], true ])          if not @display_user              raise ActiveRecord::RecordNotFound.new("user not found, url_name=" + params[:url_name]) @@ -55,7 +55,7 @@ class UserController < ApplicationController                  end                  @xapian_requests = perform_search([InfoRequestEvent], requests_query, 'newest', 'request_collapse')                  @xapian_comments = perform_search([InfoRequestEvent], comments_query, 'newest', nil) -                 +                  if (@page > 1)                      @page_desc = " (page " + @page.to_s + ")"                  else @@ -129,7 +129,7 @@ class UserController < ApplicationController                      session[:user_id] = @user_signin.id                      session[:user_circumstance] = nil                      session[:remember_me] = params[:remember_me] ? true : false -                     +                      if is_modal_dialog                          render :action => 'signin_successful'                      else @@ -182,7 +182,7 @@ class UserController < ApplicationController              return          end -        if !User.stay_logged_in_on_redirect?(@user) +        if !User.stay_logged_in_on_redirect?(@user) || post_redirect.circumstance == "login_as"              @user = post_redirect.user              @user.email_confirmed = true              @user.save! @@ -319,7 +319,7 @@ class UserController < ApplicationController          if (not session[:user_circumstance]) or (session[:user_circumstance] != "change_email")              # don't store the password in the db              params[:signchangeemail].delete(:password) -            post_redirect = PostRedirect.new(:uri => signchangeemail_url(),  +            post_redirect = PostRedirect.new(:uri => signchangeemail_url(),                                               :post_params => params,                                               :circumstance => "change_email" # special login that lets you change your email              ) @@ -538,12 +538,12 @@ class UserController < ApplicationController      def is_modal_dialog          (params[:modal].to_i != 0)      end -     +      # when logging in through a modal iframe, don't display chrome around the content      def select_layout          is_modal_dialog ? 'no_chrome' : 'default'      end -     +      # Decide where we are going to redirect back to after signin/signup, and record that      def work_out_post_redirect          # Redirect to front page later if nothing else specified diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a0f16dfaf..0520a8c77 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -32,14 +32,14 @@ module ApplicationHelper                    html[key] = 'errorExplanation'                end            end -           +            error_messages = []            for object in objects                object.errors.each do |attr, message|                    error_messages << content_tag(:li, message)                end            end -           +            content_tag(:div,                content_tag(:ul, error_messages),              html @@ -48,7 +48,7 @@ module ApplicationHelper              ''          end      end -     +      # Highlight words, also escapes HTML (other than spans that we add)      def highlight_words(t, words, html = true)          if html @@ -70,10 +70,10 @@ module ApplicationHelper          t = highlight_words(t, words, html)          return t      end -	 +      def locale_name(locale)          return LanguageNames::get_language_name(locale) -    end   +    end      # Use our own algorithm for finding path of cache      def foi_cache(name = {}, options = nil, &block) @@ -100,11 +100,11 @@ module ApplicationHelper      def sanitized_object_name(object_name)          object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/,"_").sub(/_$/,"")      end -  +      def sanitized_method_name(method_name)          method_name.sub(/\?$/, "")      end -  +      def form_tag_id(object_name, method_name, locale=nil)  	if locale.nil?              return "#{sanitized_object_name(object_name.to_s)}_#{sanitized_method_name(method_name.to_s)}" @@ -113,5 +113,19 @@ module ApplicationHelper          end      end +    def admin_value(v) +      if v.nil? +        nil +      elsif v.instance_of?(Time) +        admin_date(v) +      else +        h(v) +      end +    end + +    def admin_date(date) +      "#{I18n.l(date, :format => "%e %B %Y %H:%M:%S")} (#{_('{{length_of_time}} ago', :length_of_time => time_ago_in_words(date))})" +    end +  end diff --git a/app/helpers/config_helper.rb b/app/helpers/config_helper.rb index b0381a2f5..543b60256 100644 --- a/app/helpers/config_helper.rb +++ b/app/helpers/config_helper.rb @@ -2,7 +2,7 @@ module ConfigHelper      def site_name          MySociety::Config.get('SITE_NAME', 'Alaveteli')      end -     +      def force_registration_on_new_request          MySociety::Config.get('FORCE_REGISTRATION_ON_NEW_REQUEST', false)      end diff --git a/app/helpers/link_to_helper.rb b/app/helpers/link_to_helper.rb index 56c33e512..f621721b6 100755 --- a/app/helpers/link_to_helper.rb +++ b/app/helpers/link_to_helper.rb @@ -1,6 +1,6 @@  # app/helpers/link_to_helper.rb:  # This module is included into all controllers via controllers/application.rb -# -  +# -  #  # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved.  # Email: francis@mysociety.org; WWW: http://www.mysociety.org/ @@ -10,25 +10,29 @@  module LinkToHelper      # Links to various models -     +      # Requests      def request_url(info_request, extra_params={})          params = {:url_title => info_request.url_title, :only_path => true}          return show_request_url(params.merge(extra_params))      end -     -    def request_link(info_request) -        link_to h(info_request.title), request_url(info_request) + +    def request_link(info_request, cls=nil ) +        link_to h(info_request.title), request_url(info_request), :class => cls      end -     +      def request_admin_url(info_request)          return admin_url('request/show/' + info_request.id.to_s)      end -     + +    def request_admin_link(info_request, name="admin", cls=nil) +      link_to name, request_admin_url(info_request), :class => cls +    end +      def request_both_links(info_request)          link_to(h(info_request.title), main_url(request_url(info_request))) + " (" + link_to("admin", request_admin_url(info_request)) + ")"      end -     +      def request_similar_url(info_request)          return similar_request_url(:url_title => info_request.url_title, :only_path => true)      end @@ -58,7 +62,7 @@ module LinkToHelper          end          return respond_url      end -   +      # Public bodies      def public_body_url(public_body)          public_body.url_name.nil? ? '' : show_public_body_url(:url_name => public_body.url_name, :only_path => true) @@ -66,8 +70,8 @@ module LinkToHelper      def public_body_link_short(public_body)          link_to h(public_body.short_or_long_name), public_body_url(public_body)      end -    def public_body_link(public_body) -        link_to h(public_body.name), public_body_url(public_body) +    def public_body_link(public_body, cls=nil) +        link_to h(public_body.name), public_body_url(public_body), :class => cls      end      def public_body_link_absolute(public_body) # e.g. for in RSS          link_to h(public_body.name), main_url(public_body_url(public_body)) @@ -79,15 +83,15 @@ module LinkToHelper          link_to(h(public_body.name), main_url(public_body_url(public_body))) + " (" + link_to("admin", public_body_admin_url(public_body)) + ")"      end      def list_public_bodies_default -        list_public_bodies_url(:tag => 'all')  +        list_public_bodies_url(:tag => 'all')      end      # Users      def user_url(user)          return show_user_url(:url_name => user.url_name, :only_path => true)      end -    def user_link(user) -        link_to h(user.name), user_url(user) +    def user_link(user, cls=nil) +        link_to h(user.name), user_url(user), :class => cls      end      def user_link_absolute(user)          link_to h(user.name), main_url(user_url(user)) @@ -112,6 +116,9 @@ module LinkToHelper      def user_admin_url(user)          return admin_url('user/show/' + user.id.to_s)      end +    def user_admin_link(user, name="admin", cls=nil) +      link_to name, user_admin_url(user), :class => cls +    end      def user_both_links(user)          link_to(h(user.name), main_url(user_url(user))) + " (" + link_to("admin", user_admin_url(user)) + ")"      end @@ -120,15 +127,15 @@ module LinkToHelper      def do_track_url(track_thing, feed = 'track')          if track_thing.track_type == 'request_updates'              track_request_url(:url_title => track_thing.info_request.url_title, :feed => feed) -        elsif track_thing.track_type == 'all_new_requests'  +        elsif track_thing.track_type == 'all_new_requests'              track_list_url(:view => 'recent', :feed => feed) -        elsif track_thing.track_type == 'all_successful_requests'  +        elsif track_thing.track_type == 'all_successful_requests'              track_list_url(:view => 'successful', :feed => feed) -        elsif track_thing.track_type == 'public_body_updates'  +        elsif track_thing.track_type == 'public_body_updates'              track_public_body_url(:url_name => track_thing.public_body.url_name, :feed => feed) -        elsif track_thing.track_type == 'user_updates'  +        elsif track_thing.track_type == 'user_updates'              track_user_url(:url_name => track_thing.tracked_user.url_name, :feed => feed) -        elsif track_thing.track_type == 'search_query'  +        elsif track_thing.track_type == 'search_query'              track_search_url(:query_array => track_thing.track_query, :feed => feed)          else              raise "unknown tracking type " + track_thing.track_type @@ -141,7 +148,7 @@ module LinkToHelper              query = query - ["", nil]              query = query.join("/")          end -        routing_info = {:controller => 'general',  +        routing_info = {:controller => 'general',                          :action => 'search',                          :combined => query,                          :view => nil} @@ -204,7 +211,9 @@ module LinkToHelper      # Basic date format      def simple_date(date) -        return I18n.l(date, :format => "%e %B %Y") +        date_format = _("simple_date_format") +        date_format = :long if date_format == "simple_date_format" +        return I18n.l(date.to_date, :format => date_format)      end      def simple_time(date) diff --git a/app/models/about_me_validator.rb b/app/models/about_me_validator.rb index e24c5512c..67b81bc9c 100644 --- a/app/models/about_me_validator.rb +++ b/app/models/about_me_validator.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 95 +# Schema version: 114  #  # Table name: about_me_validators  # diff --git a/app/models/application_mailer.rb b/app/models/application_mailer.rb index e9f82a2c3..80f0d7289 100644 --- a/app/models/application_mailer.rb +++ b/app/models/application_mailer.rb @@ -28,7 +28,7 @@ class ApplicationMailer < ActionMailer::Base      # views (for links) and mailers (for use in emails), so include them into      # all of all.      include LinkToHelper -     +      # Site-wide access to configuration settings      include ConfigHelper  end diff --git a/app/models/censor_rule.rb b/app/models/censor_rule.rb index 201e60746..a477d2568 100644 --- a/app/models/censor_rule.rb +++ b/app/models/censor_rule.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: censor_rules  # @@ -51,7 +51,10 @@ class CensorRule < ActiveRecord::Base              errors.add("Censor must apply to an info request a user or a body; ")          end      end -end - - +  def for_admin_column +    self.class.content_columns.each do |column| +      yield(column.human_name, self.send(column.name), column.type.to_s, column.name) +    end +  end +end diff --git a/app/models/change_email_validator.rb b/app/models/change_email_validator.rb index e3f8fa892..0395ab6d5 100644 --- a/app/models/change_email_validator.rb +++ b/app/models/change_email_validator.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: change_email_validators  # @@ -30,7 +30,7 @@ class ChangeEmailValidator < ActiveRecord::BaseWithoutTable      validates_presence_of :old_email, :message => N_("Please enter your old email address")      validates_presence_of :new_email, :message => N_("Please enter your new email address")      validates_presence_of :password, :message => N_("Please enter your password"), :unless => :changing_email -     +      def changing_email()        self.user_circumstance == 'change_email'      end diff --git a/app/models/comment.rb b/app/models/comment.rb index 44a1079cd..6edfaa24f 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: comments  # @@ -84,7 +84,9 @@ class Comment < ActiveRecord::Base              return Comment.find(:first, :conditions => [ "info_request_id = ? and body = ?", info_request_id, body ])          end      end - +  def for_admin_column +    self.class.content_columns.each do |column| +      yield(column.human_name, self.send(column.name), column.type.to_s, column.name) +    end +  end  end - - diff --git a/app/models/contact_mailer.rb b/app/models/contact_mailer.rb index 0390fc347..74c213c7a 100644 --- a/app/models/contact_mailer.rb +++ b/app/models/contact_mailer.rb @@ -25,7 +25,7 @@ class ContactMailer < ApplicationMailer      # they shouldn't, and this might help. (Have had mysterious cases of a      # reply coming in duplicate from a public body to both From and envelope      # from) -  +      # Send message to another user      def user_message(from_user, recipient_user, from_user_url, subject, message)          @from = from_user.name_and_email @@ -34,7 +34,7 @@ class ContactMailer < ApplicationMailer          headers 'Return-Path' => blackhole_email, 'Reply-To' => @from          @recipients = recipient_user.name_and_email          @subject = subject -        @body = {  +        @body = {              :message => message,              :from_user => from_user,              :recipient_user => recipient_user, diff --git a/app/models/contact_validator.rb b/app/models/contact_validator.rb index 0bc562835..a9748a739 100644 --- a/app/models/contact_validator.rb +++ b/app/models/contact_validator.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 95 +# Schema version: 114  #  # Table name: contact_validators  # diff --git a/app/models/exim_log.rb b/app/models/exim_log.rb index 77e5e2d21..60faa7f0b 100644 --- a/app/models/exim_log.rb +++ b/app/models/exim_log.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: exim_logs  # @@ -40,7 +40,7 @@ class EximLog < ActiveRecord::Base          ActiveRecord::Base.transaction do              # see if we already have it -            done = EximLogDone.find_by_filename(file_name_db)   +            done = EximLogDone.find_by_filename(file_name_db)              if !done.nil?                  if modified.utc == done.last_stat.utc                      # already have that, nothing to do @@ -124,7 +124,7 @@ class EximLog < ActiveRecord::Base          return ok      end -     +  end diff --git a/app/models/exim_log_done.rb b/app/models/exim_log_done.rb index b8a39033a..3cedc1379 100644 --- a/app/models/exim_log_done.rb +++ b/app/models/exim_log_done.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 95 +# Schema version: 114  #  # Table name: exim_log_dones  # diff --git a/app/models/foi_attachment.rb b/app/models/foi_attachment.rb index da92d1c2d..f3e3d7e00 100644 --- a/app/models/foi_attachment.rb +++ b/app/models/foi_attachment.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: foi_attachments  # @@ -177,7 +177,7 @@ class FoiAttachment < ActiveRecord::Base          filename = filename.gsub(/\//, "-")          return filename -    end  +    end      # XXX changing this will break existing URLs, so have a care - maybe      # make another old_display_filename see above @@ -248,16 +248,16 @@ class FoiAttachment < ActiveRecord::Base          return !! {              "application/pdf" => true, # .pdf              "image/tiff" => true, # .tiff -             +              "application/vnd.ms-word" => true, # .doc              "application/vnd.openxmlformats-officedocument.wordprocessingml.document" => true, # .docx -             +              "application/vnd.ms-powerpoint" => true, # .ppt              "application/vnd.openxmlformats-officedocument.presentationml.presentation" => true, # .pptx -             +              "application/vnd.ms-excel" => true, # .xls              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" => true, # .xlsx -             +          } [self.content_type]      end @@ -277,16 +277,16 @@ class FoiAttachment < ActiveRecord::Base          return {              "text/plain" => "Text file",              'application/rtf' => "RTF file", -             +              'application/pdf' => "PDF file",              'image/tiff' => "TIFF image", -             +              'application/vnd.ms-word' => "Word document",              'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => "Word document", -             +              'application/vnd.ms-powerpoint' => "PowerPoint presentation",              'application/vnd.openxmlformats-officedocument.presentationml.presentation' => "PowerPoint presentation", -             +              'application/vnd.ms-excel' => "Excel spreadsheet",              'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => "Excel spreadsheet",          }[self.content_type] @@ -345,7 +345,7 @@ class FoiAttachment < ActiveRecord::Base              if self.has_google_docs_viewer?                  wrapper_id = "wrapper_google_embed"                  ret = ret + "<iframe src='http://docs.google.com/viewer?url=<attachment-url-here>&embedded=true' width='100%' height='100%' style='border: none;'></iframe>"; -            else  +            else                  ret = ret + "<p>Sorry, we were unable to convert this file to HTML. Please use the download link at the top right.</p>"              end              ret = ret + "</body></html>" diff --git a/app/models/holiday.rb b/app/models/holiday.rb index 60b5ff443..debd88dec 100644 --- a/app/models/holiday.rb +++ b/app/models/holiday.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: holidays  # @@ -10,7 +10,7 @@  # models/holiday.rb:  # -# Store details on, and perform calculations with, public holidays on which  +# Store details on, and perform calculations with, public holidays on which  # the clock for answering FOI requests does not run:  #  #   ... "working day" means any day other than a Saturday, a Sunday, Christmas @@ -37,7 +37,7 @@ class Holiday < ActiveRecord::Base          # Count forward (20) working days. We start with today as "day zero". The          # first of the twenty full working days is the next day. We return the          # date of the last of the twenty. -         +          # This response for example of a public authority complains that we had          # it wrong.  We didn't (even thought I changed the code for a while,          # it's changed back now). A day is a day, our lawyer tells us. diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb index 5f8c2ee15..3419956d6 100644 --- a/app/models/incoming_message.rb +++ b/app/models/incoming_message.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: incoming_messages  # @@ -11,12 +11,12 @@  #  cached_attachment_text_clipped :text  #  cached_main_body_text_folded   :text  #  cached_main_body_text_unfolded :text -#  sent_at                        :time  #  subject                        :text  #  mail_from_domain               :text  #  valid_to_reply_to              :boolean  #  last_parsed                    :datetime  #  mail_from                      :text +#  sent_at                        :datetime  #  # encoding: UTF-8 @@ -81,7 +81,7 @@ class IncomingMessage < ActiveRecord::Base              # http://www.whatdotheyknow.com/request/reviews_of_unduly_lenient_senten#incoming-4830              # Report of TMail bug:              # http://rubyforge.org/tracker/index.php?func=detail&aid=21810&group_id=4512&atid=17370 -            copy_of_raw_data = self.raw_email.data.gsub(/; boundary=\s+"/ims,'; boundary="')  +            copy_of_raw_data = self.raw_email.data.gsub(/; boundary=\s+"/ims,'; boundary="')              @mail = TMail::Mail.parse(copy_of_raw_data)              @mail.base64_decode @@ -92,7 +92,7 @@ class IncomingMessage < ActiveRecord::Base      # Returns the name of the person the incoming message is from, or nil if      # there isn't one or if there is only an email address. XXX can probably      # remove from_name_if_present (which is a monkey patch) by just calling -    # .from_addrs[0].name here instead?  +    # .from_addrs[0].name here instead?      # Return false if for some reason this is a message that we shouldn't let them reply to      def _calculate_valid_to_reply_to @@ -178,7 +178,7 @@ class IncomingMessage < ActiveRecord::Base      def safe_mail_from          if !self.mail_from.nil?              mail_from = self.mail_from.dup -            self.info_request.apply_censor_rules_to_text!(mail_from)             +            self.info_request.apply_censor_rules_to_text!(mail_from)              return mail_from          end      end @@ -191,7 +191,7 @@ class IncomingMessage < ActiveRecord::Base      # XXX This fills in part.rfc822_attachment and part.url_part_number within      # all the parts of the email (see TMail monkeypatch above for how these      # attributes are added). ensure_parts_counted must be called before using -    # the attributes.  +    # the attributes.      def ensure_parts_counted          @count_parts_count = 0          _count_parts_recursive(self.mail) @@ -215,7 +215,7 @@ class IncomingMessage < ActiveRecord::Base                      # e.g. http://www.whatdotheyknow.com/request/chinese_names_for_british_politi                      msg = Mapi::Msg.open(StringIO.new(part.body))                      part.rfc822_attachment = TMail::Mail.parse(msg.to_mime.to_s) -                elsif part.content_type == 'application/ms-tnef'  +                elsif part.content_type == 'application/ms-tnef'                      # A set of attachments in a TNEF file                      part.rfc822_attachment = TNEF.as_tmail(part.body)                  end @@ -289,7 +289,7 @@ class IncomingMessage < ActiveRecord::Base                          # buggy versions of pdftk sometimes fail on                          # compression, I don't see it's a disaster in                          # these cases to save an uncompressed version? -                        recompressed_text = censored_uncompressed_text                         +                        recompressed_text = censored_uncompressed_text                          logger.warn "Unable to compress PDF; problem with your pdftk version?"                      end                      if !recompressed_text.nil? && !recompressed_text.empty? @@ -297,10 +297,10 @@ class IncomingMessage < ActiveRecord::Base                      end                  end              end -            return  +            return          end -        self._binary_mask_stuff_internal!(text)  +        self._binary_mask_stuff_internal!(text)      end      # Used by binary_mask_stuff - replace text in place @@ -309,7 +309,7 @@ class IncomingMessage < ActiveRecord::Base          orig_size = text.size          # Replace ASCII email addresses... -        text.gsub!(MySociety::Validate.email_find_regexp) do |email|  +        text.gsub!(MySociety::Validate.email_find_regexp) do |email|              email.gsub(/[^@.]/, 'x')          end @@ -320,7 +320,7 @@ class IncomingMessage < ActiveRecord::Base          emails = ascii_chars.scan(MySociety::Validate.email_find_regexp)          # Convert back to UCS-2, making a mask at the same time          emails.map! {|email| [ -                Iconv.conv('ucs-2le', 'ascii', email[0]),  +                Iconv.conv('ucs-2le', 'ascii', email[0]),                  Iconv.conv('ucs-2le', 'ascii', email[0].gsub(/[^@.]/, 'x'))          ] }          # Now search and replace the UCS-2 email with the UCS-2 mask @@ -416,7 +416,7 @@ class IncomingMessage < ActiveRecord::Base          # http://www.whatdotheyknow.com/request/secured_convictions_aided_by_cct          multiline_original_message = '(' + '''>>>.* \d\d/\d\d/\d\d\d\d\s+\d\d:\d\d(?::\d\d)?\s*>>>''' + ')'          text.gsub!(/^(#{multiline_original_message}\n.*)$/ms, replacement) -  +          # Single line sections          text.gsub!(/^(>.*\n)/, replacement)          text.gsub!(/^(On .+ (wrote|said):\n)/, replacement) @@ -453,8 +453,8 @@ class IncomingMessage < ActiveRecord::Base          # http://www.whatdotheyknow.com/request/123/response/192          # http://www.whatdotheyknow.com/request/235/response/513          # http://www.whatdotheyknow.com/request/445/response/743 -        original_message =  -            '(' + '''----* This is a copy of the message, including all the headers. ----*''' +  +        original_message = +            '(' + '''----* This is a copy of the message, including all the headers. ----*''' +              '|' + '''----*\s*Original Message\s*----*''' +              '|' + '''----*\s*Forwarded message.+----*''' +              '|' + '''----*\s*Forwarded by.+----*''' + @@ -482,7 +482,7 @@ class IncomingMessage < ActiveRecord::Base          return part_file_name      end -    # (This risks losing info if the unchosen alternative is the only one to contain  +    # (This risks losing info if the unchosen alternative is the only one to contain      # useful info, but let's worry about that another time)      def get_attachment_leaves          force = true @@ -538,7 +538,7 @@ class IncomingMessage < ActiveRecord::Base                  if calc_mime                      curr_mail.content_type = calc_mime                  end -            end  +            end              # Use standard content types for Word documents etc.              curr_mail.content_type = normalise_content_type(curr_mail.content_type) @@ -660,11 +660,11 @@ class IncomingMessage < ActiveRecord::Base              # Test if it's good UTF-8              text = Iconv.conv('utf-8', 'utf-8', text)          rescue Iconv::IllegalSequence -            # Text looks like unlabelled nonsense,  +            # Text looks like unlabelled nonsense,              # strip out anything that isn't UTF-8              begin -                text = Iconv.conv('utf-8//IGNORE', source_charset, text) +  -                    _("\n\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]",  +                text = Iconv.conv('utf-8//IGNORE', source_charset, text) + +                    _("\n\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]",                        :site_name => MySociety::Config.get('SITE_NAME', 'Alaveteli'))              rescue Iconv::InvalidEncoding, Iconv::IllegalSequence                  if source_charset != "utf-8" @@ -673,7 +673,6 @@ class IncomingMessage < ActiveRecord::Base                  end              end          end -                  # Fix DOS style linefeeds to Unix style ones (or other later regexps won't work)          # Needed for e.g. http://www.whatdotheyknow.com/request/60/response/98 @@ -693,7 +692,7 @@ class IncomingMessage < ActiveRecord::Base          # Find first part which is text/plain or text/html          # (We have to include HTML, as increasingly there are mail clients that          # include no text alternative for the main part, and we don't want to -        # instead use the first text attachment  +        # instead use the first text attachment          # e.g. http://www.whatdotheyknow.com/request/list_of_public_authorties)          leaves.each do |p|              if p.content_type == 'text/plain' or p.content_type == 'text/html' @@ -707,8 +706,8 @@ class IncomingMessage < ActiveRecord::Base                  return p              end          end -  -        # ... or if none, consider first part  + +        # ... or if none, consider first part          p = leaves[0]          # if it is a known type then don't use it, return no body (nil)          if !p.nil? && AlaveteliFileTypes.mimetype_to_extension(p.content_type) @@ -752,7 +751,7 @@ class IncomingMessage < ActiveRecord::Base                                           :display_size => "0K")              attachment.save!              attachments << attachment -        end     +        end          return attachments      end @@ -802,7 +801,7 @@ class IncomingMessage < ActiveRecord::Base                      # XXX call _convert_part_body_to_text here, but need to get charset somehow                      # e.g. http://www.whatdotheyknow.com/request/1593/response/3088/attach/4/Freedom%20of%20Information%20request%20-%20car%20oval%20sticker:%20Article%2020,%20Convention%20on%20Road%20Traffic%201949.txt                      body = headers + "\n" + body -                     +                      # This is quick way of getting all headers, but instead we only add some a) to                      # make it more usable, b) as at least one authority accidentally leaked security                      # information into a header. @@ -816,7 +815,6 @@ class IncomingMessage < ActiveRecord::Base                                           :filename => _get_part_file_name(leaf),                                           :charset => leaf.charset,                                           :within_rfc822_subject => within_rfc822_subject, -                                         :display_size => "0K",                                           :body => body)              attachment.save!              attachments << attachment.id @@ -837,7 +835,7 @@ class IncomingMessage < ActiveRecord::Base          end          # now get rid of any attachments we no longer have -        FoiAttachment.destroy_all("id NOT IN (#{attachments.join(',')}) AND incoming_message_id = #{self.id}")         +        FoiAttachment.destroy_all("id NOT IN (#{attachments.join(',')}) AND incoming_message_id = #{self.id}")     end      # Returns body text as HTML with quotes flattened, and emails removed. @@ -899,13 +897,13 @@ class IncomingMessage < ActiveRecord::Base          self.remove_privacy_sensitive_things!(text)          # This can be useful for memory debugging          #STDOUT.puts 'xxx '+ MySociety::DebugHelpers::allocated_string_size_around_gc -         +          # Save clipped version for snippets          if self.cached_attachment_text_clipped.nil?              self.cached_attachment_text_clipped = text[0..MAX_ATTACHMENT_TEXT_CLIPPED]              self.save!          end -         +          return text      end      # Returns a version reduced to a sensible maximum size - this @@ -988,7 +986,7 @@ class IncomingMessage < ActiveRecord::Base          for entry in zip_file              if entry.file?                  filename = entry.to_s -                begin  +                begin                      body = entry.get_input_stream.read                  rescue                      # move to next attachment silently if there were problems @@ -1002,7 +1000,7 @@ class IncomingMessage < ActiveRecord::Base                  else                      content_type = 'application/octet-stream'                  end -             +                  text += _get_attachment_text_internal_one_file(content_type, body)              end          end @@ -1030,8 +1028,6 @@ class IncomingMessage < ActiveRecord::Base          return get_body_for_quoting + "\n\n" + get_attachment_text_clipped      end - -      # Has message arrived "recently"?      def recently_arrived          (Time.now - self.created_at) <= 3.days @@ -1052,7 +1048,7 @@ class IncomingMessage < ActiveRecord::Base          end      end -    # Search all info requests for  +    # Search all info requests for      def IncomingMessage.find_all_unknown_mime_types          for incoming_message in IncomingMessage.find(:all)              for attachment in incoming_message.get_attachments_for_display @@ -1118,7 +1114,7 @@ class IncomingMessage < ActiveRecord::Base              content_type = 'application/vnd.ms-excel'          end          if content_type == 'application/mspowerpoint' or content_type == 'application/x-ms-powerpoint' -            content_type = 'application/vnd.ms-powerpoint'  +            content_type = 'application/vnd.ms-powerpoint'          end          if content_type == 'application/msword' or content_type == 'application/x-ms-word'              content_type = 'application/vnd.ms-word' @@ -1134,8 +1130,16 @@ class IncomingMessage < ActiveRecord::Base          return content_type      end -    private :normalise_content_type + +  def for_admin_column +    self.class.content_columns.each do |column| +      yield(column.human_name, self.send(column.name), column.type.to_s, column.name) +    end +  end + +  private :normalise_content_type  end + diff --git a/app/models/info_request.rb b/app/models/info_request.rb index a0ea72320..726383ad7 100644 --- a/app/models/info_request.rb +++ b/app/models/info_request.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: info_requests  # @@ -17,12 +17,16 @@  #  allow_new_responses_from  :string(255)     default("anybody"), not null  #  handle_rejected_responses :string(255)     default("bounce"), not null  #  idhash                    :string(255)     not null +#  attention_requested       :boolean         default(FALSE)  #  require 'digest/sha1'  class InfoRequest < ActiveRecord::Base +    include ActionView::Helpers::UrlHelper +    include ActionController::UrlWriter +      strip_attributes!      validates_presence_of :title, :message => N_("Please enter a summary of your request") @@ -48,14 +52,14 @@ class InfoRequest < ActiveRecord::Base      # user described state (also update in info_request_event, admin_request/edit.rhtml)      validate :must_be_valid_state -    validates_inclusion_of :prominence, :in => [  -        'normal',  +    validates_inclusion_of :prominence, :in => [ +        'normal',          'backpage',          'hidden',          'requester_only'      ] -    validates_inclusion_of :law_used, :in => [  +    validates_inclusion_of :law_used, :in => [          'foi', # Freedom of Information Act          'eir', # Environmental Information Regulations      ] @@ -74,18 +78,19 @@ class InfoRequest < ActiveRecord::Base      ]      def self.enumerate_states -        states = [  +        states = [          'waiting_response', -        'waiting_clarification',  +        'waiting_clarification',          'gone_postal',          'not_held',          'rejected', # this is called 'refused' in UK FOI law and the user interface, but 'rejected' internally for historic reasons -        'successful',  +        'successful',          'partially_successful',          'internal_review',          'error_message',          'requires_admin', -        'user_withdrawn' +        'user_withdrawn', +        'attention_requested'          ]          if @@custom_states_loaded              states += InfoRequest.theme_extra_states @@ -94,7 +99,7 @@ class InfoRequest < ActiveRecord::Base      end      def must_be_valid_state -        errors.add(:described_state, "is not a valid state") if  +        errors.add(:described_state, "is not a valid state") if              !InfoRequest.enumerate_states.include? described_state      end @@ -120,7 +125,7 @@ class InfoRequest < ActiveRecord::Base              errors.add(:title, _('Please describe more what the request is about in the subject. There is no need to say it is an FOI request, we add that on anyway.'))          end      end -     +      OLD_AGE_IN_DAYS = 21.days      def after_initialize @@ -166,7 +171,7 @@ class InfoRequest < ActiveRecord::Base          end      end      # Force reindex when tag string changes -    alias_method :orig_tag_string=, :tag_string=  +    alias_method :orig_tag_string=, :tag_string=      def tag_string=(tag_string)          ret = self.orig_tag_string=(tag_string)          reindex_request_events @@ -219,7 +224,7 @@ public      end      # Email which public body should use to respond to request. This is in -    # the format PREFIXrequest-ID-HASH@DOMAIN. Here ID is the id of the  +    # the format PREFIXrequest-ID-HASH@DOMAIN. Here ID is the id of the      # FOI request, and HASH is a signature for that id.      def incoming_email          return self.magic_email("request-") @@ -251,7 +256,7 @@ public          end      end -    # Two sorts of laws for requests, FOI or EIR  +    # Two sorts of laws for requests, FOI or EIR      def law_used_full          if self.law_used == 'foi'              return _("Freedom of Information") @@ -306,7 +311,7 @@ public          guesses = []          # 1. Try to guess based on the email address(es)          addresses = -            (incoming_message.mail.to || []) +  +            (incoming_message.mail.to || []) +              (incoming_message.mail.cc || []) +              (incoming_message.mail.envelope_to || [])          addresses.uniq! @@ -453,7 +458,6 @@ public      # An annotation (comment) is made      def add_comment(body, user)          comment = Comment.new -          ActiveRecord::Base.transaction do              comment.body = body              comment.user = user @@ -501,7 +505,7 @@ public      # states which require administrator action (hence email administrators      # when they are entered, and offer state change dialog to them)      def InfoRequest.requires_admin_states -        return ['requires_admin', 'error_message'] +        return ['requires_admin', 'error_message', 'attention_requested']      end      def requires_admin? @@ -509,6 +513,9 @@ public          return false      end +    def can_have_attention_requested? +    end +      # change status, including for last event for later historical purposes      def set_described_state(new_state)          ActiveRecord::Base.transaction do @@ -539,7 +546,7 @@ public              self.base_calculate_status          end      end -      +      def base_calculate_status          return 'waiting_classification' if self.awaiting_description          return described_state unless self.described_state == "waiting_response" @@ -559,13 +566,13 @@ public          curr_state = nil          for event in self.info_request_events.reverse              event.xapian_mark_needs_index  # we need to reindex all events in order to update their latest_* terms -            if curr_state.nil?  +            if curr_state.nil?                  if !event.described_state.nil?                      curr_state = event.described_state                  end              end -            if !curr_state.nil? && event.event_type == 'response'  +            if !curr_state.nil? && event.event_type == 'response'                  if event.calculated_state != curr_state                      event.calculated_state = curr_state                      event.last_described_at = Time.now() @@ -579,7 +586,7 @@ public              elsif !curr_state.nil? && (event.event_type == 'followup_sent' || event.event_type == 'sent') && !event.described_state.nil? && (event.described_state == 'waiting_response' || event.described_state == 'internal_review')                  # Followups can set the status to waiting response / internal                  # review. Initial requests ('sent') set the status to waiting response. -                +                  # We want to store that in calculated_state state so it gets                  # indexed.                  if event.calculated_state != event.described_state @@ -727,8 +734,8 @@ public      def index_of_last_described_event          events = self.info_request_events          events.each_index do |i| -            revi = events.size - 1 - i  -            m = events[revi]  +            revi = events.size - 1 - i +            m = events[revi]              if not m.described_state.nil?                  return revi              end @@ -739,7 +746,7 @@ public      def last_event_id_needing_description          last_event = events_needing_description[-1]          last_event.nil? ? 0 : last_event.id -    end         +    end      # Returns all the events which the user hasn't described yet - an empty array if all described.      def events_needing_description @@ -805,6 +812,8 @@ public              _("Delivery error")          elsif status == 'requires_admin'              _("Unusual response.") +        elsif status == 'attention_requested' +            _("Reported for administrator attention.")          elsif status == 'user_withdrawn'              _("Withdrawn by the requester.")          else @@ -827,11 +836,11 @@ public              track_thing.destroy          end          self.user_info_request_sent_alerts.each { |a| a.destroy } -        self.info_request_events.each do |info_request_event|  +        self.info_request_events.each do |info_request_event|              info_request_event.track_things_sent_emails.each { |a| a.destroy }              info_request_event.destroy          end -        self.exim_logs.each do |exim_log|  +        self.exim_logs.each do |exim_log|              exim_log.destroy          end          self.outgoing_messages.each { |a| a.destroy } @@ -846,8 +855,8 @@ public          return InfoRequest.magic_email_for_id(prefix_part, self.id)      end -    def InfoRequest.magic_email_for_id(prefix_part, id)  -        magic_email = MySociety::Config.get("INCOMING_EMAIL_PREFIX", "")  +    def InfoRequest.magic_email_for_id(prefix_part, id) +        magic_email = MySociety::Config.get("INCOMING_EMAIL_PREFIX", "")          magic_email += prefix_part + id.to_s          magic_email += "-" + InfoRequest.hash_from_id(id)          magic_email += "@" + MySociety::Config.get("INCOMING_EMAIL_DOMAIN", "localhost") @@ -892,14 +901,14 @@ public      def InfoRequest.find_old_unclassified(extra_params={})          last_response_created_at = last_event_time_clause('response')          age = extra_params[:age_in_days] ? extra_params[:age_in_days].days : OLD_AGE_IN_DAYS -        params = {:select => "*, #{last_response_created_at} as last_response_time",  -                  :conditions => ["awaiting_description = ? and #{last_response_created_at} < ? and url_title != 'holding_pen'",  -                                 true, Time.now() - age],  +        params = {:select => "*, #{last_response_created_at} as last_response_time", +                  :conditions => ["awaiting_description = ? and #{last_response_created_at} < ? and url_title != 'holding_pen'", +                                 true, Time.now() - age],                                   :order => "last_response_time"}          params[:limit] = extra_params[:limit] if extra_params[:limit]          params[:include] = extra_params[:include] if extra_params[:include]          if extra_params[:order] -            params[:order] = extra_params[:order]  +            params[:order] = extra_params[:order]              params.delete(:select)          end          if extra_params[:conditions] @@ -909,7 +918,7 @@ public          end          find(:all, params)      end -     +      def is_old_unclassified?          return false if !awaiting_description          return false if url_title == 'holding_pen' @@ -928,7 +937,7 @@ public                  next              end              incoming_message.safe_mail_from -             +              email = OutgoingMailer.email_for_followup(self, incoming_message)              name = OutgoingMailer.name_for_followup(self, incoming_message) @@ -957,7 +966,7 @@ public              end          end      end -     +      def apply_censor_rules_to_binary!(binary)          for censor_rule in self.censor_rules              censor_rule.apply_to_binary!(binary) @@ -968,7 +977,7 @@ public              end          end      end -     +      def is_owning_user?(user)          !user.nil? && (user.id == user_id || user.owns_every_request?)      end @@ -977,10 +986,10 @@ public      end      def user_can_view?(user) -        if self.prominence == 'hidden'  +        if self.prominence == 'hidden'              return User.view_hidden_requests?(user)          end -        if self.prominence == 'requester_only'  +        if self.prominence == 'requester_only'              return self.is_owning_user?(user)          end          return true @@ -1021,7 +1030,7 @@ public      end      def json_for_api(deep) -        ret = {  +        ret = {              :id => self.id,              :url_title => self.url_title,              :title => self.title, @@ -1046,6 +1055,26 @@ public          end          return ret      end -end +    before_save :purge_in_cache +    def purge_in_cache +        if !MySociety::Config.get('VARNISH_HOST').nil? && !self.id.nil? +            # we only do this for existing info_requests (new ones have a nil id) +            path = url_for(:controller => 'request', :action => 'show', :url_title => self.url_title, :only_path => true, :locale => :none) +            req = PurgeRequest.find_by_url(path) +            if req.nil? +                req = PurgeRequest.new(:url => path, +                                       :model => self.class.base_class.to_s, +                                       :model_id => self.id) +            end +            req.save() +        end +    end + +    def for_admin_column +      self.class.content_columns.map{|c| c unless %w(title url_title).include?(c.name) }.compact.each do |column| +        yield(column.human_name, self.send(column.name), column.type.to_s, column.name) +      end +    end +end diff --git a/app/models/info_request_event.rb b/app/models/info_request_event.rb index 99f34cf9e..a410328b0 100644 --- a/app/models/info_request_event.rb +++ b/app/models/info_request_event.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: info_request_events  # @@ -36,51 +36,56 @@ class InfoRequestEvent < ActiveRecord::Base      has_many :track_things_sent_emails      validates_presence_of :event_type -    validates_inclusion_of :event_type, :in => [ -        'sent',  -        'resent',  -        'followup_sent',  -        'followup_resent',  - -        'edit', # title etc. edited (in admin interface) -        'edit_outgoing', # outgoing message edited (in admin interface) -        'edit_comment', # comment edited (in admin interface) -        'destroy_incoming', # deleted an incoming message (in admin interface) -        'destroy_outgoing', # deleted an outgoing message (in admin interface) -        'redeliver_incoming', # redelivered an incoming message elsewhere (in admin interface) -        'move_request', # changed user or public body (in admin interface) -        'manual', # you did something in the db by hand - -        'response', -        'comment',  -        'status_update'  -    ] + +    def self.enumerate_event_types +        [ +         'sent', +         'resent', +         'followup_sent', +         'followup_resent', +          +         'edit', # title etc. edited (in admin interface) +         'edit_outgoing', # outgoing message edited (in admin interface) +         'edit_comment', # comment edited (in admin interface) +         'destroy_incoming', # deleted an incoming message (in admin interface) +         'destroy_outgoing', # deleted an outgoing message (in admin interface) +         'redeliver_incoming', # redelivered an incoming message elsewhere (in admin interface) +         'move_request', # changed user or public body (in admin interface) +         'manual', # you did something in the db by hand +          +         'response', +         'comment', +         'status_update' +        ] +    end + +    validates_inclusion_of :event_type, :in => enumerate_event_types      # user described state (also update in info_request)      validate :must_be_valid_state      # whether event is publicly visible -    validates_inclusion_of :prominence, :in => [  -        'normal',  +    validates_inclusion_of :prominence, :in => [ +        'normal',          'hidden',          'requester_only'      ]      def must_be_valid_state          if !described_state.nil? and !InfoRequest.enumerate_states.include?(described_state) -            errors.add(described_state, "is not a valid state")  +            errors.add(described_state, "is not a valid state")          end      end -     +      def user_can_view?(user)          if !self.info_request.user_can_view?(user)              raise "internal error, called user_can_view? on event when there is not permission to view entire request"          end -        if self.prominence == 'hidden'  +        if self.prominence == 'hidden'              return User.view_hidden_requests?(user)          end -        if self.prominence == 'requester_only'  +        if self.prominence == 'requester_only'              return self.info_request.is_owning_user?(user)          end          return true @@ -89,7 +94,7 @@ class InfoRequestEvent < ActiveRecord::Base      # Full text search indexing      acts_as_xapian :texts => [ :search_text_main, :title ], -        :values => [  +        :values => [                       [ :created_at, 0, "range_search", :date ], # for QueryParser range searches e.g. 01/01/2008..14/01/2008                       [ :created_at_numeric, 1, "created_at", :number ], # for sorting                       [ :described_at_numeric, 2, "described_at", :number ], # XXX using :number for lack of :datetime support in Xapian values @@ -106,7 +111,7 @@ class InfoRequestEvent < ActiveRecord::Base                  [ :latest_status, 'L', "latest_status" ],                  [ :waiting_classification, 'W', "waiting_classification" ],                  [ :filetype, 'T', "filetype" ], -                [ :tags, 'U', "tag" ]  +                [ :tags, 'U', "tag" ]          ],          :if => :indexed_by_search?,          :eager_load => [ :outgoing_message, :comment, { :info_request => [ :user, :public_body, :censor_rules ] } ] @@ -115,7 +120,7 @@ class InfoRequestEvent < ActiveRecord::Base          self.info_request.user.url_name      end      def requested_from -        # acts_as_xapian will detect translated fields via Globalize and add all the  +        # acts_as_xapian will detect translated fields via Globalize and add all the          # available locales to the index. But 'requested_from' is not translated directly,          # although it relies on a translated field in PublicBody. Hence, we need to          # manually add all the localized values to the index (Xapian can handle a list @@ -170,15 +175,15 @@ class InfoRequestEvent < ActiveRecord::Base      end      def described_at_numeric          # format it here as no datetime support in Xapian's value ranges -        return self.described_at.strftime("%Y%m%d%H%M%S")  +        return self.described_at.strftime("%Y%m%d%H%M%S")      end      def created_at_numeric          # format it here as no datetime support in Xapian's value ranges -        return self.created_at.strftime("%Y%m%d%H%M%S")  +        return self.created_at.strftime("%Y%m%d%H%M%S")      end -     +      def incoming_message_selective_columns(fields) -        message = IncomingMessage.find(:all,  +        message = IncomingMessage.find(:all,                                         :select => fields + ", incoming_messages.info_request_id",                                         :joins => "INNER JOIN info_request_events ON incoming_messages.id = incoming_message_id ",                                         :conditions => "info_request_events.id = #{self.id}" @@ -214,7 +219,7 @@ class InfoRequestEvent < ActiveRecord::Base      # performance reasons. Xapian will take the full text.      def search_text_main(clipped = false)          text = '' -        if self.event_type == 'sent'  +        if self.event_type == 'sent'              text = text + self.outgoing_message.get_text_for_indexing + "\n\n"          elsif self.event_type == 'followup_sent'              text = text + self.outgoing_message.get_text_for_indexing + "\n\n" @@ -232,7 +237,7 @@ class InfoRequestEvent < ActiveRecord::Base          return text      end      def title -        if self.event_type == 'sent'  +        if self.event_type == 'sent'              return self.info_request.title          end          return '' @@ -313,26 +318,26 @@ class InfoRequestEvent < ActiveRecord::Base              old_value = old_params[key].to_s              new_value = new_params[key].to_s              if old_value != new_value -                ret = ret + "<em>" + CGI.escapeHTML(key) + ":</em> "  -                ret = ret +  -                      CGI.escapeHTML(MySociety::Format.wrap_email_body_by_lines(old_value).strip).gsub(/\n/, '<br>') +  -                        " => " +  +                ret = ret + "<em>" + CGI.escapeHTML(key) + ":</em> " +                ret = ret + +                      CGI.escapeHTML(MySociety::Format.wrap_email_body_by_lines(old_value).strip).gsub(/\n/, '<br>') + +                        " => " +                        CGI.escapeHTML(MySociety::Format.wrap_email_body_by_lines(new_value).strip).gsub(/\n/, '<br>')                  ret = ret + "<br>"              end          end          for key, value in other_params -            ret = ret + "<em>" + CGI.escapeHTML(key.to_s) + ":</em> "  -            ret = ret + CGI.escapeHTML(value.to_s.strip)  +            ret = ret + "<em>" + CGI.escapeHTML(key.to_s) + ":</em> " +            ret = ret + CGI.escapeHTML(value.to_s.strip)              ret = ret + "<br>"          end          return ret      end -  -    def is_incoming_message?()  not self.incoming_message_selective_columns("incoming_messages.id").nil?  end  -    def is_outgoing_message?()  not self.outgoing_message.nil?  end  -    def is_comment?()           not self.comment.nil?           end  + +    def is_incoming_message?()  not self.incoming_message_selective_columns("incoming_messages.id").nil?  end +    def is_outgoing_message?()  not self.outgoing_message.nil?  end +    def is_comment?()           not self.comment.nil?           end      # Display version of status      def display_status @@ -402,7 +407,7 @@ class InfoRequestEvent < ActiveRecord::Base      end      def json_for_api(deep, snippet_highlight_proc = nil) -        ret = {  +        ret = {              :id => self.id,              :event_type => self.event_type,              # params_yaml has possibly sensitive data in it, don't include it @@ -427,7 +432,7 @@ class InfoRequestEvent < ActiveRecord::Base              ret[:snippet] = snippet_highlight_proc.call(self.search_text_main(true))          end -        if deep  +        if deep              ret[:info_request] = self.info_request.json_for_api(false)              ret[:public_body] = self.info_request.public_body.json_for_api              ret[:user] = self.info_request.user.json_for_api @@ -436,7 +441,9 @@ class InfoRequestEvent < ActiveRecord::Base          return ret      end - +  def for_admin_column +    self.class.content_columns.each do |column| +      yield(column.human_name, self.send(column.name), column.type.to_s, column.name) +    end +  end  end - - diff --git a/app/models/outgoing_mailer.rb b/app/models/outgoing_mailer.rb index bf81bb89f..8562c5b68 100644 --- a/app/models/outgoing_mailer.rb +++ b/app/models/outgoing_mailer.rb @@ -10,12 +10,12 @@  # separated) paragraphs, as is the convention for all the other mailers. This  # turned out to fit better with user exepectations when formatting messages.  # -# XXX The other mail templates are written to use blank line separated  +# XXX The other mail templates are written to use blank line separated  # paragraphs. They could be rewritten, and the wrapping method made uniform  # throughout the application.  class OutgoingMailer < ApplicationMailer -     +      # Email to public body requesting info      def initial_request(info_request, outgoing_message)          @wrap_lines_as_paragraphs = true @@ -96,4 +96,4 @@ class OutgoingMailer < ApplicationMailer      end  end -  + diff --git a/app/models/outgoing_message.rb b/app/models/outgoing_message.rb index cc561b21d..0ce1ee11c 100644 --- a/app/models/outgoing_message.rb +++ b/app/models/outgoing_message.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: outgoing_messages  # @@ -37,7 +37,7 @@ class OutgoingMessage < ActiveRecord::Base      # can have many events, for items which were resent by site admin e.g. if      # contact address changed -    has_many :info_request_events  +    has_many :info_request_events      # To override the default letter      attr_accessor :default_letter @@ -45,7 +45,7 @@ class OutgoingMessage < ActiveRecord::Base      # reindex if body text is edited (e.g. by admin interface)      after_update :xapian_reindex_after_update      def xapian_reindex_after_update -        if self.changes.include?('body')  +        if self.changes.include?('body')              for info_request_event in self.info_request_events                  info_request_event.xapian_mark_needs_index              end @@ -83,11 +83,11 @@ class OutgoingMessage < ActiveRecord::Base              "\n\n" +              "I am writing to request an internal review of " +              self.info_request.public_body.name + -            "'s handling of my FOI request " +  -            "'" + self.info_request.title + "'." +  +            "'s handling of my FOI request " + +            "'" + self.info_request.title + "'." +              "\n\n\n\n [ " + self.get_internal_review_insert_here_note + " ] \n\n\n\n" +              "A full history of my FOI request and all correspondence is available on the Internet at this address:\n" + -            "http://" + MySociety::Config.get("DOMAIN", '127.0.0.1:3000') + "/request/" + self.info_request.url_title  +            "http://" + MySociety::Config.get("DOMAIN", '127.0.0.1:3000') + "/request/" + self.info_request.url_title          else              ""          end @@ -98,7 +98,7 @@ class OutgoingMessage < ActiveRecord::Base      def set_signature_name(name)          # XXX We use raw_body here to get unstripped one          if self.raw_body == self.get_default_message -            self.body = self.raw_body + name  +            self.body = self.raw_body + name          end      end @@ -130,7 +130,7 @@ class OutgoingMessage < ActiveRecord::Base      def contains_postcode?          MySociety::Validate.contains_postcode?(self.body)      end -  +      # Set default letter      def after_initialize          if self.body.nil? @@ -267,7 +267,16 @@ class OutgoingMessage < ActiveRecord::Base          end      end +    after_save(:purge_in_cache) +    def purge_in_cache +        self.info_request.purge_in_cache +    end +    def for_admin_column +        self.class.content_columns.each do |column| +            yield(column.human_name, self.send(column.name), column.type.to_s, column.name) +        end +    end  end diff --git a/app/models/post_redirect.rb b/app/models/post_redirect.rb index 59cc86799..f613fc58d 100644 --- a/app/models/post_redirect.rb +++ b/app/models/post_redirect.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: post_redirects  # @@ -39,7 +39,7 @@ class PostRedirect < ActiveRecord::Base          self.post_params_yaml = params.to_yaml      end      def post_params -        if self.post_params_yaml.nil?    +        if self.post_params_yaml.nil?              return {}          end          YAML.load(self.post_params_yaml) @@ -64,7 +64,7 @@ class PostRedirect < ActiveRecord::Base          MySociety::Util.generate_token      end -    # Make the token  +    # Make the token      def after_initialize          # The token is used to return you to what you are doing after the login form.          if not self.token diff --git a/app/models/profile_photo.rb b/app/models/profile_photo.rb index 798094d90..72bfe954f 100644 --- a/app/models/profile_photo.rb +++ b/app/models/profile_photo.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: profile_photos  # @@ -27,9 +27,9 @@ class ProfilePhoto < ActiveRecord::Base      belongs_to :user      # deliberately don't strip_attributes, so keeps raw photo properly -     +      attr_accessor :x, :y, :w, :h -     +      # convert binary data blob into ImageMagick image when assigned      attr_accessor :image      def after_initialize @@ -45,7 +45,7 @@ class ProfilePhoto < ActiveRecord::Base              self.image = nil              return          end -     +          self.image = image_list[0] # XXX perhaps take largest image or somesuch if there were multiple in the file?          self.convert_image      end @@ -68,7 +68,7 @@ class ProfilePhoto < ActiveRecord::Base          # draft images are before the user has cropped them          if !self.draft && (image.columns != WIDTH || image.rows != HEIGHT)              # do any exact cropping (taken from Jcrop interface) -            if self.w && self.h  +            if self.w && self.h                  image.crop!(self.x.to_i, self.y.to_i, self.w.to_i, self.h.to_i)              end              # do any further cropping @@ -98,7 +98,7 @@ class ProfilePhoto < ActiveRecord::Base          if self.image.format != 'PNG'              errors.add(:data, N_("Failed to convert image to a PNG"))          end -         +          if !self.draft && (self.image.columns != WIDTH || self.image.rows != HEIGHT)              errors.add(:data, N_("Failed to convert image to the correct size: at %{cols}x%{rows}, need %{width}x%{height}" % { :cols => self.image.columns, :rows => self.image.rows, :width => WIDTH, :height => HEIGHT }))          end diff --git a/app/models/public_body.rb b/app/models/public_body.rb index a18af8c69..267b5d60c 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 95 +# Schema version: 114  #  # Table name: public_bodies  # @@ -58,11 +58,11 @@ class PublicBody < ActiveRecord::Base          short_long_name = t.short_name if t.short_name and !t.short_name.empty?          t.url_name = MySociety::Format.simplify_url_part(short_long_name, 'body')      end -     +      def translated_versions          translations      end -     +      def translated_versions=(translation_attrs)          def skip?(attrs)              valueless = attrs.inject({}) { |h, (k, v)| h[k] = v if v != '' and k != 'locale'; h } # because we want to fall back to alternative translations where there are empty values @@ -86,7 +86,7 @@ class PublicBody < ActiveRecord::Base              end          end      end -     +      # Make sure publication_scheme gets the correct default value.      # (This would work automatically, were publication_scheme not a translated attribute)      def after_initialize @@ -96,7 +96,7 @@ class PublicBody < ActiveRecord::Base      # like find_by_url_name but also search historic url_name if none found      def self.find_by_url_name_with_historic(name)          locale = self.locale || I18n.locale -        PublicBody.with_locale(locale) do  +        PublicBody.with_locale(locale) do              found = PublicBody.find(:all,                                      :conditions => ["public_body_translations.url_name='#{name}'"],                                      :joins => :translations, @@ -189,18 +189,37 @@ class PublicBody < ActiveRecord::Base              text = text.gsub(/\n/, '<br>')              return text          end + +        def compare(previous = nil) +          if previous.nil? +            yield([]) +          else +            v = self +            changes = self.class.content_columns.inject([]) {|memo, c| +              unless %w(version last_edit_editor last_edit_comment updated_at).include?(c.name) +                from = previous.send(c.name) +                to = self.send(c.name) +                memo << { :name => c.human_name, :from => from, :to => to } if from != to +              end +              memo +            } +            changes.each do |change| +              yield(change) +            end +          end +        end      end      acts_as_xapian :texts => [ :name, :short_name, :notes ], -        :values => [  +        :values => [               [ :created_at_numeric, 1, "created_at", :number ] # for sorting          ],          :terms => [ [ :variety, 'V', "variety" ], -                [ :tag_array_for_search, 'U', "tag" ]  +                [ :tag_array_for_search, 'U', "tag" ]          ]      def created_at_numeric          # format it here as no datetime support in Xapian's value ranges -        return self.created_at.strftime("%Y%m%d%H%M%S")  +        return self.created_at.strftime("%Y%m%d%H%M%S")      end      def variety          return "authority" @@ -236,11 +255,11 @@ class PublicBody < ActiveRecord::Base      def update_url_name          self.url_name = MySociety::Format.simplify_url_part(self.short_or_long_name, 'body')      end -     +      # Return the short name if present, or else long name      def short_or_long_name          if self.short_name.nil? || self.short_name.empty?   # 'nil' can happen during construction -            self.name +            self.name.nil? ? "" : self.name          else              self.short_name          end @@ -253,7 +272,7 @@ class PublicBody < ActiveRecord::Base          first = true          for tag in self.tags              if PublicBodyCategories::get().by_tag().include?(tag.name) -                desc = PublicBodyCategories::get().singular_by_tag()[tag.name]  +                desc = PublicBodyCategories::get().singular_by_tag()[tag.name]                  if first                      # terrible that Ruby/Rails doesn't have an equivalent of ucfirst                      # (capitalize shockingly converts later characters to lowercase) @@ -270,7 +289,7 @@ class PublicBody < ActiveRecord::Base          if types.size > 0              ret = types[0, types.size - 1].join(", ")              if types.size > 1 -                ret = ret + " and "  +                ret = ret + " and "              end              ret = ret + types[-1]              return ret @@ -351,12 +370,12 @@ class PublicBody < ActiveRecord::Base                      for existing_body in bodies                          # Hide InternalAdminBody from import notes                          next if existing_body.id == PublicBody.internal_admin_body.id -                         +                          bodies_by_name[existing_body.name] = existing_body                          set_of_existing.add(existing_body.name)                      end                  end -                 +                  set_of_importing = Set.new()                  field_names = { 'name'=>1, 'request_email'=>2 }     # Default values in case no field list is given                  line = 0 @@ -372,7 +391,7 @@ class PublicBody < ActiveRecord::Base                      fields = {}                      field_names.each{|name, i| fields[name] = row[i]} -     +                      name = row[field_names['name']]                      email = row[field_names['request_email']]                      next if name.nil? @@ -384,7 +403,7 @@ class PublicBody < ActiveRecord::Base                          errors.push "error: line #{line.to_s}: invalid email '#{email}' for authority '#{name}'"                          next                      end -                     +                      field_list = ['name', 'short_name', 'request_email', 'notes', 'publication_scheme', 'home_page', 'tag_string']                      if public_body = bodies_by_name[name]   # Existing public body @@ -394,7 +413,7 @@ class PublicBody < ActiveRecord::Base                                  field_list.each do |field_name|                                      localized_field_name = (locale.to_s == I18n.default_locale.to_s) ? field_name : "#{field_name}.#{locale}"                                      localized_value = field_names[localized_field_name] && row[field_names[localized_field_name]] -                                     +                                      # Tags are a special case, as we support adding to the field, not just setting a new value                                      if localized_field_name == 'tag_string'                                          if localized_value.nil? @@ -402,11 +421,11 @@ class PublicBody < ActiveRecord::Base                                          else                                              if tag_behaviour == 'add'                                                  localized_value = "#{localized_value} #{tag}" unless tag.empty? -                                                localized_value = "#{localized_value} #{public_body.tag_string}"  +                                                localized_value = "#{localized_value} #{public_body.tag_string}"                                              end                                          end                                      end -                                     +                                      if !localized_value.nil? and public_body.send(field_name) != localized_value                                          changed[field_name] = "#{public_body.send(field_name)}: #{localized_value}"                                          public_body.send("#{field_name}=", localized_value) @@ -416,14 +435,14 @@ class PublicBody < ActiveRecord::Base                                  unless changed.empty?                                      notes.push "line #{line.to_s}: updating authority '#{name}' (locale: #{locale}):\n\t#{changed.to_json}"                                      public_body.last_edit_editor = editor -                                    public_body.last_edit_comment = 'Updated from spreadsheet'                             +                                    public_body.last_edit_comment = 'Updated from spreadsheet'                                      public_body.save!                                  end                              end                          end                      else # New public body                          public_body = PublicBody.new(:name=>"", :short_name=>"", :request_email=>"") -                        available_locales.each do |locale|                             +                        available_locales.each do |locale|                              PublicBody.with_locale(locale) do                                  changed = ActiveSupport::OrderedHash.new                                  field_list.each do |field_name| @@ -433,7 +452,7 @@ class PublicBody < ActiveRecord::Base                                      if localized_field_name == 'tag_string' and tag_behaviour == 'add'                                          localized_value = "#{localized_value} #{tag}" unless tag.empty?                                      end -                                 +                                      if !localized_value.nil? and public_body.send(field_name) != localized_value                                          changed[field_name] = localized_value                                          public_body.send("#{field_name}=", localized_value) @@ -444,7 +463,7 @@ class PublicBody < ActiveRecord::Base                                      notes.push "line #{line.to_s}: creating new authority '#{name}' (locale: #{locale}):\n\t#{changed.to_json}"                                      public_body.publication_scheme = public_body.publication_scheme || ""                                      public_body.last_edit_editor = editor -                                    public_body.last_edit_comment = 'Created from spreadsheet'                             +                                    public_body.last_edit_comment = 'Created from spreadsheet'                                      public_body.save!                                  end                              end @@ -454,7 +473,7 @@ class PublicBody < ActiveRecord::Base                      set_of_importing.add(name)                  end -                # Give an error listing ones that are to be deleted  +                # Give an error listing ones that are to be deleted                  deleted_ones = set_of_existing - set_of_importing                  if deleted_ones.size > 0                      notes.push "Notes: Some " + tag + " bodies are in database, but not in CSV file:\n    " + Array(deleted_ones).sort.join("\n    ") + "\nYou may want to delete them manually.\n" @@ -519,7 +538,7 @@ class PublicBody < ActiveRecord::Base      end      def has_notes? -        return self.notes != "" +        return !self.notes.nil? && self.notes != ""      end      def notes_as_html          self.notes @@ -530,7 +549,7 @@ class PublicBody < ActiveRecord::Base      end      def json_for_api -        return {  +        return {              :id => self.id,              :url_name => self.url_name,              :name => self.name, @@ -538,7 +557,7 @@ class PublicBody < ActiveRecord::Base              # :request_email  # we hide this behind a captcha, to stop people doing bulk requests easily              :created_at => self.created_at,              :updated_at => self.updated_at, -            # don't add the history as some edit comments contain sensitive information  +            # don't add the history as some edit comments contain sensitive information              # :version, :last_edit_editor, :last_edit_comment              :home_page => self.calculated_home_page,              :notes => self.notes, @@ -547,6 +566,17 @@ class PublicBody < ActiveRecord::Base          }      end +    after_save(:purge_in_cache) +    def purge_in_cache +        self.info_requests.each {|x| x.purge_in_cache} +    end + +    def for_admin_column +        self.class.content_columns.map{|c| c unless %w(name last_edit_comment).include?(c.name)}.compact.each do |column| +            yield(column.human_name, self.send(column.name), column.type.to_s, column.name) +        end +    end +  end diff --git a/app/models/purge_request.rb b/app/models/purge_request.rb new file mode 100644 index 000000000..48a16f9e6 --- /dev/null +++ b/app/models/purge_request.rb @@ -0,0 +1,52 @@ +# == Schema Information +# Schema version: 114 +# +# Table name: purge_requests +# +#  id         :integer         not null, primary key +#  url        :string(255) +#  created_at :datetime        not null +#  model      :string(255)     not null +#  model_id   :integer         not null +# + +# models/purge_request.rb: +# A queue of URLs to purge +# +# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. +# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# + +class PurgeRequest < ActiveRecord::Base +    def self.purge_all +        done_something = false +        for item in PurgeRequest.all() +            item.purge +            done_something = true +        end +        return done_something +    end + +    def self.purge_all_loop +        # Run purge_all in an endless loop, sleeping when there is nothing to do +        while true +            sleep_seconds = 1 +            while !purge_all +                sleep sleep_seconds +                sleep_seconds *= 2 +                sleep_seconds = 30 if sleep_seconds > 30 +            end +        end +    end + +    def purge +        config = MySociety::Config.load_default() +        varnish_url = config['VARNISH_HOST'] +        result = quietly_try_to_purge(varnish_url, self.url) +        self.delete() +    end +end + + + + diff --git a/app/models/raw_email.rb b/app/models/raw_email.rb index 3e12a6feb..1466e5d9c 100644 --- a/app/models/raw_email.rb +++ b/app/models/raw_email.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: raw_emails  # @@ -16,7 +16,7 @@  class RawEmail < ActiveRecord::Base      # deliberately don't strip_attributes, so keeps raw email properly -     +      has_one :incoming_message      # We keep the old data_text field (which is of type text) for backwards @@ -27,10 +27,10 @@ class RawEmail < ActiveRecord::Base      def directory          request_id = self.incoming_message.info_request.id.to_s          if ENV["RAILS_ENV"] == "test" -            return File.join(RAILS_ROOT, 'files/raw_email_test') +            return File.join(Rails.root, 'files/raw_email_test')          else              return File.join(MySociety::Config.get('RAW_EMAILS_LOCATION', -                                                   'files/raw_emails'),  +                                                   'files/raw_emails'),                               request_id[0..2], request_id)          end      end diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb index 77ea49237..8e6e65a26 100644 --- a/app/models/request_mailer.rb +++ b/app/models/request_mailer.rb @@ -9,7 +9,7 @@  require 'alaveteli_file_types'  class RequestMailer < ApplicationMailer -     +      # Used when an FOI officer uploads a response from their web browser - this is      # the "fake" email used to store in the same format in the database as if they @@ -38,9 +38,9 @@ class RequestMailer < ApplicationMailer          @subject = "Your response to an FOI request was not delivered"          attachment :content_type => 'message/rfc822', :body => raw_email_data,              :filename => "original.eml", :transfer_encoding => '7bit', :content_disposition => 'inline' -        @body = {  +        @body = {              :info_request => info_request, -            :contact_email => MySociety::Config.get("CONTACT_EMAIL", 'contact@localhost')      +            :contact_email => MySociety::Config.get("CONTACT_EMAIL", 'contact@localhost')          }      end @@ -48,7 +48,7 @@ class RequestMailer < ApplicationMailer      def requires_admin(info_request)          @from = info_request.user.name_and_email          @recipients = contact_from_name_and_email -        @subject = _("FOI response requires admin - ") + info_request.title +        @subject = _("FOI response requires admin ({{reason}}) - {{title}}", :reason => info_request.described_state, :title => info_request.title)          url = main_url(request_url(info_request))          admin_url = request_admin_url(info_request)          @body = {:info_request => info_request, :url => url, :admin_url => admin_url } @@ -142,7 +142,7 @@ class RequestMailer < ApplicationMailer      # Tell the requester that they need to clarify their request      def not_clarified_alert(info_request, incoming_message)          respond_url = show_response_url(:id => info_request.id, :incoming_message_id => incoming_message.id) -        respond_url = respond_url + "#followup"  +        respond_url = respond_url + "#followup"          post_redirect = PostRedirect.new(              :uri => respond_url, @@ -182,7 +182,7 @@ class RequestMailer < ApplicationMailer      # Class function, called by script/mailin with all incoming responses.      # [ This is a copy (Monkeypatch!) of function from action_mailer/base.rb,      # but which additionally passes the raw_email to the member function, as we -    # want to record it.  +    # want to record it.      #      # That is because we want to be sure we properly record the actual message      # received in its raw form - so any information won't be lost in a round @@ -215,7 +215,7 @@ class RequestMailer < ApplicationMailer          # Find which info requests the email is for          reply_info_requests = self.requests_matching_email(email)          # Nothing found, so save in holding pen -        if reply_info_requests.size == 0  +        if reply_info_requests.size == 0              reason = _("Could not identify the request from the email address")              request = InfoRequest.holding_pen_request              request.receive(email, raw_email, false, reason) @@ -225,7 +225,7 @@ class RequestMailer < ApplicationMailer          # Send the message to each request, to be archived with it          for reply_info_request in reply_info_requests              # If environment variable STOP_DUPLICATES is set, don't send message with same id again -            if ENV['STOP_DUPLICATES']  +            if ENV['STOP_DUPLICATES']                  if reply_info_request.already_received?(email, raw_email)                      raise "message " + email.message_id + " already received by request"                  end @@ -275,7 +275,7 @@ class RequestMailer < ApplicationMailer          end      end -    # Send email alerts for new responses which haven't been classified. By default,  +    # Send email alerts for new responses which haven't been classified. By default,      # it goes out 3 days after last update of event, then after 10, then after 24.      def self.alert_new_response_reminders          MySociety::Config.get("NEW_RESPONSE_REMINDER_AFTER_DAYS", [3, 10, 24]).each_with_index do |days, i| @@ -283,10 +283,10 @@ class RequestMailer < ApplicationMailer          end      end      def self.alert_new_response_reminders_internal(days_since, type_code) -        info_requests = InfoRequest.find_old_unclassified(:order => 'info_requests.id',  -                                                          :include => [:user],  +        info_requests = InfoRequest.find_old_unclassified(:order => 'info_requests.id', +                                                          :include => [:user],                                                            :age_in_days => days_since) -         +          for info_request in info_requests              alert_event_id = info_request.get_last_response_event_id              last_response_message = info_request.get_last_response @@ -302,7 +302,7 @@ class RequestMailer < ApplicationMailer                  store_sent.user = info_request.user                  store_sent.alert_type = type_code                  store_sent.info_request_event_id = alert_event_id -                # XXX uses same template for reminder 1 and reminder 2 right now.  +                # XXX uses same template for reminder 1 and reminder 2 right now.                  RequestMailer.deliver_new_response_reminder_alert(info_request, last_response_message)                  store_sent.save!              end @@ -340,11 +340,11 @@ class RequestMailer < ApplicationMailer      # Send email alert to request submitter for new comments on the request.      def self.alert_comment_on_request() -         +          # We only check comments made in the last month - this means if the          # cron jobs broke for more than a month events would be lost, but no          # matter. I suspect the performance gain will be needed (with an index on updated_at) -         +          # XXX the :order part info_request_events.created_at is a work around          # for a very old Rails bug which means eager loading does not respect          # association orders. @@ -352,7 +352,7 @@ class RequestMailer < ApplicationMailer          #   http://lists.rubyonrails.org/pipermail/rails-core/2006-July/001798.html          # That that patch has not been applied, despite bribes of beer, is          # typical of the lack of quality of Rails. -         +          info_requests = InfoRequest.find(:all,              :conditions => [                 "info_requests.id in ( diff --git a/app/models/track_mailer.rb b/app/models/track_mailer.rb index 0c053c4ad..f618fba49 100644 --- a/app/models/track_mailer.rb +++ b/app/models/track_mailer.rb @@ -48,7 +48,7 @@ class TrackMailer < ApplicationMailer          end          for user in users              next if !user.should_be_emailed? -             +              email_about_things = []              track_things = TrackThing.find(:all, :conditions => [ "tracking_user_id = ? and track_medium = ?", user.id, 'email_daily' ])              for track_thing in track_things @@ -56,7 +56,7 @@ class TrackMailer < ApplicationMailer                  #                  # We only use track_things_sent_emails records which are less than 14 days old.                  # In the search query loop below, we also only use items described in last 7 days. -                # An item described that recently definitely can't appear in track_things_sent_emails  +                # An item described that recently definitely can't appear in track_things_sent_emails                  # earlier, so this is safe (with a week long margin of error). If the alerts break                  # for a whole week, then they will miss some items. Tough.                  done_info_request_events = {} @@ -70,7 +70,7 @@ class TrackMailer < ApplicationMailer                  # Query for things in this track. We use described_at for the                  # ordering, so we catch anything new (before described), or                  # anything whose new status has been described. -                xapian_object = InfoRequest.full_search([InfoRequestEvent], track_thing.track_query, 'described_at', true, nil, 100, 1)  +                xapian_object = InfoRequest.full_search([InfoRequestEvent], track_thing.track_query, 'described_at', true, nil, 100, 1)                  # Go through looking for unalerted things                  alert_results = []                  for result in xapian_object.results @@ -86,7 +86,7 @@ class TrackMailer < ApplicationMailer                      alert_results.push(result)                  end                  # If there were more alerts for this track, then store them -                if alert_results.size > 0  +                if alert_results.size > 0                      email_about_things.push([track_thing, alert_results, xapian_object])                  end              end diff --git a/app/models/track_thing.rb b/app/models/track_thing.rb index 58d70ed86..f6a58189f 100644 --- a/app/models/track_thing.rb +++ b/app/models/track_thing.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: track_things  # @@ -35,17 +35,17 @@ class TrackThing < ActiveRecord::Base      has_many :track_things_sent_emails -    validates_inclusion_of :track_type, :in => [  -        'request_updates',  +    validates_inclusion_of :track_type, :in => [ +        'request_updates',          'all_new_requests',          'all_successful_requests', -        'public_body_updates',  +        'public_body_updates',          'user_updates',          'search_query'      ] -    validates_inclusion_of :track_medium, :in => [  -        'email_daily',  +    validates_inclusion_of :track_medium, :in => [ +        'email_daily',          'feed'      ] @@ -69,7 +69,7 @@ class TrackThing < ActiveRecord::Base      end      def track_query_description -        # XXX this is very brittle... we should probably ask users  +        # XXX this is very brittle... we should probably ask users          # simply to name their tracks when they make them?          original_text = parsed_text = self.track_query.gsub(/([()]|OR)/, "")          filters = parsed_text.scan /\b\S+:\S+\b/ @@ -101,14 +101,14 @@ class TrackThing < ActiveRecord::Base              end              if filter =~ /waiting/                  statuses << _("awaiting a response") -            end                 +            end          end          if filters.empty?              parsed_text = original_text          end          descriptions = []          if varieties.include? _("requests") -            descriptions << _("requests which are {{list_of_statuses}}", :list_of_statuses => Array(statuses).join(_(' or '))) +            descriptions << _("requests which are {{list_of_statuses}}", :list_of_statuses => Array(statuses).sort.join(_(' or ')))              varieties -= [_("requests")]          end          if descriptions.empty? and varieties.empty? @@ -116,7 +116,7 @@ class TrackThing < ActiveRecord::Base          end          descriptions += Array(varieties)          parsed_text = parsed_text.strip -        descriptions = descriptions.join(_(" or ")) +        descriptions = descriptions.sort.join(_(" or "))          if !parsed_text.empty?              descriptions += _("{{list_of_things}} matching text '{{search_query}}'", :list_of_things => "", :search_query => parsed_text)          end @@ -146,11 +146,15 @@ class TrackThing < ActiveRecord::Base          return track_thing      end -    def TrackThing.create_track_for_public_body(public_body) +    def TrackThing.create_track_for_public_body(public_body, event_type = nil)          track_thing = TrackThing.new          track_thing.track_type = 'public_body_updates'          track_thing.public_body = public_body -        track_thing.track_query = "requested_from:" + public_body.url_name +        query = "requested_from:" + public_body.url_name +        if InfoRequestEvent.enumerate_event_types.include?(event_type) +            query += " variety:" + event_type +        end +        track_thing.track_query = query          return track_thing      end @@ -171,10 +175,10 @@ class TrackThing < ActiveRecord::Base                  query += " variety:sent"              when "users"                  query += " variety:user" -            when "authorities" -                query += " variety:authority"                 +            when "bodies" +                query += " variety:authority"              end -        end             +        end          track_thing.track_query = query          # XXX should extract requested_by:, request:, requested_from:          # and stick their values into the respective relations. @@ -191,7 +195,7 @@ class TrackThing < ActiveRecord::Base              if self.track_type == 'request_updates'                  @params = {                      # Website -                    :list_description => _("'{{link_to_request}}', a request", :link_to_request => "<a href=\"/request/" + CGI.escapeHTML(self.info_request.url_title) + "\">" + CGI.escapeHTML(self.info_request.title) + "</a>"), # XXX yeuch, sometimes I just want to call view helpers from the model, sorry! can't work out how  +                    :list_description => _("'{{link_to_request}}', a request", :link_to_request => "<a href=\"/request/" + CGI.escapeHTML(self.info_request.url_title) + "\">" + CGI.escapeHTML(self.info_request.title) + "</a>"), # XXX yeuch, sometimes I just want to call view helpers from the model, sorry! can't work out how                      :verb_on_page => _("Track this request by email"),                      :verb_on_page_already => _("You are already tracking this request by email"),                      # Email @@ -242,7 +246,7 @@ class TrackThing < ActiveRecord::Base              elsif self.track_type == 'public_body_updates'                  @params = {                      # Website -                    :list_description => _("'{{link_to_authority}}', a public authority", :link_to_authority => "<a href=\"/body/" + CGI.escapeHTML(self.public_body.url_name) + "\">" + CGI.escapeHTML(self.public_body.name) + "</a>"), # XXX yeuch, sometimes I just want to call view helpers from the model, sorry! can't work out how  +                    :list_description => _("'{{link_to_authority}}', a public authority", :link_to_authority => "<a href=\"/body/" + CGI.escapeHTML(self.public_body.url_name) + "\">" + CGI.escapeHTML(self.public_body.name) + "</a>"), # XXX yeuch, sometimes I just want to call view helpers from the model, sorry! can't work out how                      :verb_on_page => _("Track requests to {{public_body_name}} by email",:public_body_name=>CGI.escapeHTML(self.public_body.name)),                      :verb_on_page_already => _("You are already tracking requests to {{public_body_name}} by email", :public_body_name=>CGI.escapeHTML(self.public_body.name)),                      # Email @@ -258,7 +262,7 @@ class TrackThing < ActiveRecord::Base              elsif self.track_type == 'user_updates'                  @params = {                      # Website -                    :list_description => _("'{{link_to_user}}', a person", :link_to_user => "<a href=\"/user/" + CGI.escapeHTML(self.tracked_user.url_name) + "\">" + CGI.escapeHTML(self.tracked_user.name) + "</a>"), # XXX yeuch, sometimes I just want to call view helpers from the model, sorry! can't work out how  +                    :list_description => _("'{{link_to_user}}', a person", :link_to_user => "<a href=\"/user/" + CGI.escapeHTML(self.tracked_user.url_name) + "\">" + CGI.escapeHTML(self.tracked_user.name) + "</a>"), # XXX yeuch, sometimes I just want to call view helpers from the model, sorry! can't work out how                      :verb_on_page => _("Track this person by email"),                      :verb_on_page_already => _("You are already tracking this person by email"),                      # Email @@ -274,7 +278,7 @@ class TrackThing < ActiveRecord::Base              elsif self.track_type == 'search_query'                  @params = {                      # Website -                    :list_description => "<a href=\"/search/" + CGI.escapeHTML(self.track_query) + "/newest/advanced\">" + self.track_query_description + "</a>", # XXX yeuch, sometimes I just want to call view helpers from the model, sorry! can't work out how  +                    :list_description => "<a href=\"/search/" + CGI.escapeHTML(self.track_query) + "/newest/advanced\">" + self.track_query_description + "</a>", # XXX yeuch, sometimes I just want to call view helpers from the model, sorry! can't work out how                      :verb_on_page => _("Track things matching this search by email"),                      :verb_on_page_already => _("You are already tracking things matching this search by email"),                      # Email diff --git a/app/models/track_things_sent_email.rb b/app/models/track_things_sent_email.rb index 777339d75..24297f57b 100644 --- a/app/models/track_things_sent_email.rb +++ b/app/models/track_things_sent_email.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: track_things_sent_emails  # diff --git a/app/models/user.rb b/app/models/user.rb index 59a84b7aa..57fce429c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: users  # @@ -20,6 +20,8 @@  #  email_bounced_at       :datetime  #  email_bounce_message   :text            default(""), not null  #  no_limit               :boolean         default(FALSE), not null +#  receive_email_alerts   :boolean         default(TRUE), not null +#  user_similarity_id     :integer  #  # models/user.rb: @@ -52,22 +54,22 @@ class User < ActiveRecord::Base      attr_accessor :password_confirmation, :no_xapian_reindex      validates_confirmation_of :password, :message => _("Please enter the same password twice") -    validates_inclusion_of :admin_level, :in => [  +    validates_inclusion_of :admin_level, :in => [          'none', -        'super',  +        'super',      ], :message => N_('Admin level is not included in list')      acts_as_xapian :texts => [ :name, :about_me ], -        :values => [  +        :values => [               [ :created_at_numeric, 1, "created_at", :number ] # for sorting          ],          :terms => [ [ :variety, 'V', "variety" ] ],          :if => :indexed_by_search?      def created_at_numeric          # format it here as no datetime support in Xapian's value ranges -        return self.created_at.strftime("%Y%m%d%H%M%S")  +        return self.created_at.strftime("%Y%m%d%H%M%S")      end -     +      def variety          "user"      end @@ -79,7 +81,7 @@ class User < ActiveRecord::Base          if self.new_record?              # make alert emails go out at a random time for each new user, so              # overall they are spread out throughout the day. -            self.last_daily_track_email = User.random_time_in_last_day     +            self.last_daily_track_email = User.random_time_in_last_day          end      end @@ -101,7 +103,7 @@ class User < ActiveRecord::Base              end          end      end -     +      def get_locale          if !self.locale.nil?              locale = self.locale @@ -117,10 +119,10 @@ class User < ActiveRecord::Base      def validate          if self.email != "" && !MySociety::Validate.is_valid_email(self.email) -            errors.add(:email, _("Please enter a valid email address"))  +            errors.add(:email, _("Please enter a valid email address"))          end          if MySociety::Validate.is_valid_email(self.name) -            errors.add(:name, _("Please enter your name, not your email address, in the name field."))  +            errors.add(:name, _("Please enter your name, not your email address, in the name field."))          end      end @@ -139,7 +141,7 @@ class User < ActiveRecord::Base      end      # Return user given login email, password and other form parameters (e.g. name) -    #   +    #      # The specific_user_login parameter says that login as a particular user is      # expected, so no parallel registration form is being displayed.      def User.authenticate_from_form(params, specific_user_login = false) @@ -235,10 +237,10 @@ class User < ActiveRecord::Base      # changed more than a day ago)      def get_undescribed_requests          self.info_requests.find( -            :all,  -            :conditions => [ 'awaiting_description = ? and ' + InfoRequest.last_event_time_clause + ' < ?',  -                true, Time.now() - 1.day  -            ]  +            :all, +            :conditions => [ 'awaiting_description = ? and ' + InfoRequest.last_event_time_clause + ' < ?', +                true, Time.now() - 1.day +            ]          )      end @@ -256,7 +258,7 @@ class User < ActiveRecord::Base      def owns_every_request?          self.admin_level == 'super'      end -     +      def User.owns_every_request?(user)        !user.nil? && user.owns_every_request?      end @@ -271,7 +273,7 @@ class User < ActiveRecord::Base      def User.stay_logged_in_on_redirect?(user)        !user.nil? && user.admin_level == 'super'      end -      +      # Does the user get "(admin)" links on each page on the main site?      def admin_page_links?          self.admin_level == 'super' @@ -287,21 +289,21 @@ class User < ActiveRecord::Base      def exceeded_limit?          # Some users have no limit          return false if self.no_limit -         +          # Has the user issued as many as MAX_REQUESTS_PER_USER_PER_DAY requests in the past 24 hours?          daily_limit = MySociety::Config.get("MAX_REQUESTS_PER_USER_PER_DAY")          return false if daily_limit.nil?          recent_requests = InfoRequest.count(:conditions => ["user_id = ? and created_at > now() - '1 day'::interval", self.id]) -         +          return (recent_requests >= daily_limit)      end      def next_request_permitted_at          return nil if self.no_limit -         +          daily_limit = MySociety::Config.get("MAX_REQUESTS_PER_USER_PER_DAY")          n_most_recent_requests = InfoRequest.all(:conditions => ["user_id = ? and created_at > now() - '1 day'::interval", self.id], :order => "created_at DESC", :limit => daily_limit)          return nil if n_most_recent_requests.size < daily_limit -         +          nth_most_recent_request = n_most_recent_requests[-1]          return nth_most_recent_request.created_at + 1.day      end @@ -375,7 +377,7 @@ class User < ActiveRecord::Base      end      def json_for_api -        return {  +        return {              :id => self.id,              :url_name => self.url_name,              :name => self.name, @@ -391,36 +393,54 @@ class User < ActiveRecord::Base          self.email_bounce_message = message          self.save!      end -     +      def should_be_emailed?          return (self.email_confirmed && self.email_bounced_at.nil?)      end -     +      def indexed_by_search?          return self.email_confirmed      end +    def for_admin_column(complete = false) +      if complete +        columns = self.class.content_columns +      else +        columns = self.class.content_columns.map{|c| c if %w(created_at updated_at admin_level email_confirmed).include?(c.name) }.compact +      end +      columns.each do |column| +        yield(column.human_name, self.send(column.name), column.type.to_s) +      end +    end +      ## Private instance methods      private      def create_new_salt          self.salt = self.object_id.to_s + rand.to_s      end -     +      ## Class methods      def User.encrypted_password(password, salt)          string_to_hash = password + salt # XXX need to add a secret here too?          Digest::SHA1.hexdigest(string_to_hash)      end -         +      def User.record_bounce_for_email(email, message)          user = User.find_user_by_email(email)          return false if user.nil? -         +          if user.email_bounced_at.nil?              user.record_bounce(message)          end          return true      end + +    after_save(:purge_in_cache) +    def purge_in_cache +        # XXX should only be if specific attributes have changed +        self.info_requests.each {|x| x.purge_in_cache} +    end +  end diff --git a/app/models/user_info_request_sent_alert.rb b/app/models/user_info_request_sent_alert.rb index 5f23355bf..a97fd5d44 100644 --- a/app/models/user_info_request_sent_alert.rb +++ b/app/models/user_info_request_sent_alert.rb @@ -1,5 +1,5 @@  # == Schema Information -# Schema version: 108 +# Schema version: 114  #  # Table name: user_info_request_sent_alerts  # @@ -23,7 +23,7 @@ class UserInfoRequestSentAlert < ActiveRecord::Base      belongs_to :user      belongs_to :info_request -    validates_inclusion_of :alert_type, :in => [  +    validates_inclusion_of :alert_type, :in => [          'overdue_1', # tell user that info request has become overdue          'very_overdue_1', # tell user that info request has become very overdue          'new_response_reminder_1', # reminder user to classify the recent response diff --git a/app/views/admin_general/debug.rhtml b/app/views/admin_general/debug.rhtml index 40fe33616..d7bf1c6da 100644 --- a/app/views/admin_general/debug.rhtml +++ b/app/views/admin_general/debug.rhtml @@ -7,6 +7,8 @@  <h2>Version numbers</h2>  <p> +Alaveteli version: <%= link_to @current_version, @github_origin + @current_version %> +<br>  Alaveteli branch: <%= link_to @current_branch, @github_origin + @current_branch %>  <br>  Alaveteli commit: <%= link_to @current_commit, @github_origin + @current_commit %> diff --git a/app/views/admin_general/index.rhtml b/app/views/admin_general/index.rhtml index 1a4b8ba96..48bd7f694 100644 --- a/app/views/admin_general/index.rhtml +++ b/app/views/admin_general/index.rhtml @@ -46,6 +46,20 @@      </ul>  <% end %> +<% if @attention_requests.size > 0 %> +    <h3>Review requests which have been marked as requiring your attention by users (<%=@error_message_requests.size%> total)</h3> + +    <ul> +    <% for @request in @attention_requests %> +        <li> +            <%= request_both_links(@request)%> +            – <%=simple_date(@request.get_last_event.created_at)%> +        </li> +    <% end %> +    </ul> +<% end %> + +  <% if @requires_admin_requests.size > 0 %>      <h3>These require administrator attention (<%=@requires_admin_requests.size%> total)</h3> diff --git a/app/views/admin_request/edit.rhtml b/app/views/admin_request/edit.rhtml index ffaa1299c..4026ee712 100644 --- a/app/views/admin_request/edit.rhtml +++ b/app/views/admin_request/edit.rhtml @@ -47,7 +47,7 @@  <hr> -<% form_tag '../fully_destroy/' + @info_request.id.to_s do %> +<% form_tag '../destroy/' + @info_request.id.to_s do %>      <p>      <strong>This is permanent and irreversible!</strong> <%= submit_tag 'Destory request entirely' %>      <br>Use it mainly if someone posts private information, e.g. made a Data Protection request. It diff --git a/app/views/general/_locale_switcher.rhtml b/app/views/general/_locale_switcher.rhtml index 27e492e84..2521b5eb5 100644 --- a/app/views/general/_locale_switcher.rhtml +++ b/app/views/general/_locale_switcher.rhtml @@ -1,11 +1,13 @@ -     <% if FastGettext.default_available_locales.length > 1 && !params.empty? %> -     <div id="user_locale_switcher"> -       <% for possible_locale in FastGettext.default_available_locales %> -        <% if possible_locale == I18n.locale.to_s %> -	          <span class="active"><%= locale_name(possible_locale) %></span> -        <% else %> -          <a href="<%= locale_switcher(possible_locale, params) %>"><%= locale_name(possible_locale) %></a> -        <% end %> -         <% end %> + <% if FastGettext.default_available_locales.length > 1 && !params.empty? %> +   <div id="user_locale_switcher"> +     <div class="btn-group"> +     <% for possible_locale in FastGettext.default_available_locales %> +      <% if possible_locale == I18n.locale.to_s %> +        <a href="#" class="btn disabled"><%= locale_name(possible_locale) %></a> +      <% else %> +        <a href="<%= locale_switcher(possible_locale, params) %>" class="btn"><%= locale_name(possible_locale) %></a> +      <% end %> +       <% end %>       </div> -     <% end %> +   </div> + <% end %> diff --git a/app/views/general/search.rhtml b/app/views/general/search.rhtml index 90ace809e..a1f8c8f04 100644 --- a/app/views/general/search.rhtml +++ b/app/views/general/search.rhtml @@ -131,8 +131,7 @@      </p>    <% end %>  </div>  <!-- header left --> - -<% if @track_thing  && (@xapian_bodies_hits > 0 || @xapian_users_hits > 0 || @total_hits == 0)%> +<% if !@track_thing.nil? %>    <div id="header_right">      <h2><%= _('Track this search')%></h2>      <%= render :partial => 'track/tracking_links', :locals => { :track_thing => @track_thing, :own_request => false, :location => 'main' } %> diff --git a/app/views/layouts/admin.rhtml b/app/views/layouts/admin.rhtml index 42ca5dbbb..65670538d 100644 --- a/app/views/layouts/admin.rhtml +++ b/app/views/layouts/admin.rhtml @@ -9,7 +9,7 @@        <%= stylesheet_link_tag 'admin-theme/jquery-ui-1.8.15.custom.css', :rel => 'stylesheet'%>        <%= stylesheet_link_tag 'admin', :title => "Main", :rel => "stylesheet"  %>      </head> -    <body> +    <body class="admin">      <p>      <strong><%= link_to 'Alaveteli', main_url('/') %> admin:</strong>  diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index f439b27d2..e60e7bef2 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -58,7 +58,7 @@          <%= render :partial => 'general/before_head_end' %>      </head> -    <body <%= "class='front'" if params[:action] == 'frontpage' %>> +    <body class="<%= 'admin' if !session[:using_admin].nil?%> <%= 'front' if params[:action] == 'frontpage' %>">      <!-- XXX: move to a separate file -->      <% if force_registration_on_new_request && !@user %> @@ -154,6 +154,20 @@  <br />  <input type="text">  </div> +    <% +      ga_code = MySociety::Config.get('GA_CODE', '') +      unless ga_code.empty? +    %> +    <script> +      var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); +      document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); +    </script> +    <script> +      var pageTracker = _gat._getTracker("<%=ga_code%>"); +      pageTracker._trackPageview(); +    </script> +    <% end %> +      </body>  </html> diff --git a/app/views/request/_sidebar.rhtml b/app/views/request/_sidebar.rhtml index 758387b09..c0708a36a 100644 --- a/app/views/request/_sidebar.rhtml +++ b/app/views/request/_sidebar.rhtml @@ -4,7 +4,16 @@      <% follower_count = TrackThing.count(:all, :conditions => ["info_request_id = ?", @info_request.id]) + 1 %>      <p><%= n_("There is %d person following this request", "There are %d people following this request", follower_count) % follower_count %></p>       <%= render :partial => 'track/tracking_links', :locals => { :track_thing => @track_thing, :own_request => @info_request.user == @user, :location => 'sidebar' } %> - +    <% if @info_request.described_state != "attention_requested" %> +    <h2><%= _('Offensive? Unsuitable?') %></h2> +     <% if @info_request.attention_requested %> +       <p><%= ('The site administrators have reviewed this request and consider it to be suitable for the website.') %></p> +     <% else %> +      <p><%= _('Requests for personal information and vexatious requests are not considered valid for FOI purposes (<a href="/help/about">read more</a>).') %> +       <p><%= ('If you believe this request is not suitable, you can report it for attention by the site administrators') %></p> +       <%= link_to _("Report this request"), report_path, :class => "link_button_green" %> +      <% end %> +    <% end %>      <h2><%= _("Act on what you've learnt") %></h2>      <div class="act_link"> diff --git a/app/views/request/show.rhtml b/app/views/request/show.rhtml index 23721c31d..120950343 100644 --- a/app/views/request/show.rhtml +++ b/app/views/request/show.rhtml @@ -1,4 +1,6 @@ -<% @title = "#{h(@info_request.title)} - a Freedom of Information request to #{h(@info_request.public_body.name)}" %> +<% @title = _("{{title}} - a Freedom of Information request to {{public_body}}", +                :title => h(@info_request.title), +                :public_body => (@info_request.public_body.name)) %>  <% if @info_request.prominence == 'hidden' %>      <p id="hidden_request"> @@ -116,6 +118,8 @@      <% elsif @status == 'user_withdrawn' %>          <%= _('This request has been <strong>withdrawn</strong> by the person who made it.           	   There may be an explanation in the correspondence below.') %> +    <% elsif @status == 'attention_requested' %> +        <%= _('This request has been <strong>reported</strong> as needing administrator attention (perhaps because it is vexatious, or a request for personal information)') %>      <% else %>          <%= render :partial => 'general/custom_state_descriptions', :locals => { :status => @status } %>      <% end %> diff --git a/app/views/user/confirm.rhtml b/app/views/user/confirm.rhtml index bdaf5c8e9..bc70a1f36 100644 --- a/app/views/user/confirm.rhtml +++ b/app/views/user/confirm.rhtml @@ -1,6 +1,6 @@ -<% @title = h("Now check your email!") %> +<% @title = _("Now check your email!") %> -<h1 class="confirmation_heading">Now check your email!</h1> +<h1 class="confirmation_heading"><%= _("Now check your email!") %></h1>  <p class="confirmation_message">  <%= _('We\'ve sent you an email, and you\'ll need to click the link in it before you can diff --git a/app/views/user/show.rhtml b/app/views/user/show.rhtml index 8f1803442..f8b820f03 100644 --- a/app/views/user/show.rhtml +++ b/app/views/user/show.rhtml @@ -1,7 +1,7 @@  <% if @show_requests %> - <% @title = h(@display_user.name) + " - Freedom of Information requests" %> + <% @title = h(@display_user.name) + _(" - Freedom of Information requests") %>  <% else %> - <% @title = h(@display_user.name) + " - user profile" %> + <% @title = h(@display_user.name) + _(" - user profile") %>  <% end %>  <% if (@same_name_users.size >= 1) %> @@ -139,7 +139,7 @@      <% if !@xapian_requests.nil? %>          <% if @xapian_requests.results.empty? %>              <% if @page == 1 %> -                <h2 class="foi_results" id="foi_requests"><%= @is_you ? 'Freedom of Information requests made by you' : 'Freedom of Information requests made by this person' %> <%= @match_phrase %> +                <h2 class="foi_results" id="foi_requests"><%= @is_you ? _('Freedom of Information requests made by you') : _('Freedom of Information requests made by this person') %> <%= @match_phrase %>  </h2>                  <p><%= @is_you ? _('You have made no Freedom of Information requests using this site.') : _('This person has made no Freedom of Information requests using this site.') %>                  <%= @page_desc %> diff --git a/app/views/user/sign.rhtml b/app/views/user/sign.rhtml index bfd0fa63e..4704ea95a 100644 --- a/app/views/user/sign.rhtml +++ b/app/views/user/sign.rhtml @@ -10,7 +10,10 @@              <%= @post_redirect.reason_params[:web] %>,                <%= _('please sign in as ')%><%= link_to h(@post_redirect.reason_params[:user_name]), @post_redirect.reason_params[:user_url] %>.          <% end %> -        </p> +	</p> +        <% if @post_redirect.post_params["controller"] == "admin_general" %> +         <p id="superuser_message">Don't have a superuser account yet?  <%= link_to "Sign in as the emergency user", @post_redirect.uri + "?emergency=1" %></p> +        <% end %>      <%= render :partial => 'signin', :locals => { :sign_in_as_existing_user => true } %> diff --git a/commonlib b/commonlib -Subproject 95be1291ff71e37196643b6b83b99d8e25c0d82 +Subproject 9e1d29721b9dba232c251ef4b8b79f8505422de diff --git a/config.ru b/config.ru new file mode 100644 index 000000000..30b00bfa1 --- /dev/null +++ b/config.ru @@ -0,0 +1,2 @@ +require File.dirname(__FILE__) + '/config/environment' +run ActionController::Dispatcher.new diff --git a/config/crontab.ugly b/config/crontab.ugly index a22d5afd7..c5d023f2f 100644 --- a/config/crontab.ugly +++ b/config/crontab.ugly @@ -13,6 +13,7 @@ MAILTO=cron-!!(*= $site *)!!@mysociety.org  */5 * * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/change-xapian-database.lock "/data/vhost/!!(*= $vhost *)!!/!!(*= $vcspath *)!!/script/update-xapian-index verbose=true" >> /data/vhost/!!(*= $vhost *)!!/logs/update-xapian-index.log || echo "stalled?"  # Every 10 minutes  5,15,25,35,45,55 * * * * !!(*= $user *)!! /etc/init.d/foi-alert-tracks check +5,15,25,35,45,55 * * * * !!(*= $user *)!! /etc/init.d/purge-varnish check  # Once an hour  39 * * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/alert-overdue-requests.lock /data/vhost/!!(*= $vhost *)!!/!!(*= $vcspath *)!!/script/alert-overdue-requests || echo "stalled?" diff --git a/config/environment.rb b/config/environment.rb index 7b6e8f5bc..b958c6475 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -49,7 +49,7 @@ Rails::Initializer.run do |config|    # config.plugins = %W( exception_notification ssl_requirement )    # Add additional load paths for your own custom dirs -  # config.load_paths += %W( #{RAILS_ROOT}/extras ) +  # config.load_paths += %W( #{Rails.root}/extras )    # Force all environments to use the same logger level     # (by default production uses :info, the others :debug) @@ -135,6 +135,7 @@ require 'i18n_fixes.rb'  require 'rack_quote_monkeypatch.rb'  require 'world_foi_websites.rb'  require 'alaveteli_external_command.rb' +require 'quiet_opener.rb'  ExceptionNotification::Notifier.sender_address = MySociety::Config::get('EXCEPTION_NOTIFICATIONS_FROM')  ExceptionNotification::Notifier.exception_recipients = MySociety::Config::get('EXCEPTION_NOTIFICATIONS_TO') diff --git a/config/environments/development.rb b/config/environments/development.rb index d5f2f5772..a1e8133a8 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,5 +1,7 @@  # Settings specified here will take precedence over those in config/environment.rb +config.log_level = :info +  # In the development environment your application's code is reloaded on  # every request.  This slows down response time but is perfect for development  # since you don't have to restart the webserver when you make code changes. diff --git a/config/general.yml-example b/config/general.yml-example index ed04e0fd5..019eb7ada 100644 --- a/config/general.yml-example +++ b/config/general.yml-example @@ -1,7 +1,7 @@  # general.yml-example:  # Example values for the "general" config file.  # -# Configuration parameters, in YAML syntax.  +# Configuration parameters, in YAML syntax.  #  # Copy this file to one called "general.yml" in the same directory.  Or  # have multiple config files and use a symlink to change between them. @@ -34,8 +34,12 @@ SPECIAL_REPLY_VERY_LATE_AFTER_DAYS: 60  # example public bodies for the home page, semicolon delimited - short_names  FRONTPAGE_PUBLICBODY_EXAMPLES: 'tgq' -# URL of theme to install (when running rails-post-deploy script) -THEME_URL: 'git://github.com/sebbacon/alavetelitheme.git' +# URLs of themes to download and use (when running rails-post-deploy +# script). Earlier in the list means the templates have a higher +# priority. +THEME_URLS:  + - 'git://github.com/sebbacon/adminbootstraptheme.git' + - 'git://github.com/sebbacon/alavetelitheme.git'  # Whether a user needs to sign in to start the New Request process  FORCE_REGISTRATION_ON_NEW_REQUEST: false @@ -56,10 +60,13 @@ BLACKHOLE_PREFIX: 'do-not-reply-to-this-address'  ## Administration -# Leave these two blank to skip admin authorisation +# The emergency user  ADMIN_USERNAME: 'adminxxxx'  ADMIN_PASSWORD: 'passwordx' +# Set this to true, and the admin interface will be available to anonymous users +SKIP_ADMIN_AUTH: false +  # Email "from" details  CONTACT_EMAIL: 'postmaster@localhost'  CONTACT_NAME: 'Alaveteli Webmaster' @@ -142,3 +149,11 @@ EXCEPTION_NOTIFICATIONS_TO:  # This rate limiting can be turned off per-user via the admin interface  MAX_REQUESTS_PER_USER_PER_DAY: 6 + + +# This is used to work out where to send purge requests.  Should be +# unset if you aren't running behind varnish +VARNISH_HOST: localhost + +# Adding a value here will enable Google Analytics on all non-admin pages. +GA_CODE: '' diff --git a/config/initializers/fast_gettext.rb b/config/initializers/fast_gettext.rb index 63cf6b50d..9049fd8ed 100644 --- a/config/initializers/fast_gettext.rb +++ b/config/initializers/fast_gettext.rb @@ -1,2 +1,3 @@ +Encoding.default_external = 'UTF-8' if RUBY_VERSION.to_f >= 1.9  FastGettext.add_text_domain 'app', :path => File.join(Rails.root, 'locale'), :type => :po  FastGettext.default_text_domain = 'app' diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 9ef2dddc1..bf40e99c1 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -12,6 +12,6 @@ ActionController::Base.session = {  ActionController::Base.session_store = :cookie_store  # Insert a bit of middleware code to prevent uneeded cookie setting. -require "#{RAILS_ROOT}/lib/whatdotheyknow/strip_empty_sessions" +require "#{Rails.root}/lib/whatdotheyknow/strip_empty_sessions"  ActionController::Dispatcher.middleware.insert_before ActionController::Base.session_store, WhatDoTheyKnow::StripEmptySessions, :key => '_wdtk_cookie_session', :path => "/", :httponly => true diff --git a/config/initializers/theme_loader.rb b/config/initializers/theme_loader.rb new file mode 100644 index 000000000..df392cc08 --- /dev/null +++ b/config/initializers/theme_loader.rb @@ -0,0 +1,7 @@ +theme_urls = MySociety::Config.get("THEME_URLS", []) +if ENV["RAILS_ENV"] != "test" # Don't let the theme interfere with Alaveteli specs +    for url in theme_urls.reverse +        theme_name = url.sub(/.*\/(.*).git/, "\\1") +        require File.expand_path "../../../vendor/plugins/#{theme_name}/lib/alavetelitheme.rb", __FILE__ +    end +end diff --git a/config/purge-varnish-debian.ugly b/config/purge-varnish-debian.ugly new file mode 100644 index 000000000..3e77c09c3 --- /dev/null +++ b/config/purge-varnish-debian.ugly @@ -0,0 +1,81 @@ +#!/bin/bash +# +### BEGIN INIT INFO +# Provides: purge-varnish +# Required-Start: $local_fs $syslog +# Required-Stop: $local_fs $syslog +# Default-Start: 2 3 4 5  +# Default-Stop: 0 1 6 +# Short-Description: purge-varnish is a daemon running the Alaveteli email alerts +# Description: purge-varnish send Alaveteli email alerts as required +### END INIT INFO +# +# !!(*= $daemon_name *)!! Start the Alaveteli email alert daemon + +NAME=!!(*= $daemon_name *)!! +DAEMON=/data/vhost/!!(*= $vhost *)!!/alaveteli/script/runner +DAEMON_ARGS="--daemon PurgeRequest.purge_all_loop" +PIDFILE=/data/vhost/!!(*= $vhost *)!!/purge-varnish.pid +LOGFILE=/data/vhost/!!(*= $vhost *)!!/logs/purge-varnish.log +DUSER=!!(*= $user *)!! + +trap "" 1 + +export PIDFILE LOGFILE + +quietly_start_daemon() { +    /sbin/start-stop-daemon --quiet --start --pidfile "$PIDFILE" --chuid "$DUSER" --startas "$DAEMON" -- $DAEMON_ARGS +} + +start_daemon() { +    /sbin/start-stop-daemon --start --pidfile "$PIDFILE" --chuid "$DUSER" --startas "$DAEMON" -- $DAEMON_ARGS +} + +stop_daemon() { +    /sbin/start-stop-daemon --stop --oknodo --pidfile "$PIDFILE" +} + +restart() { stop; start; } + +case "$1" in +  check) +      quietly_start_daemon +      if [ $? -ne 1 ] +      then +          echo "Alaveteli alert daemon was not running; now restarted" +          exit 1 +      else +          exit 0 +      fi +      ;; +   +  start) +      echo -n "Starting Alaveteli alert daemon: $NAME" +      start_daemon +      ;; +   +  stop) +        echo -n "Stopping Alaveteli alert daemon: $NAME" +        stop_daemon +        ;; +   +  restart) +      echo -n "Restarting Alaveteli alert daemon: $NAME" +      stop_daemon +      start_daemon +      ;; + +  *) +    echo "Usage: /etc/init.d/$NAME {start|stop|restart|check}" +    exit 1 +    ;; +esac + +if [ $? -eq 0 ]; then +    echo . +    exit 0 +else +    echo " failed" +    exit 1 +fi + diff --git a/config/routes.rb b/config/routes.rb index 747cc9b06..698752218 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -22,6 +22,7 @@ ActionController::Routing::Routes.draw do |map|          general.blog '/blog', :action => 'blog'          general.custom_css '/stylesheets/custom.css', :action => 'custom_css'          general.search_redirect '/search',      :action => 'search_redirect' +        general.search_redirect '/search/all',      :action => 'search_redirect'          # XXX combined is the search query, and then if sorted a "/newest" at the end.          # Couldn't find a way to do this in routes which also picked up multiple other slashes          # and dots and other characters that can appear in search query. So we sort it all @@ -65,6 +66,7 @@ ActionController::Routing::Routes.draw do |map|          request.upload_response "/upload/request/:url_title", :action => 'upload_response'          request.download_entire_request '/request/:url_title/download',      :action => 'download_entire_request' +        request.report '/request/:url_title/report',      :action => 'report_request'      end diff --git a/config/test.yml b/config/test.yml index 90689395a..460d7c6c1 100644 --- a/config/test.yml +++ b/config/test.yml @@ -124,3 +124,5 @@ EXCEPTION_NOTIFICATIONS_TO:  MAX_REQUESTS_PER_USER_PER_DAY: 2 +VARNISH_HOST: varnish.localdomain +SKIP_ADMIN_AUTH: true
\ No newline at end of file diff --git a/config/varnish-alaveteli.vcl b/config/varnish-alaveteli.vcl index 7eedf83fc..452a956da 100644 --- a/config/varnish-alaveteli.vcl +++ b/config/varnish-alaveteli.vcl @@ -15,6 +15,12 @@ backend default {      .between_bytes_timeout = 600s;  } +// set the servers alaveteli can issue a purge from +acl purge { +   "localhost"; +   "127.0.0.1"; +} +  sub vcl_recv {     # Handle IPv6 @@ -54,12 +60,13 @@ sub vcl_recv {         req.request != "HEAD" &&          req.request != "POST" &&         req.request != "PUT" && +       req.request != "PURGE" &&         req.request != "DELETE" ) {          # We don't allow any other methods.          error 405 "Method Not Allowed";      } -    if (req.request != "GET" && req.request != "HEAD") { +    if (req.request != "GET" && req.request != "HEAD" && req.request != "PURGE") {          /* We only deal with GET and HEAD by default, the rest get passed direct to backend */          return (pass);      } @@ -73,15 +80,23 @@ sub vcl_recv {      if (req.http.Authorization || req.http.Cookie) {          return (pass);      } -       # Let's have a little grace      set req.grace = 30s; +    # Handle PURGE requests +    if (req.request == "PURGE") { +      if (!client.ip ~ purge) { +         error 405 "Not allowed."; +      } +      # XXX in Varnish 2.x, the following would be +      # purge("obj.http.x-url ~ " req.url); +      ban("obj.http.x-url ~ " + req.url); +      error 200 "Banned"; +    }      return (lookup);  } -  sub vcl_fetch { - +    set beresp.http.x-url = req.url;      if (req.url ~ "\.(png|gif|jpg|jpeg|swf|css|js|rdf|ico|txt)(\?.*|)$") {      # Ignore backend headers..          remove beresp.http.set-Cookie; @@ -94,3 +109,4 @@ sub vcl_fetch {          return (deliver);      }  } + diff --git a/db/migrate/111_create_purge_requests.rb b/db/migrate/111_create_purge_requests.rb new file mode 100644 index 000000000..0b4fd1d1d --- /dev/null +++ b/db/migrate/111_create_purge_requests.rb @@ -0,0 +1,14 @@ +class CreatePurgeRequests < ActiveRecord::Migration +  def self.up +    create_table :purge_requests do |t| +        t.column :url, :string +        t.column :created_at, :datetime, :null => false +        t.column :model, :string, :null => false +        t.column :model_id, :integer, :null => false +    end +  end + +  def self.down +    drop_table :purge_requests +  end +end diff --git a/db/migrate/114_add_attention_requested_flag_to_info_requests.rb b/db/migrate/114_add_attention_requested_flag_to_info_requests.rb new file mode 100644 index 000000000..48c98e5a9 --- /dev/null +++ b/db/migrate/114_add_attention_requested_flag_to_info_requests.rb @@ -0,0 +1,13 @@ +require 'digest/sha1' + +class AddAttentionRequestedFlagToInfoRequests < ActiveRecord::Migration +    def self.up +        add_column :info_requests, :attention_requested, :boolean, :default => false +    end +    def self.down +        remove_column :info_requests, :attention_requested +    end +end + + + diff --git a/doc/CHANGES.md b/doc/CHANGES.md index 51da903b1..cca6a08e8 100644 --- a/doc/CHANGES.md +++ b/doc/CHANGES.md @@ -3,17 +3,56 @@  ## Highlighted features  * Ruby dependencies are now handled by Bundler +* Support for invalidating accelerator cache -- this makes it much +  less likely, when using Varnish, that users will be presented with +  stale content.  Fixes +  [issue #436](https://github.com/sebbacon/alaveteli/issues/436) +* Adding a `GA_CODE` to `general.yml` will cause the relevant Google +  Analytics code to be added to your rendered pages +* It is now possible to have more than one theme installed.  The +  behaviour of multiple themes is now layered in the reverse order +  they're listed in the config file.  See the variable `THEME_URLS` in +  `general.yml-example` for an example.  ## Upgrade notes  * Existing installations will need to install the Bundler gem.  See    `INSTALL.md` for details. +* As a result of using bundler, the list of software packages that +  should be installed has changed.  On Debian, you can run: + +      sudo apt-get install `cut -d " " -f 1 config/packages | grep -v "^#"` +       +  [This gist](https://gist.github.com/2584766) shows the changes to +  `config/packages` since the previous release. +  * Because dependencies are now handled by Bundler, when you next run    the `rails-post-deploy` script, it will download, compile and    install various things.  Part of this is compiling xapian, which may    take a *long* time (subsequent deployments should be much faster) +* To support invalidating the Varnish cache, ensure that there's a +  value for `VARNISH_HOST` in `general.yml` (normally this would be +  `localhost`).  You will also need to update your Varnish server to +  support PURGE requests.  The example configuration provided at +  `config/varnish-alaveteli.vcl` will work for Varnish 3 and above. If +  you leave `VARNISH_HOST` blank, it will have no effect.  Finally, +  you should install the `purge-varnish` init script that's provided +  in `ugly` format at `config/purge-varnish-debian.ugly` to ensure the +  purge queue is emptied regularly. + +* Administrators are now assumed to log in using standard user accounts +  with superuser privileges (see 'Administrator Privileges' in +  `INSTALL.md`). The old-style admin account (using credentials from +  `general.yml`) is now known as the "emergency user".  Deployments +  that previously bypassed admin authentication should set the new +  `SKIP_ADMIN_AUTH` config variable to `true`. + +# Version 0.5.2 + +This is a hotfix to fix occasional problems importing public body CSVs +  # Version 0.5.1  ## Highlighted features diff --git a/doc/INSTALL-exim4.md b/doc/INSTALL-exim4.md index 82c1aba45..84f105c57 100644 --- a/doc/INSTALL-exim4.md +++ b/doc/INSTALL-exim4.md @@ -34,7 +34,7 @@ In `/etc/exim4/conf.d/transport/04_alaveteli`:         user = ALAVETELI_USER         group = ALAVETELI_USER -And, assuming you set `OPTION_INCOMING_EMAIL_PREFIX` in your config at +And, assuming you set `INCOMING_EMAIL_PREFIX` in your config at  `config/general` to "foi+", create `config/aliases` with the following  content: @@ -59,7 +59,11 @@ with `FORWARD_NONBOUNCE_RESPONSES_TO: 'raw_team@whatdotheyknow.com'`  Finally, make sure you have `dc_use_split_config='true'` in  `/etc/exim4/update-exim4.conf.conf`, and execute the command -`update-exim4.conf` +`update-exim4.conf`. + +NB: if the file `/etc/exim4/exim4.conf` exists then `update-exim4.conf` +will silently do nothing. Some distributions include this file. If +yours does, you will need to rename it before running `update-exim4.conf`.  (You may also want to set `dc_eximconfig_configtype='internet'`,  `dc_local_interfaces='0.0.0.0 ; ::1'`, and diff --git a/doc/INSTALL-postfix.md b/doc/INSTALL-postfix.md new file mode 100644 index 000000000..70a2954bd --- /dev/null +++ b/doc/INSTALL-postfix.md @@ -0,0 +1,40 @@ +As an example of how to set up your MTA, in postfix on Ubuntu, you might +add the following to its configuration. + +In /etc/postfix/master.cf: + +  alaveteli unix  -	n	n	-	50	pipe +    flags=R user=ALAVETELI_USER argv=ALAVETELI_HOME/script/mailin + +In /etc/postfix/main.cf + +  virtual_alias_maps = regexp:/etc/postfix/regexp + +For example + +ALAVETELI_HOME=/path/to/alaveteli/software +ALAVETELI_USER=www-data + +The user ALAVETELI_USER should have write permissions on ALAVETELI_HOME. + +And, assuming you set `OPTION_INCOMING_EMAIL_PREFIX` in your config at +`config/general` to "foi+", create `/etc/postfix/regexp` with the following +content: + +  /^foi.*/	alaveteli + + +You should also configure postfix to discard any messages sent to the `BLACKHOLE_PREFIX` +address, whose default value is 'do-not-reply-to-this-address'. For example, add the +following to /etc/aliases: + +    # We use this for envelope from for some messages where we don't care about delivery +    do-not-reply-to-this-address:        :blackhole: + +# Troubleshooting + +To test mail delivery, run: +   +  $ /usr/sbin/sendmail -bv foi+requrest-1234@localhost + +This tells you if sending the emails to 'foi\+.*localhost' is working.  diff --git a/doc/INSTALL.md b/doc/INSTALL.md index 8c4ed505b..2ebc21dab 100644 --- a/doc/INSTALL.md +++ b/doc/INSTALL.md @@ -6,7 +6,7 @@ deployment platform.  Commands are intended to be run via the terminal or over ssh.  As an aid to evaluation, there is an -[Amazon AMI](https://github.com/sebbacon/alaveteli/wiki/Alaveteli-ec2-amix) +[Amazon AMI](https://github.com/sebbacon/alaveteli/wiki/Alaveteli-ec2-ami)  with all these steps configured.  It is *not* production-ready.  # Get Alaveteli @@ -19,8 +19,9 @@ Next, get hold of the Alaveteli source code from github:      git clone https://github.com/sebbacon/alaveteli.git      cd alaveteli -This will get the current stable release.  If you are a developer and want to -add or try new features, you might want to swap to the development +This will get the current stable release from the master branch (which +always contains the latest release).  If you are a developer and want +to add or try new features, you might want to swap to the development  branch:      git checkout develop @@ -37,7 +38,11 @@ packages by adding the following to `/etc/apt/sources.list` and  running `apt-get update`:      deb http://debian.mysociety.org squeeze main -     + +If you don't set up that mySociety Debian source (e.g. if you're +running Ubuntu), you should comment out `wkhtmltopdf-static` from +`config/packages`, as it won't install in the next step +  Now install the packages that are listed in config/packages using apt-get  e.g.: @@ -45,10 +50,7 @@ e.g.:  Some of the files also have a version number listed in config/packages  - check that you have appropriate versions installed. Some also list -"|" and offer a choice of packages.  If you've not set up the -mySociety Debian source (e.g. if you're running Ubuntu), you should -comment out `wkhtmltopdf-static` from `config/packages`, as it won't -install. +"|" and offer a choice of packages.  # Install Ruby dependencies @@ -248,7 +250,7 @@ component so you should really try to get this working.  Make sure everything looks OK: -    rake spec +    bundle exec rake spec  If there are failures here, something has gone wrong with the  preceding steps (see the next section for a common problem and @@ -281,23 +283,26 @@ the site in action.  # Administrator privileges -By default, anyone can access the administrator pages without authentication. -They are under the URL `/admin`. +The administrative interface is at the URL `/admin`. -At mySociety (originators of the Alaveteli software), they use a -separate layer of HTTP basic authentication, proxied over HTTPS, to -check who is allowed to use the administrator pages. You might like to -do something similar. +Only users with the `super` admin level can access the admin +interface.  Users create their own accounts in the usual way, and then +administrators can give them `super` privileges. -Alternatively, update the code so that: +There is an emergency user account which can be accessed via +`/admin?emergency=1`, using the credentials `ADMIN_USERNAME` and +`ADMIN_PASSWORD`, which are set in `general.yml`.  To bootstrap the +first `super` level accounts, you will need to log in as the emergency +user. -* By default, admin pages use normal site authentication (checking user admin -level 'super'). -* Create an option in `config/general` which lets us override that -behaviour. - -And send us the patch! +Users with the superuser role also have extra privileges in the +website frontend, such as being able to categorise any request, being +able to view items that have been hidden from the search, and being +presented with "admin" links next to individual requests and comments +in the front end. +It is possible completely to override the administrator authentication +by setting `SKIP_ADMIN_AUTH` to `true` in `general.yml`.  # Cron jobs @@ -324,7 +329,14 @@ One of the cron jobs refers to a script at  `/etc/init.d/foi-alert-tracks`.  This is an init script, a copy of  which lives in `config/alert-tracks-debian.ugly`.  As with the cron  jobs above, replace the variables (and/or bits near the variables) -with paths to your software. +with paths to your software.  `config/purge-varnish-debian.ugly` is a +similar init script, which is optional and not required if you choose +not to run your site behind Varnish (see below). + +The cron jobs refer to a program `run-with-lockfile`. See +[this issue](https://github.com/sebbacon/alaveteli/issues/112) for a +discussion of where to find this program, and how you might replace +it.  # Set up production web server @@ -347,6 +359,25 @@ Some  [production server best practice notes](https://github.com/sebbacon/alaveteli/wiki/Production-Server-Best-Practices)  are evolving on the wiki. +# Upgrading Alaveteli + +The developer team policy is that the master branch in git should +always contain the latest stable release.  Therefore, in production, +you should usually have your software deployed from the master branch, +and an upgrade can be simply `git pull`. + +Patch version increases (e.g. 1.2.3 -> 1.2.4) should not require any +further action on your part. + +Minor version increases (e.g. 1.2.4 -> 1.3.0) will usually require +further action.  You should read the `CHANGES.md` document to see +what's changed since your last deployment, paying special attention to +anything in the "Updgrading" sections. + +You should always run the script `scripts/rails-post-deploy` after +each deployment.  This runs any database migrations for you, plus +various other things that can be automated for deployment. +  # Troubleshooting  *   **Incoming emails aren't appearing in my Alaveteli install** diff --git a/lib/quiet_opener.rb b/lib/quiet_opener.rb new file mode 100644 index 000000000..a077ca323 --- /dev/null +++ b/lib/quiet_opener.rb @@ -0,0 +1,34 @@ +require 'open-uri' +require 'net-purge' + +def quietly_try_to_open(url) +    begin  +        result = open(url).read.strip +    rescue OpenURI::HTTPError, SocketError, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTUNREACH +        logger.warn("Unable to open third-party URL #{url}") +        result = "" +    end +    return result +end +     +def quietly_try_to_purge(host, url) +    begin  +        result = "" +        result_body = "" +        Net::HTTP.start(host) {|http| +            request = Net::HTTP::Purge.new(url) +            response = http.request(request) +            result = response.code +            result_body = response.body +        } +    rescue OpenURI::HTTPError, SocketError, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTUNREACH +        logger.warn("Unable to reach host #{host}") +    end +    if result == "200" +        logger.info("Purged URL #{url} at #{host}: #{result}") +    else +        logger.warn("Unable to purge URL #{url} at #{host}: status #{result}") +    end +    return result +end +     diff --git a/lib/tasks/rspec.rake b/lib/tasks/rspec.rake index c54d981e2..1eee74aee 100644 --- a/lib/tasks/rspec.rake +++ b/lib/tasks/rspec.rake @@ -1,6 +1,6 @@  rspec_gem_dir = nil -Dir["#{RAILS_ROOT}/vendor/gems/*"].each do |subdir| -  rspec_gem_dir = subdir if subdir.gsub("#{RAILS_ROOT}/vendor/gems/","") =~ /^(\w+-)?rspec-(\d+)/ && File.exist?("#{subdir}/lib/spec/rake/spectask.rb") +Dir["#{Rails.root}/vendor/gems/*"].each do |subdir| +  rspec_gem_dir = subdir if subdir.gsub("#{Rails.root}/vendor/gems/","") =~ /^(\w+-)?rspec-(\d+)/ && File.exist?("#{subdir}/lib/spec/rake/spectask.rb")  end  rspec_plugin_dir = File.expand_path(File.dirname(__FILE__) + '/../../vendor/plugins/rspec') @@ -46,7 +46,7 @@ end  Rake.application.instance_variable_get('@tasks').delete('default') -spec_prereq = File.exist?(File.join(RAILS_ROOT, 'config', 'database.yml')) ? "db:test:prepare" : :noop +spec_prereq = File.exist?(File.join(Rails.root, 'config', 'database.yml')) ? "db:test:prepare" : :noop  task :noop do  end @@ -59,18 +59,18 @@ task :cruise => ['spec']  desc "Run all specs in spec directory (excluding plugin specs)"  Spec::Rake::SpecTask.new(:spec => spec_prereq) do |t| -  t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] +  t.spec_opts = ['--options', "\"#{Rails.root}/spec/spec.opts\""]    t.spec_files = FileList['spec/**/*_spec.rb']  end  namespace :spec do    desc "Run all specs in spec directory with RCov (excluding plugin specs)"    Spec::Rake::SpecTask.new(:rcov) do |t| -    t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] +    t.spec_opts = ['--options', "\"#{Rails.root}/spec/spec.opts\""]      t.spec_files = FileList['spec/**/*_spec.rb']      t.rcov = true      t.rcov_opts = lambda do -      IO.readlines("#{RAILS_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten +      IO.readlines("#{Rails.root}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten      end    end @@ -89,21 +89,21 @@ namespace :spec do    [:models, :controllers, :views, :helpers, :lib, :integration].each do |sub|      desc "Run the code examples in spec/#{sub}"      Spec::Rake::SpecTask.new(sub => spec_prereq) do |t| -      t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] +      t.spec_opts = ['--options', "\"#{Rails.root}/spec/spec.opts\""]        t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]      end    end    desc "Run the code examples in vendor/plugins (except RSpec's own)"    Spec::Rake::SpecTask.new(:plugins => spec_prereq) do |t| -    t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] +    t.spec_opts = ['--options', "\"#{Rails.root}/spec/spec.opts\""]      t.spec_files = FileList['vendor/plugins/**/spec/**/*_spec.rb'].exclude('vendor/plugins/rspec/*').exclude("vendor/plugins/rspec-rails/*")    end    namespace :plugins do      desc "Runs the examples for rspec_on_rails"      Spec::Rake::SpecTask.new(:rspec_on_rails) do |t| -      t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] +      t.spec_opts = ['--options', "\"#{Rails.root}/spec/spec.opts\""]        t.spec_files = FileList['vendor/plugins/rspec-rails/spec/**/*_spec.rb']      end    end diff --git a/public/dispatch.fcgi b/public/dispatch.fcgi index 3169ba267..ad34d90e4 100755 --- a/public/dispatch.fcgi +++ b/public/dispatch.fcgi @@ -4,7 +4,7 @@  # exceptions which forced the FastCGI instance to exit, great for debugging)  # and the number of requests to process before running garbage collection.  # -# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# By default, the FastCGI crash log is Rails.root/log/fastcgi.crash.log  # and the GC period is nil (turned off).  A reasonable number of requests  # could range from 10-100 depending on the memory footprint of your app.  # diff --git a/public/javascripts/admin.coffee b/public/javascripts/admin.coffee new file mode 100644 index 000000000..59c5c0a6f --- /dev/null +++ b/public/javascripts/admin.coffee @@ -0,0 +1,10 @@ +(($) -> +  $(document).ready(-> +    $('.locales a:first').tab('show') +  ) +  $('.toggle-hidden').live('click', -> +    $(@).parents('td').find('div:hidden').show() +    false +  ) +)(jQuery) + diff --git a/public/javascripts/admin.js b/public/javascripts/admin.js new file mode 100644 index 000000000..21725ded4 --- /dev/null +++ b/public/javascripts/admin.js @@ -0,0 +1,14 @@ +// Generated by CoffeeScript 1.3.1 +(function() { + +  (function($) { +    $(document).ready(function() { +      return $('.locales a:first').tab('show'); +    }); +    return $('.toggle-hidden').live('click', function() { +      $(this).parents('td').find('div:hidden').show(); +      return false; +    }); +  })(jQuery); + +}).call(this); diff --git a/public/javascripts/bootstrap-collapse.js b/public/javascripts/bootstrap-collapse.js new file mode 100644 index 000000000..9a364468b --- /dev/null +++ b/public/javascripts/bootstrap-collapse.js @@ -0,0 +1,138 @@ +/* ============================================================= + * bootstrap-collapse.js v2.0.2 + * http://twitter.github.com/bootstrap/javascript.html#collapse + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + +!function( $ ){ + +  "use strict" + +  var Collapse = function ( element, options ) { +  	this.$element = $(element) +    this.options = $.extend({}, $.fn.collapse.defaults, options) + +    if (this.options["parent"]) { +      this.$parent = $(this.options["parent"]) +    } + +    this.options.toggle && this.toggle() +  } + +  Collapse.prototype = { + +    constructor: Collapse + +  , dimension: function () { +      var hasWidth = this.$element.hasClass('width') +      return hasWidth ? 'width' : 'height' +    } + +  , show: function () { +      var dimension = this.dimension() +        , scroll = $.camelCase(['scroll', dimension].join('-')) +        , actives = this.$parent && this.$parent.find('.in') +        , hasData + +      if (actives && actives.length) { +        hasData = actives.data('collapse') +        actives.collapse('hide') +        hasData || actives.data('collapse', null) +      } + +      this.$element[dimension](0) +      this.transition('addClass', 'show', 'shown') +      this.$element[dimension](this.$element[0][scroll]) + +    } + +  , hide: function () { +      var dimension = this.dimension() +      this.reset(this.$element[dimension]()) +      this.transition('removeClass', 'hide', 'hidden') +      this.$element[dimension](0) +    } + +  , reset: function ( size ) { +      var dimension = this.dimension() + +      this.$element +        .removeClass('collapse') +        [dimension](size || 'auto') +        [0].offsetWidth + +      this.$element[size ? 'addClass' : 'removeClass']('collapse') + +      return this +    } + +  , transition: function ( method, startEvent, completeEvent ) { +      var that = this +        , complete = function () { +            if (startEvent == 'show') that.reset() +            that.$element.trigger(completeEvent) +          } + +      this.$element +        .trigger(startEvent) +        [method]('in') + +      $.support.transition && this.$element.hasClass('collapse') ? +        this.$element.one($.support.transition.end, complete) : +        complete() +  	} + +  , toggle: function () { +      this[this.$element.hasClass('in') ? 'hide' : 'show']() +  	} + +  } + +  /* COLLAPSIBLE PLUGIN DEFINITION +  * ============================== */ + +  $.fn.collapse = function ( option ) { +    return this.each(function () { +      var $this = $(this) +        , data = $this.data('collapse') +        , options = typeof option == 'object' && option +      if (!data) $this.data('collapse', (data = new Collapse(this, options))) +      if (typeof option == 'string') data[option]() +    }) +  } + +  $.fn.collapse.defaults = { +    toggle: true +  } + +  $.fn.collapse.Constructor = Collapse + + + /* COLLAPSIBLE DATA-API +  * ==================== */ + +  $(function () { +    $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) { +      var $this = $(this), href +        , target = $this.attr('data-target') +          || e.preventDefault() +          || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 +        , option = $(target).data('collapse') ? 'toggle' : $this.data() +      $(target).collapse(option) +    }) +  }) + +}( window.jQuery );
\ No newline at end of file diff --git a/public/javascripts/bootstrap-tab.js b/public/javascripts/bootstrap-tab.js new file mode 100644 index 000000000..26c9ece75 --- /dev/null +++ b/public/javascripts/bootstrap-tab.js @@ -0,0 +1,130 @@ +/* ======================================================== + * bootstrap-tab.js v2.0.1 + * http://twitter.github.com/bootstrap/javascript.html#tabs + * ======================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================== */ + + +!function( $ ){ + +  "use strict" + + /* TAB CLASS DEFINITION +  * ==================== */ + +  var Tab = function ( element ) { +    this.element = $(element) +  } + +  Tab.prototype = { + +    constructor: Tab + +  , show: function () { +      var $this = this.element +        , $ul = $this.closest('ul:not(.dropdown-menu)') +        , selector = $this.attr('data-target') +        , previous +        , $target + +      if (!selector) { +        selector = $this.attr('href') +        selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 +      } + +      if ( $this.parent('li').hasClass('active') ) return + +      previous = $ul.find('.active a').last()[0] + +      $this.trigger({ +        type: 'show' +      , relatedTarget: previous +      }) + +      $target = $(selector) + +      this.activate($this.parent('li'), $ul) +      this.activate($target, $target.parent(), function () { +        $this.trigger({ +          type: 'shown' +        , relatedTarget: previous +        }) +      }) +    } + +  , activate: function ( element, container, callback) { +      var $active = container.find('> .active') +        , transition = callback +            && $.support.transition +            && $active.hasClass('fade') + +      function next() { +        $active +          .removeClass('active') +          .find('> .dropdown-menu > .active') +          .removeClass('active') + +        element.addClass('active') + +        if (transition) { +          element[0].offsetWidth // reflow for transition +          element.addClass('in') +        } else { +          element.removeClass('fade') +        } + +        if ( element.parent('.dropdown-menu') ) { +          element.closest('li.dropdown').addClass('active') +        } + +        callback && callback() +      } + +      transition ? +        $active.one($.support.transition.end, next) : +        next() + +      $active.removeClass('in') +    } +  } + + + /* TAB PLUGIN DEFINITION +  * ===================== */ + +  $.fn.tab = function ( option ) { +    return this.each(function () { +      var $this = $(this) +        , data = $this.data('tab') +      if (!data) $this.data('tab', (data = new Tab(this))) +      if (typeof option == 'string') data[option]() +    }) +  } + +  $.fn.tab.Constructor = Tab + + + /* TAB DATA-API +  * ============ */ + +  $(function () { +    $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { +      e.preventDefault() +      $(this).tab('show') +    }) +  }) + +}( window.jQuery );
\ No newline at end of file diff --git a/public/javascripts/jquery.js b/public/javascripts/jquery.js index 48590ecb9..16ad06c5a 100644 --- a/public/javascripts/jquery.js +++ b/public/javascripts/jquery.js @@ -1,18 +1,4 @@ -/*! - * jQuery JavaScript Library v1.6.2 - * http://jquery.com/ - * - * Copyright 2011, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Thu Jun 30 14:16:56 2011 -0400 - */ -(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function bZ(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function bY(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bC.test(a)?d(a,e):bY(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)bY(a+"["+e+"]",b[e],c,d);else d(a,b)}function bX(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bR,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bX(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bX(a,c,d,e,"*",g));return l}function bW(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bN),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bA(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bv:bw;if(d>0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bg(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function W(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(R.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(x,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(H)return H.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;a.setAttribute("className","t"),a.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]||i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:|^on/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(o);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(o);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(n," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=w:v&&c!=="className"&&(f.nodeName(a,"form")||u.test(c))&&(i=v)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}},value:{get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return f.prop(a,c)?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.attrHooks.title=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i. -shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,N(a.origType,a.selector),f.extend({},a,{handler:M,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,N(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?E:D):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=E;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=E;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=E,this.stopPropagation()},isDefaultPrevented:D,isPropagationStopped:D,isImmediatePropagationStopped:D};var F=function(a){var b=a.relatedTarget,c=!1,d=a.type;a.type=a.data,b!==this&&(b&&(c=f.contains(this,b)),c||(f.event.handle.apply(this,arguments),a.type=d))},G=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?G:F,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?G:F)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&K("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&K("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var H,I=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function(c){var d=c.target,e,g;if(!!y.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=I(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var L={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||D,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=x.exec(h),k="",j&&(k=j[0],h=h.replace(x,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,L[h]?(a.push(L[h]+k),h=h+k):h=(L[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+N(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+N(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var O=/Until$/,P=/^(?:parents|prevUntil|prevAll)/,Q=/,/,R=/^.[^:#\[\.,]*$/,S=Array.prototype.slice,T=f.expr.match.POS,U={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(W(this,a,!1),"not",a)},filter:function(a){return this.pushStack(W(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=T.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/<tbody/i,ba=/<|&#?\w+;/,bb=/<(?:script|object|embed|option|style)/i,bc=/checked\s*(?:[^=]|=\s*.checked.)/i,bd=/\/(java|ecma)script/i,be=/^\s*<!(?:\[CDATA\[|\-\-)/,bf={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};bf.optgroup=bf.option,bf.tbody=bf.tfoot=bf.colgroup=bf.caption=bf.thead,bf.th=bf.td,f.support.htmlSerialize||(bf._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!bf[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bc.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bg(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bm)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i;b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bb.test(a[0])&&(f.support.checkClone||!bc.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j -)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bi(a,d),e=bj(a),g=bj(d);for(h=0;e[h];++h)bi(e[h],g[h])}if(b){bh(a,d);if(c){e=bj(a),g=bj(d);for(h=0;e[h];++h)bh(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!ba.test(k))k=b.createTextNode(k);else{k=k.replace(Z,"<$1></$2>");var l=($.exec(k)||["",""])[1].toLowerCase(),m=bf[l]||bf._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=_.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Y.test(k)&&o.insertBefore(b.createTextNode(Y.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bl(k[i]);else bl(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||bd.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bn=/alpha\([^)]*\)/i,bo=/opacity=([^)]*)/,bp=/([A-Z]|^ms)/g,bq=/^-?\d+(?:px)?$/i,br=/^-?\d/,bs=/^[+\-]=/,bt=/[^+\-\.\de]+/g,bu={position:"absolute",visibility:"hidden",display:"block"},bv=["Left","Right"],bw=["Top","Bottom"],bx,by,bz;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bx(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bs.test(d)&&(d=+d.replace(bt,"")+parseFloat(f.css(a,c)),h="number"),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bx)return bx(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bA(a,b,d);f.swap(a,bu,function(){e=bA(a,b,d)});return e}},set:function(a,b){if(!bq.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bo.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bn.test(g)?g.replace(bn,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bB=/%20/g,bC=/\[\]$/,bD=/\r?\n/g,bE=/#.*$/,bF=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bG=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bH=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bI=/^(?:GET|HEAD)$/,bJ=/^\/\//,bK=/\?/,bL=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bM=/^(?:select|textarea)/i,bN=/\s+/,bO=/([?&])_=[^&]*/,bP=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bQ=f.fn.load,bR={},bS={},bT,bU;try{bT=e.href}catch(bV){bT=c.createElement("a"),bT.href="",bT=bT.href}bU=bP.exec(bT.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bQ)return bQ.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bL,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bM.test(this.nodeName)||bG.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bD,"\r\n")}}):{name:b.name,value:c.replace(bD,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bT,isLocal:bH.test(bU[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bW(bR),ajaxTransport:bW(bS),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?bZ(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b$(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bF.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bE,"").replace(bJ,bU[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bN),d.crossDomain==null&&(r=bP.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bU[1]&&r[2]==bU[2]&&(r[3]||(r[1]==="http:"?80:443))==(bU[3]||(bU[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bX(bR,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bI.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bK.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bO,"$1_="+x);d.url=y+(y===d.url?(bK.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bX(bS,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bB,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn,co=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cr("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cs(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cr("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cr("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cs(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cj.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=ck.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cr("show",1),slideUp:cr("hide",1),slideToggle:cr("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cn||cp(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!cl&&(co?(cl=!0,g=function(){cl&&(co(g),e.tick())},co(g)):cl=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cn||cp(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cl),cl=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var ct=/^t(?:able|d|h)$/i,cu=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cv(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!ct.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cu.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cu.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cv(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cv(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
\ No newline at end of file +/*! jQuery v1.7.2 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"<!doctype html>":"")+"<html><body>"),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bD.test(a)?d(a,e):b_(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&f.type(b)==="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function bZ(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bS,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bZ(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bZ(a,c,d,e,"*",g));return l}function bY(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bO),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bB(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?1:0,g=4;if(d>0){if(c!=="border")for(;e<g;e+=2)c||(d-=parseFloat(f.css(a,"padding"+bx[e]))||0),c==="margin"?d+=parseFloat(f.css(a,c+bx[e]))||0:d-=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0;return d+"px"}d=by(a,b);if(d<0||d==null)d=a.style[b];if(bt.test(d))return d;d=parseFloat(d)||0;if(c)for(;e<g;e+=2)d+=parseFloat(f.css(a,"padding"+bx[e]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+bx[e]))||0);return d+"px"}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;b.nodeType===1&&(b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?b.outerHTML=a.outerHTML:c!=="input"||a.type!=="checkbox"&&a.type!=="radio"?c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text):(a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value)),b.removeAttribute(f.expando),b.removeAttribute("_submit_attached"),b.removeAttribute("_change_attached"))}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c,i[c][d])}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h,i){var j,k=d==null,l=0,m=a.length;if(d&&typeof d=="object"){for(l in d)e.access(a,c,l,d[l],1,h,f);g=1}else if(f!==b){j=i===b&&e.isFunction(f),k&&(j?(j=c,c=function(a,b,c){return j.call(e(a),c)}):(c.call(a,f),c=null));if(c)for(;l<m;l++)c(a[l],d,j?f.call(a[l],l,c(a[l],d)):f,i);g=1}return g?a:k?c.call(a):m?c(a[0],d):h},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m,n=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?n(g):h==="function"&&(!a.unique||!p.has(g))&&c.push(g)},o=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,j=!0,m=k||0,k=0,l=c.length;for(;c&&m<l;m++)if(c[m].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}j=!1,c&&(a.once?e===!0?p.disable():c=[]:d&&d.length&&(e=d.shift(),p.fireWith(e[0],e[1])))},p={add:function(){if(c){var a=c.length;n(arguments),j?l=c.length:e&&e!==!0&&(k=a,o(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){j&&f<=l&&(l--,f<=m&&m--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&p.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(j?a.once||d.push([b,c]):(!a.once||!e)&&o(b,c));return this},fire:function(){p.fireWith(this,arguments);return this},fired:function(){return!!i}};return p};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p=c.createElement("div"),q=c.documentElement;p.setAttribute("className","t"),p.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="<div "+n+"display:block;'><div style='"+t+"0;display:block;overflow:hidden;'></div></div>"+"<table "+n+"' cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="<table><tr><td style='"+t+"0;display:none'></td><td>t</td></tr></table>",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="<div style='width:5px;'></div>",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h,i,j=this[0],k=0,m=null;if(a===b){if(this.length){m=f.data(j);if(j.nodeType===1&&!f._data(j,"parsedAttrs")){g=j.attributes;for(i=g.length;k<i;k++)h=g[k].name,h.indexOf("data-")===0&&(h=f.camelCase(h.substring(5)),l(j,h,m[h]));f._data(j,"parsedAttrs",!0)}}return m}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!";return f.access(this,function(c){if(c===b){m=this.triggerHandler("getData"+e,[d[0]]),m===b&&j&&(m=f.data(j,a),m=l(j,a,m));return m===b&&d[1]?this.data(d[0]):m}d[1]=c,this.each(function(){var b=f(this);b.triggerHandler("setData"+e,d),f.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length<d)return f.queue(this[0],a);return c===b?this:this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise(c)}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,f.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i<g;i++)e=d[i],e&&(c=f.propFix[e]||e,h=u.test(e),h||f.attr(a,e,""),a.removeAttribute(v?e:c),h&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0,coords:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( +a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:g&&G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=f.event.special[c.type]||{},j=[],k,l,m,n,o,p,q,r,s,t,u;g[0]=c,c.delegateTarget=this;if(!i.preDispatch||i.preDispatch.call(this,c)!==!1){if(e&&(!c.button||c.type!=="click")){n=f(this),n.context=this.ownerDocument||this;for(m=c.target;m!=this;m=m.parentNode||this)if(m.disabled!==!0){p={},r=[],n[0]=m;for(k=0;k<e;k++)s=d[k],t=s.selector,p[t]===b&&(p[t]=s.quick?H(m,s.quick):n.is(t)),p[t]&&r.push(s);r.length&&j.push({elem:m,matches:r})}}d.length>e&&j.push({elem:this,matches:d.slice(e)});for(k=0;k<j.length&&!c.isPropagationStopped();k++){q=j[k],c.currentTarget=q.elem;for(l=0;l<q.matches.length&&!c.isImmediatePropagationStopped();l++){s=q.matches[l];if(h||!c.namespace&&!s.namespace||c.namespace_re&&c.namespace_re.test(s.namespace))c.data=s.data,c.handleObj=s,o=((f.event.special[s.origType]||{}).handle||s.handler).apply(q.elem,g),o!==b&&(c.result=o,o===!1&&(c.preventDefault(),c.stopPropagation()))}}i.postDispatch&&i.postDispatch.call(this,c);return c.result}},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),d._submit_attached=!0)})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9||d===11){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.globalPOS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")[\\s/>]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f +.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(f.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(g){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,function(a,b){b.src?f.ajax({type:"GET",global:!1,url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1></$2>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]==="<table>"&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i<u;i++)bn(l[i]);else bn(l);l.nodeType?j.push(l):j=f.merge(j,l)}if(d){g=function(a){return!a.type||be.test(a.type)};for(k=0;j[k];k++){h=j[k];if(e&&f.nodeName(h,"script")&&(!h.type||be.test(h.type)))e.push(h.parentNode?h.parentNode.removeChild(h):h);else{if(h.nodeType===1){var v=f.grep(h.getElementsByTagName("script"),g);j.splice.apply(j,[k+1,0].concat(v))}d.appendChild(h)}}}return j},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bp=/alpha\([^)]*\)/i,bq=/opacity=([^)]*)/,br=/([A-Z]|^ms)/g,bs=/^[\-+]?(?:\d*\.)?\d+$/i,bt=/^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,bu=/^([\-+])=([\-+.\de]+)/,bv=/^margin/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Top","Right","Bottom","Left"],by,bz,bA;f.fn.css=function(a,c){return f.access(this,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)},a,c,arguments.length>1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),(e===""&&f.css(d,"display")==="none"||!f.contains(d.ownerDocument.documentElement,d))&&f._data(d,"olddisplay",cu(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(ct("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(ct("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o,p,q;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]);if((k=f.cssHooks[g])&&"expand"in k){l=k.expand(a[g]),delete a[g];for(i in l)i in a||(a[i]=l[i])}}for(g in a){h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cu(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cm.test(h)?(q=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),q?(f._data(this,"toggle"+i,q==="show"?"hide":"show"),j[q]()):j[h]()):(m=cn.exec(h),n=j.cur(),m?(o=parseFloat(m[2]),p=m[3]||(f.cssNumber[i]?"":"px"),p!=="px"&&(f.style(this,i,(o||1)+p),n=(o||1)/j.cur()*n,f.style(this,i,n+p)),m[1]&&(o=(m[1]==="-="?-1:1)*o+n),j.custom(n,o,p)):j.custom(n,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:ct("show",1),slideUp:ct("hide",1),slideToggle:ct("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a){return a},swing:function(a){return-Math.cos(a*Math.PI)/2+.5}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cq||cr(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){f._data(e.elem,"fxshow"+e.prop)===b&&(e.options.hide?f._data(e.elem,"fxshow"+e.prop,e.start):e.options.show&&f._data(e.elem,"fxshow"+e.prop,e.end))},h()&&f.timers.push(h)&&!co&&(co=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cq||cr(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(cp.concat.apply([],cp),function(a,b){b.indexOf("margin")&&(f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)})}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cv,cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?cv=function(a,b,c,d){try{d=a.getBoundingClientRect()}catch(e){}if(!d||!f.contains(c,a))return d?{top:d.top,left:d.left}:{top:0,left:0};var g=b.body,h=cy(b),i=c.clientTop||g.clientTop||0,j=c.clientLeft||g.clientLeft||0,k=h.pageYOffset||f.support.boxModel&&c.scrollTop||g.scrollTop,l=h.pageXOffset||f.support.boxModel&&c.scrollLeft||g.scrollLeft,m=d.top+k-i,n=d.left+l-j;return{top:m,left:n}}:cv=function(a,b,c){var d,e=a.offsetParent,g=a,h=b.body,i=b.defaultView,j=i?i.getComputedStyle(a,null):a.currentStyle,k=a.offsetTop,l=a.offsetLeft;while((a=a.parentNode)&&a!==h&&a!==c){if(f.support.fixedPosition&&j.position==="fixed")break;d=i?i.getComputedStyle(a,null):a.currentStyle,k-=a.scrollTop,l-=a.scrollLeft,a===e&&(k+=a.offsetTop,l+=a.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(a.nodeName))&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),g=e,e=a.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),j=d}if(j.position==="relative"||j.position==="static")k+=h.offsetTop,l+=h.offsetLeft;f.support.fixedPosition&&j.position==="fixed"&&(k+=Math.max(c.scrollTop,h.scrollTop),l+=Math.max(c.scrollLeft,h.scrollLeft));return{top:k,left:l}},f.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){f.offset.setOffset(this,a,b)});var c=this[0],d=c&&c.ownerDocument;if(!d)return null;if(c===d.body)return f.offset.bodyOffset(c);return cv(c,d,d.documentElement)},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt index 6a8628c93..a187d0775 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -13,17 +13,18 @@  #   http://help.yahoo.com/l/us/yahoo/search/webcrawler/slurp-02.html  User-agent: * -Disallow: /annotate/ -Disallow: /new/ -Disallow: /search/ -Disallow: /similar/ -Disallow: /track/ -Disallow: /upload/ -Disallow: /user/contact/ -Disallow: /feed/ -Disallow: /profile/ -Disallow: /signin -Disallow: /body/*/view_email$ +Disallow: */annotate/ +Disallow: */new/ +Disallow: */search/ +Disallow: */similar/ +Disallow: */track/ +Disallow: */upload/ +Disallow: */user/contact/ +Disallow: */feed/ +Disallow: */profile/ +Disallow: */signin +Disallow: */request/*/response/ +Disallow: */body/*/view_email$  # The following adding Jan 2012 to stop robots crawling pages  # generated in error (see diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css index 87d3a3eed..0c2239409 100644 --- a/public/stylesheets/main.css +++ b/public/stylesheets/main.css @@ -41,7 +41,11 @@ border-width:1px;  }  #navigation_search input#navigation_search_query { -width:14em; +width:20.25em; +font-size: 0.8em; +padding: 5px; +margin: -1px -1px 0 0; +  }  #navigation_search p { @@ -307,7 +311,7 @@ color:#A68C2E;  background-image:url(../images/navimg/status-icons-fail.png);  } -.icon_not_held { +.icon_not_held, .icon_attention_requested {  background-image:url(/images/status-not-held.png);  color:#A68C2E;  } @@ -636,13 +640,15 @@ margin-top:-1em;  margin:0 0 0 9em;  } -p#sign_in_reason { +p#sign_in_reason, p#superuser_message {  text-align:center;  font-size:1.4em;  font-weight:700;  line-height:1em;  } - +p#superuser_message { + font-size:1.2em; +}  #signup,#signin {  clear:none;  margin-bottom:1em; @@ -910,7 +916,7 @@ border:none;  #navigation_search input[type=image] {  border:0; -margin-bottom:-8px; +margin-bottom:-9px;  margin-left:-4px;  } diff --git a/script/about b/script/about index f2b98742d..49a7c2609 100755 --- a/script/about +++ b/script/about @@ -1,3 +1,3 @@  #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot.rb' +require File.expand_path(File.dirname(__FILE__) + '/../config/boot.rb')  require 'commands/about' diff --git a/script/annotate-models b/script/annotate-models new file mode 100755 index 000000000..b6e01c010 --- /dev/null +++ b/script/annotate-models @@ -0,0 +1,5 @@ +#!/bin/bash +# +# annotates the models in app/ with schema information + +bundle exec annotate -m -p before --exclude tests,fixtures diff --git a/script/breakpointer b/script/breakpointer index 609564148..46a01d1b2 100755 --- a/script/breakpointer +++ b/script/breakpointer @@ -1,3 +1,3 @@  #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot.rb' +require File.expand_path(File.dirname(__FILE__) + '/../config/boot.rb')  require 'commands/breakpointer' diff --git a/script/dbconsole b/script/dbconsole index c25c5afcd..39042fad3 100755 --- a/script/dbconsole +++ b/script/dbconsole @@ -1,3 +1,3 @@  #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot.rb' +require File.expand_path(File.dirname(__FILE__) + '/../config/boot.rb')  require 'commands/dbconsole' diff --git a/script/destroy b/script/destroy index e63ac0ef5..fddc5160d 100755 --- a/script/destroy +++ b/script/destroy @@ -1,3 +1,3 @@  #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot.rb' +require File.expand_path(File.dirname(__FILE__) + '/../config/boot.rb')  require 'commands/destroy' diff --git a/script/generate b/script/generate index 8c0486a09..fb8139d12 100755 --- a/script/generate +++ b/script/generate @@ -1,3 +1,3 @@  #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot.rb' +require File.expand_path(File.dirname(__FILE__) + '/../config/boot.rb')  require 'commands/generate' diff --git a/script/generate_pot.sh b/script/generate_pot.sh index f6c82dda7..c0540c3d9 100755 --- a/script/generate_pot.sh +++ b/script/generate_pot.sh @@ -10,8 +10,8 @@ git status | grep app.po | awk '{print $3}' | xargs git add  git commit -m "Backup latest po files from Transifex"  # now regenerate POT and PO files from Alaveteli source -rake gettext:store_model_attributes -rake gettext:findpot +bundle exec rake gettext:store_model_attributes +bundle exec rake gettext:findpot  # upload the result to Transifex  tx push -t  diff --git a/script/handle-mail-replies b/script/handle-mail-replies index d6717bc58..7590f5848 100755 --- a/script/handle-mail-replies +++ b/script/handle-mail-replies @@ -43,9 +43,22 @@ def main(in_test_mode)              return 1          end -        if is_oof? message -            # Discard out-of-office messages -            return 2 +        # If we are still here, there are no permanent failures, +        # so if the message is a multipart/report then it must be +        # reporting a temporary failure. In this case we discard it +        if message.content_type == "multipart/report" +          return 1 +        end +         +        # Another style of temporary failure message +        subject = message.header_string("Subject") +        if message.content_type == "multipart/mixed" && subject == "Delivery Status Notification (Delay)" +          return 1 +        end +         +        # Discard out-of-office messages +        if is_oof?(message) +            return 2 # Use a different return code, to distinguish OOFs from bounces          end          # Otherwise forward the message on @@ -94,6 +107,15 @@ def permanently_failed_addresses(message)          end      end +    subject = message.header_string("Subject") +    # Then look for the style we’ve seen in WebShield bounces +    # (These do not have a return path of <> in the cases I have seen.) +    if subject == "Returned Mail: Error During Delivery" +      if message.body =~ /^\s*---- Failed Recipients ----\s*((?:<[^>]+>\n)+)/ +        return $1.scan(/<([^>]+)>/).flatten +      end +    end +      return []  end @@ -145,7 +167,7 @@ end  def load_rails      require File.join('config', 'boot') -    require RAILS_ROOT + '/config/environment' +    require Rails.root + '/config/environment'  end  def record_bounce(email_address, bounce_message) diff --git a/script/load-sample-data b/script/load-sample-data index e5f1be4cd..86e1af128 100755 --- a/script/load-sample-data +++ b/script/load-sample-data @@ -6,7 +6,7 @@  LOC=`dirname "$0"` -rake --silent spec:db:fixtures:load +bundle exec rake --silent spec:db:fixtures:load  "$LOC/runner" /dev/stdin <<END  env = ENV["RAILS_ENV"] diff --git a/script/performance/benchmarker b/script/performance/benchmarker index a94253aba..28bfceea0 100755 --- a/script/performance/benchmarker +++ b/script/performance/benchmarker @@ -1,3 +1,3 @@  #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../config/boot.rb' +require File.expand_path(File.dirname(__FILE__) + '/../config/boot.rb')  require 'commands/performance/benchmarker' diff --git a/script/performance/profiler b/script/performance/profiler index e9e5b071d..11baf44f2 100755 --- a/script/performance/profiler +++ b/script/performance/profiler @@ -1,3 +1,3 @@  #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../config/boot.rb' +require File.expand_path(File.dirname(__FILE__) + '/../config/boot.rb')  require 'commands/performance/profiler' diff --git a/script/performance/request b/script/performance/request index 658c80ef2..fff6fe660 100755 --- a/script/performance/request +++ b/script/performance/request @@ -1,3 +1,3 @@  #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../config/boot.rb' +require File.expand_path(File.dirname(__FILE__) + '/../config/boot.rb')  require 'commands/performance/request' diff --git a/script/plugin b/script/plugin index 18ae72620..49f5c441f 100755 --- a/script/plugin +++ b/script/plugin @@ -1,3 +1,3 @@  #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot.rb' +require File.expand_path(File.dirname(__FILE__) + '/../config/boot.rb')  require 'commands/plugin' diff --git a/script/process/inspector b/script/process/inspector index 696551c6b..467962602 100755 --- a/script/process/inspector +++ b/script/process/inspector @@ -1,3 +1,3 @@  #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../config/boot.rb' +require File.expand_path(File.dirname(__FILE__) + '/../config/boot.rb')  require 'commands/process/inspector' diff --git a/script/process/reaper b/script/process/reaper index a03da9387..2eea898db 100755 --- a/script/process/reaper +++ b/script/process/reaper @@ -1,3 +1,3 @@  #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../config/boot.rb' +require File.expand_path(File.dirname(__FILE__) + '/../config/boot.rb')  require 'commands/process/reaper' diff --git a/script/process/spawner b/script/process/spawner index 6852fba27..ac417c3a7 100755 --- a/script/process/spawner +++ b/script/process/spawner @@ -1,3 +1,3 @@  #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../config/boot.rb' +require File.expand_path(File.dirname(__FILE__) + '/../config/boot.rb')  require 'commands/process/spawner' diff --git a/script/purge-varnish b/script/purge-varnish new file mode 100755 index 000000000..932cf6635 --- /dev/null +++ b/script/purge-varnish @@ -0,0 +1,11 @@ +#!/bin/bash + +LOC=`dirname $0` + +if [ "$1" == "--loop" ] +then +  "$LOC/runner" 'PurgeRequest.purge_all_loop' +else +  "$LOC/runner" 'PurgeRequest.purge_all' +fi + diff --git a/script/rails-post-deploy b/script/rails-post-deploy index 6e2c88d28..575b995f9 100755 --- a/script/rails-post-deploy +++ b/script/rails-post-deploy @@ -76,17 +76,28 @@ fi  if [ "$OPTION_STAGING_SITE" = "0" ]  then -    bundle install  --without development:test --deployment +    bundle exec bundle install  --without development:test --deployment  else -    bundle install +    bundle exec bundle install  fi +if [ -n "$OPTION_THEME_URLS" ] +then +    for THEME in "${OPTION_THEME_URLS[@]}" +    do +	echo "Installing $THEME..." +	script/plugin install --force $THEME +    done +fi + +# Old version of the above, for backwards compatibility  if [ -n "$OPTION_THEME_URL" ]  then +        echo "Installing $OPTION_THEME_URL using deprecated THEME_URL..."  	script/plugin install --force $OPTION_THEME_URL  fi  # upgrade database -rake db:migrate #--trace +bundle exec rake db:migrate #--trace diff --git a/script/rebuild-xapian-index b/script/rebuild-xapian-index index 5986f5259..3012511de 100755 --- a/script/rebuild-xapian-index +++ b/script/rebuild-xapian-index @@ -1,4 +1,4 @@  #!/bin/bash  cd `dirname $0` -rake --silent "$@" xapian:rebuild_index models="PublicBody User InfoRequestEvent" +bundle exec rake --silent "$@" xapian:rebuild_index models="PublicBody User InfoRequestEvent" diff --git a/script/spec-all-pairs b/script/spec-all-pairs index 5b6439a4e..6d7bb17c4 100755 --- a/script/spec-all-pairs +++ b/script/spec-all-pairs @@ -6,7 +6,7 @@  log_file=/dev/null  test_pair () { -    rake db:test:prepare > /dev/null 2>&1 +    bundle exec rake db:test:prepare > /dev/null 2>&1      output=$(script/spec "$1" "$2" 2>&1)      if [ $? -eq 0 ]      then diff --git a/script/spec_server b/script/spec_server index 1e839355f..dfdf8ff6c 100755 --- a/script/spec_server +++ b/script/spec_server @@ -41,7 +41,7 @@ module Spec          load File.dirname(__FILE__) + '/../spec/spec_helper.rb'          if in_memory_database? -          load "#{RAILS_ROOT}/db/schema.rb" # use db agnostic schema by default +          load "#{Rails.root}/db/schema.rb" # use db agnostic schema by default            ActiveRecord::Migrator.up('db/migrate') # use migrations          end @@ -80,7 +80,7 @@ def daemonize(pid_file = nil)    return yield if $DEBUG    pid = Process.fork{      Process.setsid -    Dir.chdir(RAILS_ROOT) +    Dir.chdir(Rails.root)      trap("SIGINT"){ exit! 0 }      trap("SIGTERM"){ exit! 0 }      trap("SIGHUP"){ restart_test_server } diff --git a/script/test-run b/script/test-run index 4c7a0e3ec..7810b57d5 100755 --- a/script/test-run +++ b/script/test-run @@ -1,5 +1,5 @@  #!/bin/bash  cd ../ -rake spec +bundle exec rake spec diff --git a/script/update-xapian-index b/script/update-xapian-index index 6ece02de0..e365e2fec 100755 --- a/script/update-xapian-index +++ b/script/update-xapian-index @@ -1,5 +1,5 @@  #!/bin/bash  cd `dirname $0` -rake --silent xapian:update_index "$@" +bundle exec rake --silent xapian:update_index "$@" diff --git a/spec/controllers/admin_censor_rule_controller_spec.rb b/spec/controllers/admin_censor_rule_controller_spec.rb new file mode 100644 index 000000000..8893a858b --- /dev/null +++ b/spec/controllers/admin_censor_rule_controller_spec.rb @@ -0,0 +1,19 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe AdminCensorRuleController, "when making censor rules from the admin interface" do +    integrate_views +    before { basic_auth_login @request } +   +    it "should create a censor rule and purge the corresponding request from varnish" do +        ir = info_requests(:fancy_dog_request)  +        post :create, :censor_rule => { +                         :text => "meat", +                         :replacement => "tofu", +                         :last_edit_comment => "none", +                         :info_request => ir +        } +        PurgeRequest.all().first.model_id.should == ir.id +    end + + +end diff --git a/spec/controllers/admin_public_body_controller_spec.rb b/spec/controllers/admin_public_body_controller_spec.rb index 1e82a0ba4..171cb21b5 100644 --- a/spec/controllers/admin_public_body_controller_spec.rb +++ b/spec/controllers/admin_public_body_controller_spec.rb @@ -4,10 +4,6 @@ describe AdminPublicBodyController, "when administering public bodies" do      integrate_views      before do -        username = MySociety::Config.get('ADMIN_USERNAME', '') -        password = MySociety::Config.get('ADMIN_PASSWORD', '') -        basic_auth_login @request -                  @old_filters = ActionController::Routing::Routes.filters          ActionController::Routing::Routes.filters = RoutingFilter::Chain.new      end @@ -80,19 +76,29 @@ describe AdminPublicBodyController, "when administering public bodies and paying      integrate_views +    before do +        config = MySociety::Config.load_default() +        config['SKIP_ADMIN_AUTH'] = false +        basic_auth_login @request    +    end +    after do +        config = MySociety::Config.load_default() +        config['SKIP_ADMIN_AUTH'] = true +    end + +      it "disallows non-authenticated users to do anything" do          @request.env["HTTP_AUTHORIZATION"] = ""          n = PublicBody.count          post :destroy, { :id => 3 } -        response.code.should == "401" +        response.should redirect_to(:controller=>'user', :action=>'signin', :token=>PostRedirect.get_last_post_redirect.token)          PublicBody.count.should == n          session[:using_admin].should == nil      end -    it "skips admin authorisation when no username/password set" do +    it "skips admin authorisation when SKIP_ADMIN_AUTH set" do          config = MySociety::Config.load_default() -        config['ADMIN_USERNAME'] = '' -        config['ADMIN_PASSWORD'] = '' +        config['SKIP_ADMIN_AUTH'] = true          @request.env["HTTP_AUTHORIZATION"] = ""          n = PublicBody.count @@ -101,30 +107,44 @@ describe AdminPublicBodyController, "when administering public bodies and paying          session[:using_admin].should == 1      end -    it "skips admin authorisation when no username set" do +    it "doesn't let people with bad credentials log in" do          config = MySociety::Config.load_default() -        config['ADMIN_USERNAME'] = '' +        config['SKIP_ADMIN_AUTH'] = false +        config['ADMIN_USERNAME'] = 'biz'          config['ADMIN_PASSWORD'] = 'fuz'          @request.env["HTTP_AUTHORIZATION"] = "" -                  n = PublicBody.count +        basic_auth_login(@request, "baduser", "badpassword")          post :destroy, { :id => public_bodies(:forlorn_public_body).id } -        PublicBody.count.should == n - 1 -        session[:using_admin].should == 1 +        response.should redirect_to(:controller=>'user', :action=>'signin', :token=>PostRedirect.get_last_post_redirect.token) +        PublicBody.count.should == n +        session[:using_admin].should == nil      end -    it "forces authorisation when password and username set" do + +    it "allows people with good credentials log in using HTTP Basic Auth" do          config = MySociety::Config.load_default() +        config['SKIP_ADMIN_AUTH'] = false          config['ADMIN_USERNAME'] = 'biz'          config['ADMIN_PASSWORD'] = 'fuz'          @request.env["HTTP_AUTHORIZATION"] = ""          n = PublicBody.count -        basic_auth_login(@request, "baduser", "badpassword") +        basic_auth_login(@request, "biz", "fuz") +        post :show, { :id => public_bodies(:humpadink_public_body).id, :emergency => 1} +        session[:using_admin].should == 1 +        n = PublicBody.count          post :destroy, { :id => public_bodies(:forlorn_public_body).id } -        response.code.should == "401" -        PublicBody.count.should == n -        session[:using_admin].should == nil +        session[:using_admin].should == 1 +        PublicBody.count.should == n - 1      end +    it "allows superusers to do stuff" do +        session[:user_id] = users(:admin_user).id +        @request.env["HTTP_AUTHORIZATION"] = "" +        n = PublicBody.count +        post :destroy, { :id => public_bodies(:forlorn_public_body).id } +        PublicBody.count.should == n - 1 +        session[:using_admin].should == 1 +    end  end @@ -132,12 +152,6 @@ end  describe AdminPublicBodyController, "when administering public bodies with i18n" do      integrate_views -    before do -        username = MySociety::Config.get('ADMIN_USERNAME', '') -        password = MySociety::Config.get('ADMIN_PASSWORD', '') -        basic_auth_login @request -    end -      it "shows the index page" do          get :index      end @@ -201,10 +215,6 @@ describe AdminPublicBodyController, "when creating public bodies with i18n" do      integrate_views      before do -        username = MySociety::Config.get('ADMIN_USERNAME', '') -        password = MySociety::Config.get('ADMIN_PASSWORD', '') -        basic_auth_login @request -                  @old_filters = ActionController::Routing::Routes.filters          ActionController::Routing::Routes.filters = RoutingFilter::Chain.new      end diff --git a/spec/controllers/admin_user_controller_spec.rb b/spec/controllers/admin_user_controller_spec.rb index 60ac6969d..cf3665c9f 100644 --- a/spec/controllers/admin_user_controller_spec.rb +++ b/spec/controllers/admin_user_controller_spec.rb @@ -2,9 +2,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')  describe AdminUserController, "when administering users" do      integrate_views -    before do -        basic_auth_login @request -    end      it "shows the index/list page" do          get :index @@ -24,13 +21,7 @@ describe AdminUserController, "when administering users" do          post_redirect = PostRedirect.get_last_post_redirect          response.should redirect_to(:controller => 'user', :action => 'confirm', :email_token => post_redirect.email_token)      end - -    it "logs in as another user when already logged in as an admin" do -        session[:user_id] = users(:admin_user).id -        get :login_as,  :id => users(:bob_smith_user).id -        post_redirect = PostRedirect.get_last_post_redirect -        response.should redirect_to(:controller => 'user', :action => 'confirm', :email_token => post_redirect.email_token) -        session[:user_id].should be_nil -    end +     +    # See also "allows an admin to log in as another user" in spec/integration/admin_spec.rb  end diff --git a/spec/controllers/general_controller_spec.rb b/spec/controllers/general_controller_spec.rb index 81f4ed6d5..8a08ab7d0 100644 --- a/spec/controllers/general_controller_spec.rb +++ b/spec/controllers/general_controller_spec.rb @@ -215,5 +215,10 @@ describe GeneralController, "when searching" do          assigns[:xapian_users].results.map{|x|x[:model]}.should == [u]      end +    it "should show tracking links for requests-only searches" do +        get :search, :combined => ['"bob"', "requests"] +        response.body.should include('Track this search')  +    end +  end diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb index f50158ff9..c70284748 100644 --- a/spec/controllers/request_controller_spec.rb +++ b/spec/controllers/request_controller_spec.rb @@ -64,6 +64,14 @@ describe RequestController, "when listing recent requests" do          assigns[:cache_tag].size.should <= 32      end +    it "should vary the cache tag with locale" do +        get :list, :view => 'all', :request_date_after => '13/10/2007', :request_date_before => '01/11/2007' +        en_tag = assigns[:cache_tag] +        session[:locale] = :es +        get :list, :view => 'all', :request_date_after => '13/10/2007', :request_date_before => '01/11/2007' +        assigns[:cache_tag].should_not == en_tag +    end +      it "should list internal_review requests as unresolved ones" do          get :list, :view => 'awaiting' @@ -117,10 +125,72 @@ describe RequestController, "when listing recent requests" do  end +describe RequestController, "when changing things that appear on the request page" do + +    integrate_views + +    it "should purge the downstream cache when mail is received" do +        ir = info_requests(:fancy_dog_request) +        receive_incoming_mail('incoming-request-plain.email', ir.incoming_email) +        PurgeRequest.all().first.model_id.should == ir.id +    end +    it "should purge the downstream cache when a comment is added" do +        ir = info_requests(:fancy_dog_request) +        new_comment = info_requests(:fancy_dog_request).add_comment('I also love making annotations.', users(:bob_smith_user)) +        PurgeRequest.all().first.model_id.should == ir.id +    end +    it "should purge the downstream cache when a followup is made" do +        session[:user_id] = users(:bob_smith_user).id +        ir = info_requests(:fancy_dog_request) +        post :show_response, :outgoing_message => { :body => "What a useless response! You suck.", :what_doing => 'normal_sort' }, :id => ir.id, :incoming_message_id => incoming_messages(:useless_incoming_message), :submitted_followup => 1 +        PurgeRequest.all().first.model_id.should == ir.id +    end +    it "should purge the downstream cache when the request is categorised" do +        ir = info_requests(:fancy_dog_request) +        ir.set_described_state('waiting_clarification') +        PurgeRequest.all().first.model_id.should == ir.id +    end +    it "should purge the downstream cache when the authority data is changed" do +        ir = info_requests(:fancy_dog_request) +        ir.public_body.name = "Something new" +        ir.public_body.save! +        PurgeRequest.all().map{|x| x.model_id}.should =~ ir.public_body.info_requests.map{|x| x.id} +    end +    it "should purge the downstream cache when the user details are changed" do +        ir = info_requests(:fancy_dog_request) +        ir.user.name = "Something new" +        ir.user.save! +        PurgeRequest.all().map{|x| x.model_id}.should =~ ir.user.info_requests.map{|x| x.id} +    end +    it "should purge the downstream cache when censor rules have changed" do +        # XXX really, CensorRules should execute expiry logic as part +        # of the after_save of the model. Currently this is part of +        # the AdminCensorRuleController logic, so must be tested from +        # there. Leaving this stub test in place as a reminder         +    end +    it "should purge the downstream cache when something is hidden by an admin" do +        ir = info_requests(:fancy_dog_request) +        ir.prominence = 'hidden' +        ir.save! +        PurgeRequest.all().first.model_id.should == ir.id +    end +    it "should not create more than one entry for any given resourcce" do +        ir = info_requests(:fancy_dog_request) +        ir.prominence = 'hidden' +        ir.save! +        PurgeRequest.all().count.should == 1 +        ir = info_requests(:fancy_dog_request) +        ir.prominence = 'hidden' +        ir.save! +        PurgeRequest.all().count.should == 1 +    end +end +  describe RequestController, "when showing one request" do      before(:each) do          load_raw_emails_data +        FileUtils.rm_rf File.join(File.dirname(__FILE__), "../../cache/zips")      end      it "should be successful" do @@ -184,7 +254,7 @@ describe RequestController, "when showing one request" do      describe 'when handling incoming mail' do           integrate_views -         +          it "should receive incoming messages, send email to creator, and show them" do              ir = info_requests(:fancy_dog_request)              ir.incoming_messages.each { |x| x.parse_raw_email! } @@ -565,7 +635,7 @@ end  # XXX do this for invalid ids  #  it "should render 404 file" do -#    response.should render_template("#{RAILS_ROOT}/public/404.html") +#    response.should render_template("#{Rails.root}/public/404.html")  #    response.headers["Status"].should == "404 Not Found"  #  end @@ -990,6 +1060,7 @@ describe RequestController, "when classifying an information request" do              session[:user_id] = @admin_user.id              @dog_request = info_requests(:fancy_dog_request)              InfoRequest.stub!(:find).and_return(@dog_request) +            @dog_request.stub!(:each).and_return([@dog_request])          end          it 'should update the status of the request' do  @@ -1031,6 +1102,7 @@ describe RequestController, "when classifying an information request" do              @dog_request.user = @admin_user              @dog_request.save!              InfoRequest.stub!(:find).and_return(@dog_request) +            @dog_request.stub!(:each).and_return([@dog_request])          end          it 'should update the status of the request' do  @@ -1067,6 +1139,7 @@ describe RequestController, "when classifying an information request" do              @request_owner = users(:bob_smith_user)              session[:user_id] = @request_owner.id              @dog_request.awaiting_description.should == true +            @dog_request.stub!(:each).and_return([@dog_request])          end          it "should successfully classify response if logged in as user controlling request" do @@ -1134,6 +1207,7 @@ describe RequestController, "when classifying an information request" do              @request_owner = users(:bob_smith_user)              session[:user_id] = @request_owner.id              @dog_request = info_requests(:fancy_dog_request) +            @dog_request.stub!(:each).and_return([@dog_request])              InfoRequest.stub!(:find).and_return(@dog_request)              @old_filters = ActionController::Routing::Routes.filters              ActionController::Routing::Routes.filters = RoutingFilter::Chain.new @@ -1767,6 +1841,48 @@ describe RequestController, "when showing similar requests" do              get :similar, :url_title => "there_is_really_no_such_path_owNAFkHR"          }.should raise_error(ActiveRecord::RecordNotFound)      end + +end + + +describe RequestController, "when reporting a request" do +    integrate_views + +    it "should mark a request as having been reported" do +        ir = info_requests(:badger_request) +        title = ir.url_title +        get :show, :url_title => title +        assigns[:info_request].attention_requested.should == false +        get :report_request, :url_title => title +        get :show, :url_title => title +        assigns[:info_request].attention_requested.should == true +        assigns[:info_request].described_state.should == "attention_requested" +    end + +    it "should not allow a request to be reported twice" do +        title = info_requests(:badger_request).url_title +        get :report_request, :url_title => title +        get :show, :url_title => title +        response.body.should include("has been reported") +        get :report_request, :url_title => title +        get :show, :url_title => title +        response.body.should include("has already been reported") +    end + +    it "should let users know a request has been reported" do +        title = info_requests(:badger_request).url_title +        get :show, :url_title => title +        response.body.should include("Offensive?") +        get :report_request, :url_title => title +        get :show, :url_title => title +        response.body.should_not include("Offensive?")         +        response.body.should include("This request has been reported") +        info_requests(:badger_request).set_described_state("successful") +        get :show, :url_title => title +        response.body.should_not include("This request has been reported") +        response.body.should include("The site administrators have reviewed this request") +    end +  end diff --git a/spec/controllers/track_controller_spec.rb b/spec/controllers/track_controller_spec.rb index 2ebf0109a..bc7cfce64 100644 --- a/spec/controllers/track_controller_spec.rb +++ b/spec/controllers/track_controller_spec.rb @@ -1,7 +1,7 @@  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')  describe TrackController, "when making a new track on a request" do -    before do +    before(:each) do          @ir = mock_model(InfoRequest, :url_title => 'myrequest',                                        :title => 'My request')          @track_thing = mock_model(TrackThing, :save! => true, @@ -9,6 +9,7 @@ describe TrackController, "when making a new track on a request" do                                                :track_medium= => nil,                                                :tracking_user_id= => nil)          TrackThing.stub!(:create_track_for_request).and_return(@track_thing) +        TrackThing.stub!(:create_track_for_search_query).and_return(@track_thing)          TrackThing.stub!(:find_by_existing_track).and_return(nil)          InfoRequest.stub!(:find_by_url_title).and_return(@ir) @@ -23,13 +24,20 @@ describe TrackController, "when making a new track on a request" do          response.should redirect_to(:controller => 'user', :action => 'signin', :token => post_redirect.token)      end -    it "should save the track and redirect if you are logged in" do +    it "should save a request track and redirect if you are logged in" do          session[:user_id] = @user.id          @track_thing.should_receive(:save!)          get :track_request, :url_title => @ir.url_title, :feed => 'track'          response.should redirect_to(:controller => 'request', :action => 'show', :url_title => @ir.url_title)      end +    it "should save a search track and redirect to the right place" do +        session[:user_id] = @user.id +        @track_thing.should_receive(:save!) +        get :track_search_query, :query_array => ["bob variety:sent"], :feed => 'track' +        response.should redirect_to(:controller => 'general', :action => 'search', :combined => ["bob", "requests"]) +    end +  end  describe TrackController, "when sending alerts for a track" do @@ -181,6 +189,35 @@ describe TrackController, "when viewing JSON version of a track feed" do  end +describe TrackController, "when tracking a public body" do +    integrate_views +    before(:each) do +        load_raw_emails_data +        rebuild_xapian_index +    end +     +    it "should work" do +        geraldine = public_bodies(:geraldine_public_body) +        get :track_public_body, :feed => 'feed', :url_name => geraldine.url_name +        response.should be_success +        response.should render_template('track/atom_feed') +        tt = assigns[:track_thing] +        tt.public_body.should == geraldine +        tt.track_type.should == 'public_body_updates' +        tt.track_query.should == "requested_from:" + geraldine.url_name +    end +    it "should filter by event type" do +        geraldine = public_bodies(:geraldine_public_body) +        get :track_public_body, :feed => 'feed', :url_name => geraldine.url_name, :event_type => 'sent' +        response.should be_success +        response.should render_template('track/atom_feed') +        tt = assigns[:track_thing] +        tt.public_body.should == geraldine +        tt.track_type.should == 'public_body_updates' +        tt.track_query.should == "requested_from:" + geraldine.url_name + " variety:sent" +    end + +end diff --git a/spec/fixtures/files/track-response-ms-bounce.email b/spec/fixtures/files/track-response-ms-bounce.email new file mode 100644 index 000000000..405799d19 --- /dev/null +++ b/spec/fixtures/files/track-response-ms-bounce.email @@ -0,0 +1,168 @@ +Delivered-To: mysociety.robin@gmail.com +Received: by 10.216.93.2 with SMTP id k2csp112824wef; +        Tue, 1 May 2012 07:34:18 -0700 (PDT) +Received: by 10.180.86.197 with SMTP id r5mr1890784wiz.21.1335882857831; +        Tue, 01 May 2012 07:34:17 -0700 (PDT) +Return-Path: <MAILER-DAEMON@wildfire.ukcod.org.uk> +Received: from wildfire.ukcod.org.uk (wildfire.ukcod.org.uk. [89.238.145.74]) +        by mx.google.com with ESMTPS id m57si21571764wee.109.2012.05.01.07.34.17 +        (version=TLSv1/SSLv3 cipher=OTHER); +        Tue, 01 May 2012 07:34:17 -0700 (PDT) +Received-SPF: pass (google.com: best guess record for domain of MAILER-DAEMON@wildfire.ukcod.org.uk designates 89.238.145.74 as permitted sender) client-ip=89.238.145.74; +Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of MAILER-DAEMON@wildfire.ukcod.org.uk designates 89.238.145.74 as permitted sender) smtp.mail=MAILER-DAEMON@wildfire.ukcod.org.uk +Received: from foi by wildfire.ukcod.org.uk with local (Exim 4.72) +	(envelope-from <MAILER-DAEMON@wildfire.ukcod.org.uk>) +	id 1SPE9b-0004QG-IC +	for team_delivery@whatdotheyknow.com; Tue, 01 May 2012 15:34:11 +0100 +Received: from majestic.ukcod.org.uk ([89.238.145.68]:41415) +	by wildfire.ukcod.org.uk with esmtp (Exim 4.72) +	id 1SPE9a-0004PY-Pt +	for foi-track@wildfire.ukcod.org.uk; Tue, 01 May 2012 15:34:11 +0100 +Received: from 83-216-147-106.lancas785.adsl.metronet.co.uk ([83.216.147.106]:41706 helo=SERVER1.example.internal) +	by majestic.ukcod.org.uk with esmtp (Exim 4.72) +	id 1SPE9P-00024F-Vz +	for track@whatdotheyknow.com; Tue, 01 May 2012 15:34:00 +0100 +Received: from server.example.internal (192.168.0.2) by + SERVER1.example.internal (192.168.0.3) with Microsoft SMTP Server id + 14.1.323.3; Tue, 1 May 2012 15:43:04 +0100 +From: <postmaster@example.org.uk> +To: <track@whatdotheyknow.com> +Date: Tue, 1 May 2012 11:42:55 +0100 +MIME-Version: 1.0 +X-DSNContext: 7ac7e7f9 - 374 - 00000004 - C00402D1 +Message-ID: <5C10braWX00000031@server.example.internal> +Subject: Delivery Status Notification (Delay) +Content-Type: multipart/mixed; +	boundary="_c3f90810-77af-49ee-88c2-d3a2f6212326_" + +--_c3f90810-77af-49ee-88c2-d3a2f6212326_ +Content-Type: multipart/report; report-type=delivery-status; +	boundary="_12a1cb74-57e4-4506-a3d9-c4b591d5a63f_" + +--_12a1cb74-57e4-4506-a3d9-c4b591d5a63f_ +Content-Type: text/plain; charset="unicode-1-1-utf-7" + +This is an automatically generated Delivery Status Notification. + +THIS IS A WARNING MESSAGE ONLY. + +YOU DO NOT NEED TO RESEND YOUR MESSAGE. + +Delivery to the following recipients has been delayed. + +       username@example.org.uk + + + + +--_12a1cb74-57e4-4506-a3d9-c4b591d5a63f_ +Content-Type: message/delivery-status + +Reporting-MTA: dns;server.example.internal +Received-From-MTA: dns;wildfire.ukcod.org.uk +Arrival-Date: Mon, 30 Apr 2012 23:24:59 +0100 + +Final-Recipient: rfc822;username@example.org.uk +Action: delayed +Status: 4.4.7 +Will-Retry-Until: Wed, 2 May 2012 23:24:59 +0100 + +--_12a1cb74-57e4-4506-a3d9-c4b591d5a63f_ +Content-Type: message/rfc822 + +Received: from wildfire.ukcod.org.uk ([89.238.145.74]) by + server.example.internal with Microsoft SMTPSVC(6.0.3790.4675);	 Mon, 30 + Apr 2012 23:24:59 +0100 +Received: from foi by wildfire.ukcod.org.uk with local (Exim 4.72) +	(envelope-from <track@whatdotheyknow.com>)	id 1SOysI-0003iJ-1S	for + username@example.org.uk; Mon, 30 Apr 2012 23:15:19 +0100 +Message-ID: <E1SOysI-0003iJ-1S@wildfire.ukcod.org.uk> +Date: Mon, 30 Apr 2012 23:15:17 +0100 +From: WhatDoTheyKnow <track@whatdotheyknow.com> +To: User Name <username@example.org.uk> +Subject: Your WhatDoTheyKnow email alert +MIME-Version: 1.0 +Precedence: bulk +Auto-Submitted: auto-generated +Return-Path: track@whatdotheyknow.com +X-OriginalArrivalTime: 30 Apr 2012 22:24:59.0309 (UTC) FILETIME=[1416D5D0:01CD2720] +X-MS-Exchange-Organization-AVStamp-AVG: 10.0.1424 [2113.1.1/4831];0; +X-MS-Exchange-Organization-AVStamp-Mailbox: AVGESE;6944;0; +Content-Type: multipart/mixed; +	boundary="_c03afbbc-87c9-4022-a9f2-fec3c53e1fef_" + +--_c03afbbc-87c9-4022-a9f2-fec3c53e1fef_ +Content-Type: text/plain; charset="utf-8" + +FOI requests to 'Lancashire Constabulary' +========================================= + +-- Copy of Information from Comments made in FOI -- +Jim Ebbs sent a request to Lancashire Constabulary (30 April 2012) +     "My previous FOI related to Section 20 of the 1989 Chidrens Act +     legislation and parents having parental control of their children. +     In your response..." +http://www.whatdotheyknow.com/request/copy_of_information_from_comment#outgoing-199196 + + +FOI requests to 'Lancashire County Council' +=========================================== + +-- Telecommunications Contracts -- +Lancashire County Council sent a response to Wendy (30 April 2012) +     "Dear Wendy, Please accept my apologies for not having provided you +     with a response before the statutory time limit. I am still in the +     process of de..." +http://www.whatdotheyknow.com/request/telecommunications_contracts_20#incoming-277652 + +-- Adult's and childreen's social care IT systems -- +Lancashire County Council sent a response to will johnson (30 April 2012) +     "Dear Mr Johnson,   Request for information under the Freedom of +     Information Act 2000   Further to your email dated 2^nd April, in +     which you re..." +http://www.whatdotheyknow.com/request/adults_and_childreens_social_car#incoming-277643 + + +FOI requests to 'Lancaster City Council' +======================================== + +-- Empty Commercial Property -- +Lancaster City Council sent a response to Paul Norris (30 April 2012) +     "Dear Mr Norris Please find attached a spreadheet with the +     information you requested for. If you are not happy with this +     response please contact me i..." +http://www.whatdotheyknow.com/request/empty_commercial_property_93#incoming-277913 + + +Alter your subscription +======================= + + +http://www.whatdotheyknow.com/c/huz3dzb3gtyq5y47r4r + +-- the WhatDoTheyKnow team + + +--_c03afbbc-87c9-4022-a9f2-fec3c53e1fef_ +Content-Type: text/plain; x-avg=cert; charset="windows-1252" +Content-Disposition: inline; filename="AVG certification.txt" +Content-Description: "AVG certification" +Content-Transfer-Encoding: quoted-printable + +The message does not contain any threats +AVG for MS Exchange Server (10.0.1424 - 2113/4831)= + +--_c03afbbc-87c9-4022-a9f2-fec3c53e1fef_-- + +--_12a1cb74-57e4-4506-a3d9-c4b591d5a63f_-- + +--_c3f90810-77af-49ee-88c2-d3a2f6212326_ +Content-Type: text/plain; x-avg=cert; charset="windows-1252" +Content-Disposition: inline; filename="AVG certification.txt" +Content-Description: "AVG certification" +Content-Transfer-Encoding: quoted-printable + +The message does not contain any threats +AVG for MS Exchange Server (10.0.1424 - 2113/4831)= + +--_c3f90810-77af-49ee-88c2-d3a2f6212326_-- diff --git a/spec/fixtures/files/track-response-webshield-bounce.email b/spec/fixtures/files/track-response-webshield-bounce.email new file mode 100644 index 000000000..1fd0f68ef --- /dev/null +++ b/spec/fixtures/files/track-response-webshield-bounce.email @@ -0,0 +1,513 @@ +Delivered-To: mysociety.robin@gmail.com +Received: by 10.216.93.2 with SMTP id k2csp412wef; +        Sat, 28 Apr 2012 15:10:07 -0700 (PDT) +Received: by 10.216.200.90 with SMTP id y68mr994412wen.49.1335651006883; +        Sat, 28 Apr 2012 15:10:06 -0700 (PDT) +Return-Path: <MAILER-DAEMON@wildfire.ukcod.org.uk> +Received: from wildfire.ukcod.org.uk (wildfire.ukcod.org.uk. [89.238.145.74]) +        by mx.google.com with ESMTPS id bw9si8451394wib.28.2012.04.28.15.10.06 +        (version=TLSv1/SSLv3 cipher=OTHER); +        Sat, 28 Apr 2012 15:10:06 -0700 (PDT) +Received-SPF: pass (google.com: best guess record for domain of MAILER-DAEMON@wildfire.ukcod.org.uk designates 89.238.145.74 as permitted sender) client-ip=89.238.145.74; +Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of MAILER-DAEMON@wildfire.ukcod.org.uk designates 89.238.145.74 as permitted sender) smtp.mail=MAILER-DAEMON@wildfire.ukcod.org.uk +Received: from foi by wildfire.ukcod.org.uk with local (Exim 4.72) +	(envelope-from <MAILER-DAEMON@wildfire.ukcod.org.uk>) +	id 1SOFq3-0002Lx-HQ +	for team_delivery@whatdotheyknow.com; Sat, 28 Apr 2012 23:09:59 +0100 +Received: from majestic.ukcod.org.uk ([89.238.145.68]:48989) +	by wildfire.ukcod.org.uk with esmtp (Exim 4.72) +	id 1SOFq2-0002La-Pq +	for foi-track@wildfire.ukcod.org.uk; Sat, 28 Apr 2012 23:09:59 +0100 +Received: from mailproxy1.example.co.uk ([93.174.8.200]:65135) +	by majestic.ukcod.org.uk with esmtp (Exim 4.72) +	id 1SOFps-0001uQ-4T +	for track@whatdotheyknow.com; Sat, 28 Apr 2012 23:09:48 +0100 +Message-Id: <f8b79b$go3iov@mailproxy1.example.co.uk> +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154271" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:37 +0100 +From: Webshield SMTP V4.5 MR3 Mail Service +Date: Sat Apr 28 23:09:37 2012 +To: <track@whatdotheyknow.com> +Subject: Returned Mail: Error During Delivery + +    ---- Failed Recipients ---- + +<failed.user@example.co.uk> +Mail Loop Detected + + +Requested action aborted: Mail loop detected + +    ---- Contents of the undelivered mail ---- + +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650976813; Sat, 28 Apr 2012 23:09:36 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154267" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:36 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650976250; Sat, 28 Apr 2012 23:09:36 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154266" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:36 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650975812; Sat, 28 Apr 2012 23:09:35 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154264" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:35 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650975250; Sat, 28 Apr 2012 23:09:35 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154263" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:35 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650974469; Sat, 28 Apr 2012 23:09:34 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154259" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:34 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650973250; Sat, 28 Apr 2012 23:09:33 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154255" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:33 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650972250; Sat, 28 Apr 2012 23:09:32 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154253" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:32 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650971250; Sat, 28 Apr 2012 23:09:31 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154250" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:31 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650970797; Sat, 28 Apr 2012 23:09:30 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154247" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:30 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650970250; Sat, 28 Apr 2012 23:09:30 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154246" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:30 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650969797; Sat, 28 Apr 2012 23:09:29 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154244" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:29 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650969250; Sat, 28 Apr 2012 23:09:29 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154243" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:29 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650968250; Sat, 28 Apr 2012 23:09:28 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154241" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:28 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650967250; Sat, 28 Apr 2012 23:09:27 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154238" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:27 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650966250; Sat, 28 Apr 2012 23:09:26 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154236" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:26 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650965828; Sat, 28 Apr 2012 23:09:25 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154235" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:25 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650965250; Sat, 28 Apr 2012 23:09:25 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154234" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:25 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650964781; Sat, 28 Apr 2012 23:09:24 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154233" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:24 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650964250; Sat, 28 Apr 2012 23:09:24 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154231" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:24 +0100 +Received: From mailproxy1.example.co.uk ([192.168.80.80]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 1335650963281; Sat, 28 Apr 2012 23:09:23 +0100 +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="562154222" +Received: from unknown (HELO so-itws3.so.ad.example.co.uk) ([10.56.10.5]) +  by mailproxy1.example.co.uk with SMTP; 28 Apr 2012 23:09:18 +0100 +Received: From mailproxy2.example.co.uk ([192.168.80.81]) by so-itws3.so.ad.example.co.uk (WebShield SMTP v4.5 MR3) +	id 133565095893; Sat, 28 Apr 2012 23:09:18 +0100 +X-SBRS: 2.9 +X-SG: DEFAULT +X-IronPort-Anti-Spam-Filtered: true +X-IronPort-Anti-Spam-Result: AnQDAGtpnE9Z7pFKgWdsb2JhbAA8B4Mpgj+sMSIBARYmJ4F8AQgBCwIgJhgQAQEVIAYCCQgHDgICAw0sFgsYhUMBAYItARkHpjJSkUqBL4EliDMFB4JeghmBGASMYYEwmxiBUwgLAw +X-IronPort-AV: E=Sophos;i="4.75,497,1330905600";  +   d="scan'208";a="514125909" +Received: from wildfire.ukcod.org.uk ([89.238.145.74]) +  by mailproxy2.example.co.uk with ESMTP; 28 Apr 2012 23:09:12 +0100 +Received: from foi by wildfire.ukcod.org.uk with local (Exim 4.72) +	(envelope-from <track@whatdotheyknow.com>) +	id 1SOFpI-0002Iz-6a +	for failed.user@example.co.uk; Sat, 28 Apr 2012 23:09:12 +0100 +Message-Id: <E1SOFpI-0002Iz-6a@wildfire.ukcod.org.uk> +Date: Sat, 28 Apr 2012 23:09:10 +0100 +From: WhatDoTheyKnow <track@whatdotheyknow.com> +To: Failed User <failed.user@example.co.uk> +Subject: Your WhatDoTheyKnow email alert +Mime-Version: 1.0 +Content-Type: text/plain; charset=utf-8 +Precedence: bulk +Auto-Submitted: auto-generated + +Successful Freedom of Information requests +========================================== + +-- geographical address for PO Box 1244, Enfield, EN1 9UF -- +Royal Mail Group Limited sent a response to Mr Mahmood (16 March 2012) +     "Dear Mr Mahmood, Please find attached a response to your recent +     Freedom of Information Request. Yours Sincerely Kate Fearn Company +     Secretary's..." +http://www.whatdotheyknow.com/request/geographical_address_for_po_box_3#incoming-264886 + +-- Foi About Animal Testin -- +University of Strathclyde sent a response to Mr Jonathon Proctor (27 April 2012) +     "Dear Mr Proctor,   FOI Request Reference – 2012/050   Thank you +     for your email of 03 April 2012 requesting information under the +     Freedom of I..." +http://www.whatdotheyknow.com/request/foi_about_animal_testin#incoming-277502 + +-- School's Spend on Recruitment Advertising -- +Staffordshire County Council sent a response to Robert Saunders ( 3 April 2012) +     "Dear Mr Saunders Re: Freedom of Information Act 2000 Thank you for +     your request for information. We have completed the search of our +     records and pl..." +http://www.whatdotheyknow.com/request/schools_spend_on_recruitment_adv_8#incoming-270606 + +-- WBC - Enforcement Team Dog Mess -- +Wirral Metropolitan Borough Council sent a response to Pete Sheffield (26 April 2012) +     "Hello Pete Sheffield   Thank you for your request below, Wirral +     Council is able to supply the following recorded information, I +     have included res..." +http://www.whatdotheyknow.com/request/wbc_enforcement_team_dog_mess#incoming-277027 + +-- City Garden Project technical feasibility study -- +Aberdeen City Council sent a response to Kenneth Watt (24 April 2012) +     "Dear Mr Watt Thank you for your information request of 17 April +     2012, made under the Freedom of Information (Scotland) Act 2002 +     (FOISA). Aberdeen C..." +http://www.whatdotheyknow.com/request/city_garden_project_technical_fe#incoming-276087 + +-- Government grant -- +Lambeth Borough Council sent a response to Ed Clarke (17 April 2012) +     "Dear Ed Clarke, Thank you for your FOI request under reference +     156969 Please accept my apologies for the late response. You asked +     " What was the to..." +http://www.whatdotheyknow.com/request/government_grant#incoming-274144 + +-- Advertising Policy -- +Humber Bridge Board sent a response to Mr. A. Wilson (24 April 2012) +     "Dear Mr Wilson Thank you for your email of 23rd April 2012. As the +     Humber Bridge Board is not a Public Body it is not formally subject +     to the Freedo..." +http://www.whatdotheyknow.com/request/advertising_policy#incoming-276348 + +-- Number of roads/estates/areas in the process of being adopted by Oxfordshire County Council -- +Oxfordshire County Council sent a response to Alex Lalvani (17 April 2012) +     "Dear Mr. Lalvani   Please find below the definition of an +     ‘agreement’ as requested by you further to our response to your +     Freedom of Information..." +http://www.whatdotheyknow.com/request/number_of_roadsestatesareas_in_t#incoming-273742 + +-- Correct Diagnosis of Lyme Disease -- +Health Protection Agency sent a response to jacqui butterworth (24 April 2012) +     "Dear Ms Butterworth I would like to apologise, for not responding +     to the last point you raised in your email of 29th March, regarding +     information y..." +http://www.whatdotheyknow.com/request/correct_diagnosis_of_lyme_diseas#incoming-276391 + +-- Genesis -- +Camden Borough Council sent a response to salim (24 April 2012) +     "Camden Council - Information request (FOI/EIR) - Housing and adult +     social care Our reference: 7706354 +     -------------------------------------------..." +http://www.whatdotheyknow.com/request/genesis#incoming-276075 + +-- Missing Persons -- +Humberside Police sent a response to Otis Holmes ( 1 March 2012) +     "Your Ref:   Our Ref:  F-2012-351   1 March 2012 Dear Mr Holmes, +     Please find attached my response to your recent Freedom of +     Information requ..." +http://www.whatdotheyknow.com/request/missing_persons_2#incoming-260422 + +-- A reply to my two previous emails -- +Blackpool Borough Council sent a response to Debbie Tomkinson ( 8 December 2011) +     "Dear Debbie FREEDOM OF INFORMATION ACT 2000 - INFORMATION REQUEST +     I am writing in response to your email of 09.11.12. You were +     previously employed..." +http://www.whatdotheyknow.com/request/a_reply_to_my_two_previous_email#incoming-234255 + +-- Expenditure of public funds by Police & CPS on persecution of World War 2 Veteran Norman Scarth -- +Ministry of Justice sent a response to Norman Scarth (24 April 2012) +     "Dear Mr Scarth,   Please find herewith our reply to your Freedom +     of Information request dated 3rd April 2012.     Mrs K Smith   Mrs +     K Smith R..." +http://www.whatdotheyknow.com/request/expenditure_of_public_funds_by_p#incoming-276263 + +-- Security screening at Edinburgh Airport -- +UK Border Agency sent a response to S Gray ( 5 April 2012) +     "Sent on behalf of Anne Webber   Dear Mr Gray,   Please find +     attached the FOI response.   Kind Regards   Sue   Sue Dinham +     Cyclame..." +http://www.whatdotheyknow.com/request/security_screening_at_edinburgh#incoming-271223 + +-- Inverness business improvement district (BID) -- +Highland Council sent a response to Laurel Bush (27 April 2012) +     "Dear Mr Bush,   Please find attached the response from William +     Gilfillan, in relation to the above Freedom of Information request +     dated 26 April..." +http://www.whatdotheyknow.com/request/inverness_business_improvement_d_2#incoming-277507 + +-- Waiting times in Contact Applications -- +Children and Family Court Advisory Support Service sent a response to Philip J Measures (24 April 2012) +     "Dear Sir Please find atatched a response to your recent request +     for an internal review. Any additioanl information will be provided +     by 8th May 2012...." +http://www.whatdotheyknow.com/request/waiting_times_in_contact_applica#incoming-276410 + +-- Payments to companies on behalf of directors -- +Moorfields Eye Hospital NHS Foundation Trust sent a response to Alex Miller ( 5 March 2012) +     "Dear Alex Miller   In response to your clarification of 8.2.12, +     payments made to permanent managers  and directors employed at the +     Trust  are pai..." +http://www.whatdotheyknow.com/request/payments_to_companies_on_behalf#incoming-261184 + +-- Personal Injury Claims -- +Leeds City Council sent a response to Ben Stanley ( 3 April 2012) +     "Dear Ben Stanley Please accept my apologies regarding the in +     responding to your request. Please see attached letter in respect +     of your recent Free..." +http://www.whatdotheyknow.com/request/personal_injury_claims_10#incoming-270346 + +-- Beetham Tower West £750,000 payment. -- +Liverpool City Council sent a response to A Rudkin (24 April 2012) +     "Please find attached response Regards, Kevin Symm Senior +     Information Officer Legal Services Liverpool City Council Municipal +     Buildings Dale St..." +http://www.whatdotheyknow.com/request/beetham_tower_west_750000_paymen_3#incoming-276145 + +-- Housing Benefit statistics -- +Leeds City Council sent a response to Peter Balderston (17 February 2012) +     "Dear Mr Balderston, Please see attached letter in respect of your +     recent Freedom of Information request. Please contact me if you +     have any furthe..." +http://www.whatdotheyknow.com/request/housing_benefit_statistics_19#incoming-255605 + +-- Board Minutes - Accuracy & Completeness -- +Avon and Wiltshire Mental Health Partnership NHS Trust sent a response to Steven King (24 April 2012) +     "Dear Mr King, Please see attached correspondence regarding your +     Freedom of Information request: <<0718 Response Letter.doc>> Yours +     sincerely,..." +http://www.whatdotheyknow.com/request/board_minutes_accuracy_completen#incoming-276314 + +-- Marked vehicle fleet list -- +Gloucestershire Constabulary sent a response to luke (27 April 2012) +     "Dear Mr Staddon, Gloucestershire Constabulary Freedom of +     Information request 2012.3788 On the 28th March 2012 you sent a +     letter constituting a requ..." +http://www.whatdotheyknow.com/request/marked_vehicle_fleet_list_7#incoming-277276 + +-- Quality metrics for decisionmakers. -- +Department for Work and Pensions sent a response to Ian Stirling (26 April 2012) +     "Dear Mr Stirling Please see attached response to your FoI request. +     Kind regards DWP Central FoI Team Website: www.dwp.gov.uk Your +     Reference: I St..." +http://www.whatdotheyknow.com/request/quality_metrics_for_decisionmake#incoming-276968 + +-- Stray Dogs -- +Wirral Metropolitan Borough Council sent a response to Carla Bottle (17 April 2012) +     "Hello Carla   Thank you for your further enquiry, I can clarify   +     Strays 76   and a further Handovers by owner (sick or aggressive) +     74..." +http://www.whatdotheyknow.com/request/stray_dogs_84#incoming-274073 + +-- Stray Dogs -- +Wakefield City Council sent a response to Carla Bottle (17 April 2012) +     "Dear Carla, Freedom of Information Act 2000 - Request for +     Information I am writing in respect of your recent enquiry for +     information held by the Aut..." +http://www.whatdotheyknow.com/request/stray_dogs_149#incoming-273859 + +-- Stray Dogs -- +Solihull Metropolitan Borough Council sent a response to Carla Bottle (17 April 2012) +     "Dear Ms Bottle Thank you for your email. Any dogs collected as +     strays are taken to Birmingham Dogs Home, where they are detained +     by the home on the..." +http://www.whatdotheyknow.com/request/stray_dogs_136#incoming-273886 + +-- Stray Dogs -- +Newham Borough Council sent a response to Carla Bottle (17 April 2012) +     "Dear Ms Bottle, Thank you for your email received on 16/03/2012. +     We attach our response under the disclosure provisions of the +     Freedom of Information..." +http://www.whatdotheyknow.com/request/stray_dogs_152#incoming-273897 + +-- Stray Dogs -- +St Edmundsbury Borough Council sent a response to Carla Bottle (17 April 2012) +     "Dear Carla, Thank you for your email following my response to your +     Freedom of Information request. I can advise that the 15 dogs that +     were 'rehomed..." +http://www.whatdotheyknow.com/request/stray_dogs_111#incoming-273946 + +-- Stray Dogs -- +Staffordshire Moorlands District Council sent a response to Carla Bottle (17 April 2012) +     "E-mail:  [1][FOI #109668 email]       Dear Ms Bottle   Re:  +     Freedom of Information Act request – Stray Dogs   Thank you for +     your e..." +http://www.whatdotheyknow.com/request/stray_dogs_108#incoming-274005 + +-- Stray Dogs -- +Southwark Borough Council sent a response to Carla Bottle (17 April 2012) +     "Southwark Council - Information request (FOI/EIR) Our reference: +     214355 +     --------------------------------------------------------------------------..." +http://www.whatdotheyknow.com/request/stray_dogs_155#incoming-274104 + +-- Residential Building Revaluation Costs -- +Camden Borough Council sent a response to Peter CAIN (17 April 2012) +     "Dear Mr Cain   Herewith the reply of the Panel to your application +     for an internal review   Yours sincerely   Peter Swingler Obo +     Internal Revie..." +http://www.whatdotheyknow.com/request/residential_building_revaluation#incoming-274061 + +-- Stray Dogs -- +Waveney District Council sent a response to Carla Bottle (17 April 2012) +     "Dear Ms Bottle, Freedom of Information Request - Ref No 9932 I am +     writing in response to your recently submitted Freedom of +     Information request reg..." +http://www.whatdotheyknow.com/request/stray_dogs_112#incoming-274145 + +-- Stray Dogs -- +Tamworth Borough Council sent a response to Carla Bottle (27 April 2012) +     "Our Ref: FOI1264/DB/02 Please ask for: Derek Bolton Direct dial: +     01827 709 587 E-mail:  [email address]   Ms C Bottle +     Whatdotheyknow.com..." +http://www.whatdotheyknow.com/request/stray_dogs_110#incoming-277348 + +-- Stray Dogs -- +South Northamptonshire District Council sent a response to Carla Bottle (23 April 2012) +     "South Northamptonshire Council Request Ref: 1013066   Springfields +     Towcester Date: Apr 5 2012 Northants NN12 6AE" +http://www.whatdotheyknow.com/request/stray_dogs_89#incoming-275624 + +-- Stray Dogs -- +Selby District Council sent a response to Carla Bottle (23 April 2012) +     "Dear Ms Bottle   I refer to your recent request for information +     under the Freedom of Information Act. I have now had an opportunity +     to check our..." +http://www.whatdotheyknow.com/request/stray_dogs_148#incoming-275917 + +-- DASS Carers Assesment Documents -- +Wirral Metropolitan Borough Council sent a response to Heston O Riley (26 April 2012) +     "Hello Heston O Riley   Thank you for request below, apologies for +     the slight delay, Wirral Council can now provide the information +     you have r..." +http://www.whatdotheyknow.com/request/dass_carers_assesment_documents#incoming-277203 + +-- Traffic offences on Holland Park Barton Under Needwood -- +Staffordshire Police sent a response to Joanne Cooper (24 April 2012) +     "Please see another response to your FOI request.    Apologies, but +     please discard my first response.  The letter will give an +     explanation. Regards..." +http://www.whatdotheyknow.com/request/traffic_offences_on_holland_park#incoming-276112 + +-- Percy Street - Capita report input -- +Newcastle upon Tyne City Council sent a response to Katja Leyendecker (16 April 2012) +     "Katja, Firstly let me apologise for the tardy delivery of the +     response, I have been on annual leave and did not return until this +     morning. I'm afrai..." +http://www.whatdotheyknow.com/request/percy_street_capita_report_input#incoming-273690 + +-- AREa REView COMMITTee CHILd PROtection GUIDElines -- +Nottinghamshire County Council sent a response to Alison Stevens (27 April 2012) +     "REF:C&IT/PFL/NCC-007511-11   Dear Ms Stevens   FREEDOM OF +     INFORMATION ACT 2000 - INFORMATION REQUEST   In response to your +     request for the..." +http://www.whatdotheyknow.com/request/area_review_committee_child_prot#incoming-277510 + +-- Atos HeathcarE Risk AsseSSMENTS -- +Department for Work and Pensions sent a response to Alison Stevens (27 April 2012) +     "Dear Ms Stevens Please see attached response to your FoI request. +     Kind regards DWP Central FoI Team To A Stevens C/o [FOI #107839 +     email] DWP Centr..." +http://www.whatdotheyknow.com/request/atos_heathcare_risk_assessments#incoming-277463 + +-- SET(0) Postal Applications status D2c 2011 -- +UK Border Agency sent a response to S Pillai (29 March 2012) +     "S Pillai   Please find the response to your request for +     information under the Freedom of Information Act 2000, regarding +     SET (O) applications...." +http://www.whatdotheyknow.com/request/set0_postal_applications_status#incoming-269113 + +-- re London Borough of Bromley -- +Local Government Ombudsmen sent a response to ROSEMARY CANTWELL (Account suspended) ( 3 April 2012) +     "Dear Mrs Cantwell I have consulted colleagues and they inform me +     that you have exhausted our internal complaints procedures and +     there is nothing more..." +http://www.whatdotheyknow.com/request/re_london_borough_of_bromley#incoming-270360 + + +Alter your subscription +======================= + + +http://www.whatdotheyknow.com/c/abcdefghijklmnop23 + +-- the WhatDoTheyKnow team + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/integration/admin_spec.rb b/spec/integration/admin_spec.rb new file mode 100644 index 000000000..caf741749 --- /dev/null +++ b/spec/integration/admin_spec.rb @@ -0,0 +1,23 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +require "base64" + +describe "When administering the site" do +    it "allows an admin to log in as another user" do +        # First log in as Joe Admin +        admin_user = users(:admin_user) +        admin_user.email_confirmed = true +        admin_user.save! +        post_via_redirect "/profile/sign_in", :user_signin => {:email => admin_user.email, :password => "jonespassword"} +        response.should be_success +         +        # Now fetch the "log in as" link to log in as Bob +        admin_username = MySociety::Config.get('ADMIN_USERNAME') +        admin_password = MySociety::Config.get('ADMIN_PASSWORD') +        get_via_redirect "/admin/user/login_as/#{users(:bob_smith_user).id}", nil, { +          "Authorization" => "Basic " + Base64.encode64("#{admin_username}:#{admin_password}").strip +        } +        response.should be_success +        session[:user_id].should == users(:bob_smith_user).id +    end +end diff --git a/spec/integration/search_request_spec.rb b/spec/integration/search_request_spec.rb index b62f0a4c4..17a7b4aaa 100644 --- a/spec/integration/search_request_spec.rb +++ b/spec/integration/search_request_spec.rb @@ -12,6 +12,13 @@ describe "When searching" do          response.body.should include(""mouse stilton"")      end +    it "should redirect requests with search in query string to URL-based page" do +        url = '/search/all?query=bob' +        request_via_redirect("post", url) +        response.request.url.should_not include(url) +        response.request.url.should include("/search/bob/all") +    end +      it "should correctly execute simple search" do          request_via_redirect("post", "/search",                               :query => 'bob' diff --git a/spec/models/purge_request_spec.rb b/spec/models/purge_request_spec.rb new file mode 100644 index 000000000..94fe01317 --- /dev/null +++ b/spec/models/purge_request_spec.rb @@ -0,0 +1,32 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +require 'fakeweb' + +describe PurgeRequest, "purging things" do  +    before do +        FakeWeb.last_request = nil +    end + +    it 'should issue purge requests to the server' do  +        req = PurgeRequest.new(:url => "/begone_from_here", +                               :model => "don't care", +                               :model_id => "don't care") +        req.save() +        PurgeRequest.all().count.should == 1 +        PurgeRequest.purge_all() +        PurgeRequest.all().count.should == 0 +    end + +    it 'should fail silently for a misconfigured server' do  +        FakeWeb.register_uri(:get, %r|brokenv|, :body => "BROKEN") +        config = MySociety::Config.load_default() +        config['VARNISH_HOST'] = "brokencache" +        req = PurgeRequest.new(:url => "/begone_from_here", +                               :model => "don't care", +                               :model_id => "don't care") +        req.save() +        PurgeRequest.all().count.should == 1 +        PurgeRequest.purge_all() +        PurgeRequest.all().count.should == 0 +    end +end +  diff --git a/spec/models/track_thing_spec.rb b/spec/models/track_thing_spec.rb index bd122941a..345629bd6 100644 --- a/spec/models/track_thing_spec.rb +++ b/spec/models/track_thing_spec.rb @@ -36,7 +36,7 @@ describe TrackThing, "when tracking changes" do      it "will make some sane descriptions of search-based tracks" do          tests = [['bob variety:user', "users matching text 'bob'"], -                 ['bob (variety:sent OR variety:followup_sent OR variety:response OR variety:comment) (latest_status:successful OR latest_status:partially_successful OR latest_status:rejected OR latest_status:not_held)', "requests which are successful or unsuccessful or comments matching text 'bob'"], +                 ['bob (variety:sent OR variety:followup_sent OR variety:response OR variety:comment) (latest_status:successful OR latest_status:partially_successful OR latest_status:rejected OR latest_status:not_held)', "comments or requests which are successful or unsuccessful matching text 'bob'"],                   ['(latest_status:waiting_response OR latest_status:waiting_clarification OR waiting_classification:true)', 'requests which are awaiting a response']]          for query, description in tests              track_thing = TrackThing.create_track_for_search_query(query) @@ -44,5 +44,10 @@ describe TrackThing, "when tracking changes" do          end      end +    it "will create an authority-based track when called using a 'bodies' postfix" do +        track_thing = TrackThing.create_track_for_search_query('fancy dog', 'bodies') +        track_thing.track_query.should =~ /variety:authority/ +    end +  end diff --git a/spec/script/handle-mail-replies_spec.rb b/spec/script/handle-mail-replies_spec.rb index 8ed83b31f..75a2aa6ad 100644 --- a/spec/script/handle-mail-replies_spec.rb +++ b/spec/script/handle-mail-replies_spec.rb @@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')  require "external_command"  def mail_reply_test(email_filename) -    Dir.chdir RAILS_ROOT do +    Dir.chdir Rails.root do          xc = ExternalCommand.new("script/handle-mail-replies", "--test")          xc.run(load_file_fixture(email_filename)) @@ -18,6 +18,18 @@ describe "When filtering" do          r.out.should == "user@example.com\n"      end +    it "should detect a WebShield delivery error message" do +        r = mail_reply_test("track-response-webshield-bounce.email") +        r.status.should == 1 +        r.out.should == "failed.user@example.co.uk\n" +    end +     +    it "should detect a MS Exchange non-permanent delivery error message" do +        r = mail_reply_test("track-response-ms-bounce.email") +        r.status.should == 1 +        r.out.should == "" +    end +          it "should pass on a non-bounce message" do          r = mail_reply_test("incoming-request-bad-uuencoding.email")          r.status.should == 0 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c00da48bc..a98a5113d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,6 +13,9 @@ config['ADMIN_PASSWORD'] = 'baz'  # tests assume 20 days  config['REPLY_LATE_AFTER_DAYS'] = 20 +# register a fake Varnish server +require 'fakeweb' +FakeWeb.register_uri(:purge, %r|varnish.localdomain|, :body => "OK")  # Uncomment the next line to use webrat's matchers  #require 'webrat/integrations/rspec-rails' @@ -43,7 +46,7 @@ Spec::Runner.configure do |config|    #    # You can also declare which fixtures to use (for example fixtures for test/fixtures):    # -  # config.fixture_path = RAILS_ROOT + '/spec/fixtures/' +  # config.fixture_path = Rails.root + '/spec/fixtures/'    #    # == Mock Framework    # diff --git a/tmp/.gitkeep b/tmp/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tmp/.gitkeep  | 
