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 |