diff options
143 files changed, 2830 insertions, 1805 deletions
@@ -11,9 +11,12 @@ gem 'dynamic_form' gem 'exception_notification' gem 'fancybox-rails' gem 'foundation-rails' +gem 'icalendar', '1.4.3' gem 'jquery-rails', '~> 3.0.4' gem 'jquery-ui-rails' gem 'json' +gem 'holidays' +gem 'iso_country_codes' gem 'mahoro' gem 'memcache-client' gem 'net-http-local', :platforms => [:ruby_18, :ruby_19] @@ -25,6 +28,7 @@ gem 'recaptcha', '~> 0.3.1', :require => 'recaptcha/rails' # :require avoids "already initialized constant" warnings gem 'rmagick', :require => 'RMagick' gem 'ruby-msg', '~> 1.5.0', :git => 'git://github.com/mysociety/ruby-msg.git' +gem 'secure_headers' gem "statistics2", "~> 0.54" gem 'syslog_protocol' gem 'thin' diff --git a/Gemfile.lock b/Gemfile.lock index b5f5671f3..3e640ad77 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -125,7 +125,10 @@ GEM tilt highline (1.6.19) hike (1.2.3) + holidays (1.0.8) i18n (0.6.11) + icalendar (1.4.3) + iso_country_codes (0.6.1) journey (1.0.4) jquery-rails (3.0.4) railties (>= 3.0, < 5.0) @@ -230,6 +233,7 @@ GEM railties (~> 3.2.0) sass (>= 3.1.10) tilt (~> 1.3) + secure_headers (1.3.4) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) @@ -305,6 +309,9 @@ DEPENDENCIES gettext gettext_i18n_rails globalize3! + holidays + icalendar (= 1.4.3) + iso_country_codes jquery-rails (~> 3.0.4) jquery-ui-rails json @@ -330,6 +337,7 @@ DEPENDENCIES ruby-debug ruby-msg (~> 1.5.0)! sass-rails (~> 3.2.3) + secure_headers spork-rails statistics2 (~> 0.54) syslog_protocol diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index 4925a65a4..9402f7f6c 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -3,8 +3,10 @@ //= require jquery.ui.tabs //= require jquery.ui.sortable //= require jquery.ui.effect-highlight -//= require admin/bootstrap-collapse -//= require admin/bootstrap-tab +//= require bootstrap-collapse +//= require bootstrap-tab +//= require bootstrap-dropdown //= require admin/admin //= require admin/category-order +//= require admin/holidays //= require jquery_ujs diff --git a/app/assets/javascripts/admin/bootstrap-collapse.js b/app/assets/javascripts/admin/bootstrap-collapse.js deleted file mode 100644 index 9a364468b..000000000 --- a/app/assets/javascripts/admin/bootstrap-collapse.js +++ /dev/null @@ -1,138 +0,0 @@ -/* ============================================================= - * 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/app/assets/javascripts/admin/bootstrap-tab.js b/app/assets/javascripts/admin/bootstrap-tab.js deleted file mode 100644 index 26c9ece75..000000000 --- a/app/assets/javascripts/admin/bootstrap-tab.js +++ /dev/null @@ -1,130 +0,0 @@ -/* ======================================================== - * 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/app/assets/javascripts/admin/holidays.js b/app/assets/javascripts/admin/holidays.js new file mode 100644 index 000000000..55eae9e2a --- /dev/null +++ b/app/assets/javascripts/admin/holidays.js @@ -0,0 +1,46 @@ +$(function() { + + // New button loads the 'new' form via AJAX + $('#new-holiday-button').click(function(){ + var new_call = $.ajax({ type: 'GET', url: $(this).attr('href')}); + new_call.done(function(response) { + $('#existing-holidays').before(response); + }); + return false; + + }); + + // Each edit button loads the 'edit' form for that holiday via AJAX + $('.holiday').each(function(index){ + var holiday_row = $(this); + var edit_button = holiday_row.find('.edit-button'); + edit_button.click(function(){ + var edit_call = $.ajax({ type: 'GET', url: holiday_row.data('target') }); + edit_call.done(function(response) { + holiday_row.html(response); + }); + return false; + }); + }); + + // Remove button removes form div for holiday from an import set + $('.remove-holiday').each(function(index){ + $(this).click(function(){ + $(this).parents('.import-holiday-info').remove(); + return false; + }); + }); + + if ($('#holiday_import_source_suggestions').is(':checked')){ + $('#holiday_import_ical_feed_url').attr("disabled", "disabled"); + } + // Enable and disable the feed element when that is selected as the import source + $('#holiday_import_source_feed').click(function(){ + $('#holiday_import_ical_feed_url').removeAttr("disabled"); + }); + + $('#holiday_import_source_suggestions').click(function(){ + $('#holiday_import_ical_feed_url').attr("disabled", "disabled"); + }); + +}); diff --git a/app/assets/javascripts/bootstrap-collapse.js b/app/assets/javascripts/bootstrap-collapse.js deleted file mode 100644 index 9a364468b..000000000 --- a/app/assets/javascripts/bootstrap-collapse.js +++ /dev/null @@ -1,138 +0,0 @@ -/* ============================================================= - * 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/app/assets/javascripts/bootstrap-tab.js b/app/assets/javascripts/bootstrap-tab.js deleted file mode 100644 index 26c9ece75..000000000 --- a/app/assets/javascripts/bootstrap-tab.js +++ /dev/null @@ -1,130 +0,0 @@ -/* ======================================================== - * 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/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index 104f10c75..31fe7e95a 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -80,6 +80,10 @@ body.admin { } } + .fieldWithErrors input{ + border: 1px solid #ff0c11; + } + body.admin blockquote p { font-size: 13px; display: inline; @@ -119,5 +123,34 @@ body.admin { padding: 3px 0; } + /* Holidays */ + .day_select { + width: 75px; + } + + .holiday-description, .holiday-day, .holiday-buttons, .holiday-destroy { + padding: 6px 4px; + } + + .holiday-description, .holiday-day, .holiday-buttons{ + display: inline-block; + } + + .holiday-description { + width: 300px; + } + .holiday-day { + width: 240px; + text-align: center; + } + .holiday-buttons{ + width: 200px; + text-align: right; + } + + #import_start_year, #import_end_year { + width: 75px; + } + } diff --git a/app/assets/stylesheets/responsive/_new_request_layout.scss b/app/assets/stylesheets/responsive/_new_request_layout.scss index a2ab23060..a8b24e1b1 100644 --- a/app/assets/stylesheets/responsive/_new_request_layout.scss +++ b/app/assets/stylesheets/responsive/_new_request_layout.scss @@ -58,6 +58,10 @@ } } +#typeahead_response .close-button { + float: right; +} + /* Advice sits on right hand side */ #request_advice { diff --git a/app/controllers/admin_censor_rule_controller.rb b/app/controllers/admin_censor_rule_controller.rb index 68ca57510..3387fd832 100644 --- a/app/controllers/admin_censor_rule_controller.rb +++ b/app/controllers/admin_censor_rule_controller.rb @@ -5,11 +5,15 @@ # Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class AdminCensorRuleController < AdminController + + before_filter :set_editor, :only => [:create, :update] + before_filter :find_and_check_rule, :only => [:edit, :update, :destroy] + def new - if params[:info_request_id] - @info_request = InfoRequest.find(params[:info_request_id]) + if params[:request_id] + @info_request = InfoRequest.find(params[:request_id]) @censor_rule = @info_request.censor_rules.build - @form_url = admin_info_request_censor_rules_path(@info_request) + @form_url = admin_request_censor_rules_path(@info_request) end if params[:user_id] @@ -17,18 +21,13 @@ class AdminCensorRuleController < AdminController @censor_rule = @censor_user.censor_rules.build @form_url = admin_user_censor_rules_path(@censor_user) end - - @censor_rule ||= CensorRule.new - @form_url ||= admin_rule_create_path end def create - params[:censor_rule][:last_edit_editor] = admin_current_user - - if params[:info_request_id] - @info_request = InfoRequest.find(params[:info_request_id]) + if params[:request_id] + @info_request = InfoRequest.find(params[:request_id]) @censor_rule = @info_request.censor_rules.build(params[:censor_rule]) - @form_url = admin_info_request_censor_rules_path(@info_request) + @form_url = admin_request_censor_rules_path(@info_request) end if params[:user_id] @@ -37,26 +36,16 @@ class AdminCensorRuleController < AdminController @form_url = admin_user_censor_rules_path(@censor_user) end - @censor_rule ||= CensorRule.new(params[:censor_rule]) - @form_url ||= admin_rule_create_path - if @censor_rule.save - if !@censor_rule.info_request.nil? - expire_for_request(@censor_rule.info_request) - end - - if !@censor_rule.user.nil? - expire_requests_for_user(@censor_rule.user) - end flash[:notice] = 'CensorRule was successfully created.' - if !@censor_rule.info_request.nil? - redirect_to admin_request_show_url(@censor_rule.info_request) - elsif !@censor_rule.user.nil? - redirect_to admin_user_show_url(@censor_rule.user) - else - raise "internal error" + if @censor_rule.info_request + expire_for_request(@censor_rule.info_request) + redirect_to admin_request_url(@censor_rule.info_request) + elsif @censor_rule.user + expire_requests_for_user(@censor_rule.user) + redirect_to admin_user_url(@censor_rule.user) end else render :action => 'new' @@ -64,63 +53,55 @@ class AdminCensorRuleController < AdminController end def edit - @censor_rule = CensorRule.find(params[:id]) end def update - params[:censor_rule][:last_edit_editor] = admin_current_user - @censor_rule = CensorRule.find(params[:id]) - if @censor_rule.update_attributes(params[:censor_rule]) - unless @censor_rule.info_request.nil? - expire_for_request(@censor_rule.info_request) - end - - unless @censor_rule.user.nil? - expire_requests_for_user(@censor_rule.user) - end flash[:notice] = 'CensorRule was successfully updated.' - if !@censor_rule.info_request.nil? - redirect_to admin_request_show_url(@censor_rule.info_request) - elsif !@censor_rule.user.nil? - redirect_to admin_user_show_url(@censor_rule.user) - else - raise "internal error" + if @censor_rule.info_request + expire_for_request(@censor_rule.info_request) + redirect_to admin_request_url(@censor_rule.info_request) + elsif @censor_rule.user + expire_requests_for_user(@censor_rule.user) + redirect_to admin_user_url(@censor_rule.user) end + else render :action => 'edit' end end def destroy - @censor_rule = CensorRule.find(params[:censor_rule_id]) info_request = @censor_rule.info_request user = @censor_rule.user - @censor_rule.destroy - unless info_request.nil? - expire_for_request(info_request) - end - - unless user.nil? - expire_requests_for_user(user) - end - flash[:notice] = "CensorRule was successfully destroyed." - if !info_request.nil? - redirect_to admin_request_show_url(info_request) - elsif !user.nil? - redirect_to admin_user_show_url(user) - else - raise "internal error" + if info_request + expire_for_request(info_request) + redirect_to admin_request_url(info_request) + elsif user + expire_requests_for_user(user) if user + redirect_to admin_user_url(user) end + end private + def set_editor + params[:censor_rule][:last_edit_editor] = admin_current_user + end + + def find_and_check_rule + @censor_rule = CensorRule.find(params[:id]) + unless (@censor_rule.user || @censor_rule.info_request) + flash[:notice] = 'Only user and request censor rules can be edited' + redirect_to admin_general_index_path + end + end end diff --git a/app/controllers/admin_comment_controller.rb b/app/controllers/admin_comment_controller.rb new file mode 100644 index 000000000..0aafb122a --- /dev/null +++ b/app/controllers/admin_comment_controller.rb @@ -0,0 +1,36 @@ +# app/controllers/admin_comment_controller.rb: +# Controller for editing comments from the admin interface. +# +# Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ + +class AdminCommentController < AdminController + + def edit + @comment = Comment.find(params[:id]) + end + + def update + @comment = Comment.find(params[:id]) + + old_body = @comment.body + 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_current_user(), + :old_body => old_body, + :body => @comment.body, + :old_visible => old_visible, + :visible => @comment.visible, + }) + flash[:notice] = 'Comment successfully updated.' + redirect_to admin_request_url(@comment.info_request) + else + render :action => 'edit' + end + end + +end diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 3bf40b8f9..7760c372b 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -9,7 +9,6 @@ require 'fileutils' class AdminController < ApplicationController layout "admin" before_filter :authenticate - protect_from_forgery # See ActionController::RequestForgeryProtection for details # action to take if expecting an authenticity token and one isn't received def handle_unverified_request diff --git a/app/controllers/admin_holiday_imports_controller.rb b/app/controllers/admin_holiday_imports_controller.rb new file mode 100644 index 000000000..8596936f0 --- /dev/null +++ b/app/controllers/admin_holiday_imports_controller.rb @@ -0,0 +1,28 @@ +class AdminHolidayImportsController < AdminController + + def new + @holiday_import = HolidayImport.new(holiday_import_params) + @holiday_import.populate if @holiday_import.valid? + end + + def create + @holiday_import = HolidayImport.new(holiday_import_params) + if @holiday_import.save + notice = "Holidays successfully imported" + redirect_to admin_holidays_path, :notice => notice + else + render :new + end + end + + private + + def holiday_import_params(key = :holiday_import) + if params[key] + params[key].slice(:holidays_attributes, :start_year, :end_year, :source, :ical_feed_url) + else + {} + end + end + +end diff --git a/app/controllers/admin_holidays_controller.rb b/app/controllers/admin_holidays_controller.rb new file mode 100644 index 000000000..9177ebd44 --- /dev/null +++ b/app/controllers/admin_holidays_controller.rb @@ -0,0 +1,67 @@ +class AdminHolidaysController < AdminController + + def index + get_all_holidays + end + + def new + @holiday = Holiday.new + if request.xhr? + render :partial => 'new_form', :locals => { :holiday => @holiday } + else + render :action => 'new' + end + end + + def create + @holiday = Holiday.new(holiday_params) + if @holiday.save + notice = "Holiday successfully created." + redirect_to admin_holidays_path, :notice => notice + else + render :new + end + end + + def edit + @holiday = Holiday.find(params[:id]) + if request.xhr? + render :partial => 'edit_form' + else + render :action => 'edit' + end + end + + def update + @holiday = Holiday.find(params[:id]) + if @holiday.update_attributes(holiday_params) + flash[:notice] = 'Holiday successfully updated.' + redirect_to admin_holidays_path + else + render :edit + end + end + + def destroy + @holiday = Holiday.find(params[:id]) + @holiday.destroy + notice = "Holiday successfully destroyed" + redirect_to admin_holidays_path, :notice => notice + end + + private + + def get_all_holidays + @holidays_by_year = Holiday.all.group_by { |holiday| holiday.day.year } + @years = @holidays_by_year.keys.sort.reverse + end + + def holiday_params(key = :holiday) + if params[key] + params[key].slice(:description, 'day(1i)', 'day(2i)', 'day(3i)') + else + {} + end + end + +end diff --git a/app/controllers/admin_incoming_message_controller.rb b/app/controllers/admin_incoming_message_controller.rb index 6b50d0e36..bc653bf53 100644 --- a/app/controllers/admin_incoming_message_controller.rb +++ b/app/controllers/admin_incoming_message_controller.rb @@ -20,14 +20,14 @@ class AdminIncomingMessageController < AdminController :prominence_reason => @incoming_message.prominence_reason) expire_for_request(@incoming_message.info_request) flash[:notice] = 'Incoming message successfully updated.' - redirect_to admin_request_show_url(@incoming_message.info_request) + redirect_to admin_request_url(@incoming_message.info_request) else render :action => 'edit' end end def destroy - @incoming_message = IncomingMessage.find(params[:incoming_message_id]) + @incoming_message = IncomingMessage.find(params[:id]) @info_request = @incoming_message.info_request incoming_message_id = @incoming_message.id @@ -37,11 +37,11 @@ class AdminIncomingMessageController < AdminController # expire cached files expire_for_request(@info_request) flash[:notice] = 'Incoming message successfully destroyed.' - redirect_to admin_request_show_url(@info_request) + redirect_to admin_request_url(@info_request) end def redeliver - incoming_message = IncomingMessage.find(params[:redeliver_incoming_message_id]) + incoming_message = IncomingMessage.find(params[:id]) message_ids = params[:url_title].split(",").each {|x| x.strip} previous_request = incoming_message.info_request destination_request = nil @@ -54,7 +54,7 @@ class AdminIncomingMessageController < AdminController end if destination_request.nil? flash[:error] = "Failed to find destination request '" + m + "'" - return redirect_to admin_request_show_url(previous_request) + return redirect_to admin_request_url(previous_request) end raw_email_data = incoming_message.raw_email.data @@ -74,7 +74,7 @@ class AdminIncomingMessageController < AdminController expire_for_request(previous_request) incoming_message.fully_destroy end - redirect_to admin_request_show_url(destination_request) + redirect_to admin_request_url(destination_request) end end diff --git a/app/controllers/admin_info_request_event_controller.rb b/app/controllers/admin_info_request_event_controller.rb new file mode 100644 index 000000000..17d147582 --- /dev/null +++ b/app/controllers/admin_info_request_event_controller.rb @@ -0,0 +1,24 @@ +# app/controllers/admin_info_request_event_controller.rb: +# Controller for FOI request event manipulation from the admin interface. +# +# Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ + +class AdminInfoRequestEventController < AdminController + + # used so due dates get fixed + def update + @info_request_event = InfoRequestEvent.find(params[:id]) + if @info_request_event.event_type != 'response' + raise Exception("can only mark responses as requires clarification") + end + @info_request_event.described_state = 'waiting_clarification' + @info_request_event.calculated_state = 'waiting_clarification' + # TODO: deliberately don't update described_at so doesn't reenter search? + @info_request_event.save! + + flash[:notice] = "Old response marked as having been a clarification" + redirect_to admin_request_url(@info_request_event.info_request) + end + +end diff --git a/app/controllers/admin_outgoing_message_controller.rb b/app/controllers/admin_outgoing_message_controller.rb index ec0981677..2ee811dc0 100644 --- a/app/controllers/admin_outgoing_message_controller.rb +++ b/app/controllers/admin_outgoing_message_controller.rb @@ -5,7 +5,7 @@ class AdminOutgoingMessageController < AdminController end def destroy - @outgoing_message = OutgoingMessage.find(params[:outgoing_message_id]) + @outgoing_message = OutgoingMessage.find(params[:id]) @info_request = @outgoing_message.info_request outgoing_message_id = @outgoing_message.id @@ -14,7 +14,7 @@ class AdminOutgoingMessageController < AdminController { :editor => admin_current_user(), :deleted_outgoing_message_id => outgoing_message_id }) flash[:notice] = 'Outgoing message successfully destroyed.' - redirect_to admin_request_show_url(@info_request) + redirect_to admin_request_url(@info_request) end def update @@ -38,10 +38,41 @@ class AdminOutgoingMessageController < AdminController :prominence_reason => @outgoing_message.prominence_reason }) flash[:notice] = 'Outgoing message successfully updated.' expire_for_request(@outgoing_message.info_request) - redirect_to admin_request_show_url(@outgoing_message.info_request) + redirect_to admin_request_url(@outgoing_message.info_request) else render :action => 'edit' end end + def resend + @outgoing_message = OutgoingMessage.find(params[:id]) + @outgoing_message.prepare_message_for_resend + + mail_message = case @outgoing_message.message_type + when 'initial_request' + OutgoingMailer.initial_request( + @outgoing_message.info_request, + @outgoing_message + ).deliver + when 'followup' + OutgoingMailer.followup( + @outgoing_message.info_request, + @outgoing_message, + @outgoing_message.incoming_message_followup + ).deliver + else + raise "Message id #{id} has type '#{message_type}' which cannot be resent" + end + + @outgoing_message.record_email_delivery( + mail_message.to_addrs.join(', '), + mail_message.message_id, + 'resent' + ) + + flash[:notice] = "Outgoing message resent" + redirect_to admin_request_url(@outgoing_message.info_request) + end + + end diff --git a/app/controllers/admin_public_body_controller.rb b/app/controllers/admin_public_body_controller.rb index f7a80476c..cfb6f240d 100644 --- a/app/controllers/admin_public_body_controller.rb +++ b/app/controllers/admin_public_body_controller.rb @@ -5,69 +5,9 @@ # Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class AdminPublicBodyController < AdminController - def index - list - render :action => 'list' - end - - def _lookup_query_internal - @locale = self.locale_from_params() - underscore_locale = @locale.gsub '-', '_' - I18n.with_locale(@locale) do - @query = params[:query] - if @query == "" - @query = nil - end - @page = params[:page] - if @page == "" - @page = nil - end - @public_bodies = PublicBody.joins(:translations).where(@query.nil? ? "public_body_translations.locale = '#{underscore_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 = '#{underscore_locale}')", @query, @query, @query]).paginate :order => "public_body_translations.name", :page => @page, :per_page => 100 - end - @public_bodies_by_tag = PublicBody.find_by_tag(@query) - end - def list - self._lookup_query_internal - end - - def mass_tag_add - self._lookup_query_internal - - if params[:new_tag] and params[:new_tag] != "" - if params[:table_name] == 'exact' - bodies = @public_bodies_by_tag - elsif params[:table_name] == 'substring' - bodies = @public_bodies - else - raise "Unknown table_name " + params[:table_name] - end - for body in bodies - body.add_tag_if_not_already_present(params[:new_tag]) - end - flash[:notice] = "Added tag to table of bodies." - end - - redirect_to admin_body_list_url(:query => @query, :page => @page) - end - - 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 - LIMIT 20 - ") - @stats = { - "total" => PublicBody.count, - "entered" => PublicBody.count(:conditions => "publication_scheme != ''") - } + def index + lookup_query end def show @@ -111,7 +51,7 @@ class AdminPublicBodyController < AdminController @change_request.send_response(params[:subject], response_text) end flash[:notice] = 'PublicBody was successfully created.' - redirect_to admin_body_show_url(@public_body) + redirect_to admin_body_url(@public_body) else render :action => 'new' end @@ -147,7 +87,7 @@ class AdminPublicBodyController < AdminController @change_request.send_response(params[:subject], params[:response]) end flash[:notice] = 'PublicBody was successfully updated.' - redirect_to admin_body_show_url(@public_body) + redirect_to admin_body_url(@public_body) else render :action => 'edit' end @@ -161,17 +101,53 @@ class AdminPublicBodyController < AdminController if public_body.info_requests.size > 0 flash[:notice] = "There are requests associated with the authority, so can't destroy it" - redirect_to admin_body_show_url(public_body) + redirect_to admin_body_url(public_body) return end public_body.tag_string = "" public_body.destroy flash[:notice] = "PublicBody was successfully destroyed." - redirect_to admin_body_list_url + redirect_to admin_bodies_url end end + def mass_tag_add + lookup_query + + if params[:new_tag] and params[:new_tag] != "" + if params[:table_name] == 'exact' + bodies = @public_bodies_by_tag + elsif params[:table_name] == 'substring' + bodies = @public_bodies + else + raise "Unknown table_name " + params[:table_name] + end + for body in bodies + body.add_tag_if_not_already_present(params[:new_tag]) + end + flash[:notice] = "Added tag to table of bodies." + end + + redirect_to admin_bodies_url(:query => @query, :page => @page) + end + + 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 + LIMIT 20 + ") + @stats = { + "total" => PublicBody.count, + "entered" => PublicBody.count(:conditions => "publication_scheme != ''") + } + end + def import_csv @notes = "" @errors = "" @@ -251,4 +227,24 @@ class AdminPublicBodyController < AdminController return csv_contents end + def lookup_query + @locale = self.locale_from_params() + underscore_locale = @locale.gsub '-', '_' + I18n.with_locale(@locale) do + @query = params[:query] + if @query == "" + @query = nil + end + @page = params[:page] + if @page == "" + @page = nil + end + @public_bodies = PublicBody.joins(:translations).where(@query.nil? ? "public_body_translations.locale = '#{underscore_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 = '#{underscore_locale}')", @query, @query, @query]).paginate :order => "public_body_translations.name", :page => @page, :per_page => 100 + end + @public_bodies_by_tag = PublicBody.find_by_tag(@query) + end + end diff --git a/app/controllers/admin_raw_email_controller.rb b/app/controllers/admin_raw_email_controller.rb new file mode 100644 index 000000000..1b3ee2871 --- /dev/null +++ b/app/controllers/admin_raw_email_controller.rb @@ -0,0 +1,45 @@ +# app/controllers/admin_raw_email_controller.rb: +# Controller for managing raw emails from the admin interface. +# +# Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ + +class AdminRawEmailController < AdminController + + def show + @raw_email = RawEmail.find(params[:id]) + respond_to do |format| + format.html do + # For the holding pen, try to guess where it should be ... + @holding_pen = false + if (@raw_email.incoming_message.info_request == InfoRequest.holding_pen_request && !@raw_email.incoming_message.empty_from_field?) + @holding_pen = true + + # 1. Use domain of email to try and guess which public body it + # is associated with, so we can display that. + email = @raw_email.incoming_message.from_email + domain = PublicBody.extract_domain_from_email(email) + + if domain.nil? + @public_bodies = [] + else + @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) + + # 3. Give a reason why it's in the holding pen + last_event = InfoRequestEvent.find_by_incoming_message_id(@raw_email.incoming_message.id) + @rejected_reason = last_event.params[:rejected_reason] || "unknown reason" + end + end + format.text do + response.content_type = 'message/rfc822' + render :text => @raw_email.data + end + end + end + +end diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb index 8f023bf12..1e083f57e 100644 --- a/app/controllers/admin_request_controller.rb +++ b/app/controllers/admin_request_controller.rb @@ -4,15 +4,9 @@ # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. # Email: hello@mysociety.org; WWW: http://www.mysociety.org/ -require 'ostruct' - class AdminRequestController < AdminController - def index - list - render :action => 'list' - end - def list + def index @query = params[:query] if @query info_requests = InfoRequest.where(["lower(title) like lower('%'||?||'%')", @query]) @@ -35,36 +29,6 @@ class AdminRequestController < AdminController :locals => vars_for_explanation) end - def resend - @outgoing_message = OutgoingMessage.find(params[:outgoing_message_id]) - @outgoing_message.prepare_message_for_resend - - mail_message = case @outgoing_message.message_type - when 'initial_request' - OutgoingMailer.initial_request( - @outgoing_message.info_request, - @outgoing_message - ).deliver - when 'followup' - OutgoingMailer.followup( - @outgoing_message.info_request, - @outgoing_message, - @outgoing_message.incoming_message_followup - ).deliver - else - raise "Message id #{id} has type '#{message_type}' which cannot be resent" - end - - @outgoing_message.record_email_delivery( - mail_message.to_addrs.join(', '), - mail_message.message_id, - 'resent' - ) - - flash[:notice] = "Outgoing message resent" - redirect_to admin_request_show_url(@outgoing_message.info_request) - end - def edit @info_request = InfoRequest.find(params[:id]) end @@ -108,13 +72,13 @@ class AdminRequestController < AdminController # expire cached files expire_for_request(@info_request) flash[:notice] = 'Request successfully updated.' - redirect_to admin_request_show_url(@info_request) + redirect_to admin_request_url(@info_request) else render :action => 'edit' end end - def fully_destroy + def destroy @info_request = InfoRequest.find(params[:id]) user = @info_request.user @@ -125,36 +89,12 @@ class AdminRequestController < AdminController expire_for_request(@info_request) email = user.try(:email) ? user.email : 'This request is external so has no associated user' flash[:notice] = "Request #{ url_title } has been completely destroyed. Email of user who made request: #{ email }" - redirect_to admin_request_list_url - end - - def edit_comment - @comment = Comment.find(params[:id]) - end - - def update_comment - @comment = Comment.find(params[:id]) - - old_body = @comment.body - 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_current_user(), - :old_body => old_body, :body => @comment.body, - :old_visible => old_visible, :visible => @comment.visible, - }) - flash[:notice] = 'Comment successfully updated.' - redirect_to admin_request_show_url(@comment.info_request) - else - render :action => 'edit_comment' - end + redirect_to admin_requests_url end # change user or public body of a request magically - def move_request - info_request = InfoRequest.find(params[:info_request_id]) + def move + info_request = InfoRequest.find(params[:id]) if params[:commit] == 'Move request to user' && !params[:user_url_name].blank? old_user = info_request.user destination_user = User.find_by_url_name(params[:user_url_name]) @@ -172,7 +112,7 @@ class AdminRequestController < AdminController info_request.reindex_request_events flash[:notice] = "Message has been moved to new user" end - redirect_to admin_request_show_url(info_request) + redirect_to admin_request_url(info_request) elsif params[:commit] == 'Move request to authority' && !params[:public_body_url_name].blank? old_public_body = info_request.public_body destination_public_body = PublicBody.find_by_url_name(params[:public_body_url_name]) @@ -191,10 +131,10 @@ class AdminRequestController < AdminController flash[:notice] = "Request has been moved to new body" end - redirect_to admin_request_show_url(info_request) + redirect_to admin_request_url(info_request) else flash[:error] = "Please enter the user or authority to move the request to" - redirect_to admin_request_show_url(info_request) + redirect_to admin_request_url(info_request) end end @@ -218,7 +158,7 @@ class AdminRequestController < AdminController if !info_request.public_body.is_foi_officer?(user) flash[:notice] = user.email + " is not an email at the domain @" + info_request.public_body.foi_officer_domain_required + ", so won't be able to upload." - redirect_to admin_request_show_url(info_request) + redirect_to admin_request_url(info_request) return end @@ -230,61 +170,11 @@ class AdminRequestController < AdminController post_redirect.save! url = confirm_url(:email_token => post_redirect.email_token) - flash[:notice] = ("Send \"#{name}\" <<a href=\"mailto:#{email}\">#{email}</a>> this URL: <a href=\"#{url}\">#{url}</a> - it will log them in and let them upload a response to this request.").html_safe - redirect_to admin_request_show_url(info_request) - end - - def show_raw_email - @raw_email = RawEmail.find(params[:id]) - # For the holding pen, try to guess where it should be ... - @holding_pen = false - if (@raw_email.incoming_message.info_request == InfoRequest.holding_pen_request && !@raw_email.incoming_message.empty_from_field?) - @holding_pen = true - - # 1. Use domain of email to try and guess which public body it - # is associated with, so we can display that. - email = @raw_email.incoming_message.from_email - domain = PublicBody.extract_domain_from_email(email) - - if domain.nil? - @public_bodies = [] - else - @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) - - # 3. Give a reason why it's in the holding pen - last_event = InfoRequestEvent.find_by_incoming_message_id(@raw_email.incoming_message.id) - @rejected_reason = last_event.params[:rejected_reason] || "unknown reason" - end - end - - def download_raw_email - @raw_email = RawEmail.find(params[:id]) - - response.content_type = 'message/rfc822' - render :text => @raw_email.data - end - - # used so due dates get fixed - def mark_event_as_clarification - info_request_event = InfoRequestEvent.find(params[:info_request_event_id]) - if info_request_event.event_type != 'response' - raise Exception("can only mark responses as requires clarification") - end - info_request_event.described_state = 'waiting_clarification' - info_request_event.calculated_state = 'waiting_clarification' - # TODO: deliberately don't update described_at so doesn't reenter search? - info_request_event.save! - - flash[:notice] = "Old response marked as having been a clarification" - redirect_to admin_request_show_url(info_request_event.info_request) + flash[:notice] = ("Send \"#{CGI.escapeHTML(name)}\" <<a href=\"mailto:#{email}\">#{email}</a>> this URL: <a href=\"#{url}\">#{url}</a> - it will log them in and let them upload a response to this request.").html_safe + redirect_to admin_request_url(info_request) end - def hide_request + def hide ActiveRecord::Base.transaction do subject = params[:subject] explanation = params[:explanation] @@ -314,7 +204,7 @@ class AdminRequestController < AdminController end # expire cached files expire_for_request(info_request) - redirect_to admin_request_show_url(info_request) + redirect_to admin_request_url(info_request) end end diff --git a/app/controllers/admin_track_controller.rb b/app/controllers/admin_track_controller.rb index 085c9c6cc..63ee5c12e 100644 --- a/app/controllers/admin_track_controller.rb +++ b/app/controllers/admin_track_controller.rb @@ -5,7 +5,8 @@ # Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class AdminTrackController < AdminController - def list + + def index @query = params[:query] if @query track_things = TrackThing.where(["lower(track_query) like lower('%'||?||'%')", @query]) @@ -13,7 +14,14 @@ class AdminTrackController < AdminController track_things = TrackThing end @admin_tracks = track_things.paginate :order => "created_at desc", :page => params[:page], :per_page => 100 - @popular = ActiveRecord::Base.connection.select_all("select count(*) as count, title, info_request_id from track_things join info_requests on info_request_id = info_requests.id where info_request_id is not null group by info_request_id, title order by count desc limit 10;") + @popular = ActiveRecord::Base.connection.select_all("select count(*) as count, title, info_request_id from track_things join info_requests on info_request_id = info_requests.id where info_request_id is not null group by info_request_id, title order by count desc limit 10;") + end + + def destroy + track_thing = TrackThing.find(params[:id].to_i) + track_thing.destroy + flash[:notice] = 'Track destroyed' + redirect_to admin_user_url(track_thing.tracking_user) end private diff --git a/app/controllers/admin_user_controller.rb b/app/controllers/admin_user_controller.rb index a6438e151..7ef461594 100644 --- a/app/controllers/admin_user_controller.rb +++ b/app/controllers/admin_user_controller.rb @@ -5,12 +5,8 @@ # Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class AdminUserController < AdminController - def index - list - render :action => 'list' - end - def list + def index @query = params[:query] if @query users = User.where(["lower(name) like lower('%'||?||'%') or @@ -21,20 +17,11 @@ class AdminUserController < AdminController @admin_users = users.paginate :order => "name", :page => params[:page], :per_page => 100 end - def list_banned - @banned_users = User.paginate :order => "name", :page => params[:page], :per_page => 100, - :conditions => ["ban_text <> ''"] - end - def show # 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 - def edit @admin_user = User.find(params[:id]) end @@ -53,17 +40,19 @@ class AdminUserController < AdminController if @admin_user.valid? @admin_user.save! flash[:notice] = 'User successfully updated.' - redirect_to admin_user_show_url(@admin_user) + redirect_to admin_user_url(@admin_user) else render :action => 'edit' end end - def destroy_track - track_thing = TrackThing.find(params[:track_id].to_i) - track_thing.destroy - flash[:notice] = 'Track destroyed' - redirect_to admin_user_show_url(track_thing.tracking_user) + def banned + @banned_users = User.paginate :order => "name", :page => params[:page], :per_page => 100, + :conditions => ["ban_text <> ''"] + end + + def show_bounce_message + @admin_user = User.find(params[:id]) end def clear_bounce @@ -71,7 +60,7 @@ class AdminUserController < AdminController user.email_bounced_at = nil user.email_bounce_message = "" user.save! - redirect_to admin_user_show_url(user) + redirect_to admin_user_url(user) end def login_as @@ -87,16 +76,12 @@ class AdminUserController < AdminController def clear_profile_photo @admin_user = User.find(params[:id]) - if !request.post? - raise "Can only clear profile photo from POST request" - end - if @admin_user.profile_photo @admin_user.profile_photo.destroy end flash[:notice] = "Profile photo cleared" - redirect_to admin_user_show_url(@admin_user) + redirect_to admin_user_url(@admin_user) end def modify_comment_visibility diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4d3f40d40..dbd879a1c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -14,9 +14,14 @@ class ApplicationController < ActionController::Base end class RouteNotFound < StandardError end + protect_from_forgery + # assign our own handler method for non-local exceptions rescue_from Exception, :with => :render_exception + # Add some security-related headers (see config/initializers/secure_headers.rb) + ensure_security_headers + # Standard headers, footers and navigation for whole site layout "default" include FastGettext::Translation # make functions like _, n_, N_ etc available) @@ -27,6 +32,8 @@ class ApplicationController < ActionController::Base before_filter :check_in_post_redirect before_filter :session_remember_me before_filter :set_vary_header + before_filter :validate_session_timestamp + after_filter :persist_session_timestamp def set_vary_header response.headers['Vary'] = 'Cookie' @@ -118,6 +125,29 @@ class ApplicationController < ActionController::Base end end + # Set a TTL for non "remember me" sessions so that the cookie + # is not replayable forever + SESSION_TTL = 3.hours + def validate_session_timestamp + if session[:user_id] && session.key?(:ttl) && session[:ttl] < SESSION_TTL.ago + clear_session_credentials + redirect_to signin_path + end + end + + def persist_session_timestamp + session[:ttl] = Time.now if session[:user_id] && !session[:remember_me] + end + + # Logout form + def clear_session_credentials + session[:user_id] = nil + session[:user_circumstance] = nil + session[:remember_me] = false + session[:using_admin] = nil + session[:admin_name] = nil + end + def render_exception(exception) # In development or the admin interface let Rails handle the exception # with its stack trace templates diff --git a/app/controllers/comment_controller.rb b/app/controllers/comment_controller.rb index 2c0037577..890e9faaa 100644 --- a/app/controllers/comment_controller.rb +++ b/app/controllers/comment_controller.rb @@ -10,7 +10,6 @@ class CommentController < ApplicationController before_filter :create_track_thing, :only => [ :new ] before_filter :reject_unless_comments_allowed, :only => [ :new ] before_filter :reject_if_user_banned, :only => [ :new ] - protect_from_forgery :only => [ :new ] def new if params[:comment] diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index a334abcb7..39e7616ed 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -10,7 +10,6 @@ require 'open-uri' class RequestController < ApplicationController before_filter :check_read_only, :only => [ :new, :show_response, :describe_state, :upload_response ] - protect_from_forgery :only => [ :new, :show_response, :describe_state, :upload_response ] # See ActionController::RequestForgeryProtection for details before_filter :check_batch_requests_and_user_allowed, :only => [ :select_authorities, :new_batch ] MAX_RESULTS = 500 PER_PAGE = 25 @@ -770,13 +769,13 @@ class RequestController < ApplicationController get_attachment_internal(false) return unless @attachment - # 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) # 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]) || 'application/octet-stream' + # Prevent spam to magic request address. Note that the binary + # subsitution method used depends on the content type + @incoming_message.apply_masks!(@attachment.body, @attachment.content_type) if response.content_type == 'text/html' @attachment.body = ActionController::Base.helpers.sanitize(@attachment.body) end @@ -808,10 +807,9 @@ class RequestController < ApplicationController :body_prefix => render_to_string(:partial => "request/view_html_prefix") } ) - - @incoming_message.html_mask_stuff!(html) - response.content_type = 'text/html' + @incoming_message.apply_masks!(html, response.content_type) + render :text => html end @@ -845,7 +843,15 @@ class RequestController < ApplicationController end # check filename in URL matches that in database (use a censor rule if you want to change a filename) - raise ActiveRecord::RecordNotFound.new("please use same filename as original file has, display: '" + @attachment.display_filename + "' old_display: '" + @attachment.old_display_filename + "' original: '" + @original_filename + "'") if @attachment.display_filename != @original_filename && @attachment.old_display_filename != @original_filename + if @attachment.display_filename != @original_filename && @attachment.old_display_filename != @original_filename + msg = 'please use same filename as original file has, display: ' + msg += "'#{ @attachment.display_filename }' " + msg += 'old_display: ' + msg += "'#{ @attachment.old_display_filename }' " + msg += 'original: ' + msg += "'#{ @original_filename }'" + raise ActiveRecord::RecordNotFound.new(msg) + end @attachment_url = get_attachment_url(:id => @incoming_message.info_request_id, :incoming_message_id => @incoming_message.id, :part => @part_number, @@ -904,10 +910,18 @@ class RequestController < ApplicationController # Type ahead search def search_typeahead - # Since acts_as_xapian doesn't support the Partial match flag, we work around it - # by making the last work a wildcard, which is quite the same - query = params[:q] - @xapian_requests = perform_search_typeahead(query, InfoRequestEvent) + # Since acts_as_xapian doesn't support the Partial match flag, we work + # around it by making the last word a wildcard, which is quite the same + @query = '' + + if params.key?(:requested_from) + @query << "requested_from:#{ params[:requested_from] } " + end + + @per_page = (params.fetch(:per_page) { 25 }).to_i + + @query << params[:q] + @xapian_requests = perform_search_typeahead(@query, InfoRequestEvent, @per_page) render :partial => "request/search_ahead" end diff --git a/app/controllers/track_controller.rb b/app/controllers/track_controller.rb index 144f4d55a..4b272797f 100644 --- a/app/controllers/track_controller.rb +++ b/app/controllers/track_controller.rb @@ -6,9 +6,6 @@ # Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class TrackController < ApplicationController - - protect_from_forgery # See ActionController::RequestForgeryProtection for details - before_filter :medium_cache # Track all updates to a particular request diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index 43eb99c58..56f42891d 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -7,15 +7,8 @@ require 'set' class UserController < ApplicationController - layout :select_layout - protect_from_forgery :only => [ :contact, - :set_profile_photo, - :signchangeemail, - :clear_profile_photo, - :set_profile_about_me ] # See ActionController::RequestForgeryProtection for details - # Show page about a user def show long_cache @@ -260,16 +253,8 @@ class UserController < ApplicationController do_post_redirect post_redirect end - # Logout form - def _do_signout - session[:user_id] = nil - session[:user_circumstance] = nil - session[:remember_me] = false - session[:using_admin] = nil - session[:admin_name] = nil - end def signout - self._do_signout + clear_session_credentials if params[:r] redirect_to URI.parse(params[:r]).path else diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index 151e53758..d13fea79b 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -21,17 +21,17 @@ module AdminHelper def request_both_links(info_request) link_to(eye, request_path(info_request), :title => "view request on public website") + " " + - link_to(info_request.title, admin_request_show_path(info_request), :title => "view full details") + link_to(info_request.title, admin_request_path(info_request), :title => "view full details") end def public_body_both_links(public_body) link_to(eye, public_body_path(public_body), :title => "view authority on public website") + " " + - link_to(h(public_body.name), admin_body_show_path(public_body), :title => "view full details") + link_to(h(public_body.name), admin_body_path(public_body), :title => "view full details") end def user_both_links(user) link_to(eye, user_path(user), :title => "view user's page on public website") + " " + - link_to(h(user.name), admin_user_show_path(user), :title => "view full details") + link_to(h(user.name), admin_user_path(user), :title => "view full details") end def comment_visibility(comment) diff --git a/app/helpers/link_to_helper.rb b/app/helpers/link_to_helper.rb index 3709469cf..44d6c6f5f 100755 --- a/app/helpers/link_to_helper.rb +++ b/app/helpers/link_to_helper.rb @@ -116,7 +116,7 @@ module LinkToHelper if request.is_external? external_text || (request.external_user_name || _("Anonymous user")) + " (external)" else - link_to(internal_text || request.user.name, admin_user_show_url(request.user)) + link_to(internal_text || request.user.name, admin_user_url(request.user)) end end @@ -178,7 +178,7 @@ module LinkToHelper end def user_admin_link(user, name="admin", cls=nil) - link_to name, admin_user_show_url(user), :class => cls + link_to name, admin_user_url(user), :class => cls end # Tracks. feed can be 'track' or 'feed' diff --git a/app/mailers/outgoing_mailer.rb b/app/mailers/outgoing_mailer.rb index 797bf9fdd..19054b4e2 100644 --- a/app/mailers/outgoing_mailer.rb +++ b/app/mailers/outgoing_mailer.rb @@ -21,7 +21,7 @@ class OutgoingMailer < ApplicationMailer mail(:from => info_request.incoming_name_and_email, :to => info_request.recipient_name_and_email, - :subject => info_request.email_subject_request) + :subject => info_request.email_subject_request(:html => false)) end # Later message to public body regarding existing request @@ -32,7 +32,7 @@ class OutgoingMailer < ApplicationMailer mail(:from => info_request.incoming_name_and_email, :to => OutgoingMailer.name_and_email_for_followup(info_request, incoming_message_followup), - :subject => OutgoingMailer.subject_for_followup(info_request, outgoing_message)) + :subject => OutgoingMailer.subject_for_followup(info_request, outgoing_message, :html => false)) end # TODO: the condition checking valid_to_reply_to? also appears in views/request/_followup.html.erb, @@ -67,11 +67,12 @@ class OutgoingMailer < ApplicationMailer end end # Subject to use for followup - def OutgoingMailer.subject_for_followup(info_request, outgoing_message) + def OutgoingMailer.subject_for_followup(info_request, outgoing_message, options = {}) if outgoing_message.what_doing == 'internal_review' - return "Internal review of " + info_request.email_subject_request + return "Internal review of " + info_request.email_subject_request(:html => options[:html]) else - return info_request.email_subject_followup(outgoing_message.incoming_message_followup) + return info_request.email_subject_followup(:incoming_message => outgoing_message.incoming_message_followup, + :html => options[:html]) end end # Whether we have a valid email address for a followup diff --git a/app/mailers/request_mailer.rb b/app/mailers/request_mailer.rb index 768257ba8..c9decc6db 100644 --- a/app/mailers/request_mailer.rb +++ b/app/mailers/request_mailer.rb @@ -20,7 +20,7 @@ class RequestMailer < ApplicationMailer mail(:from => from_user.name_and_email, :to => info_request.incoming_name_and_email, - :subject => info_request.email_subject_followup) + :subject => info_request.email_subject_followup(:html => false)) end # Used when a response is uploaded using the API @@ -58,7 +58,7 @@ class RequestMailer < ApplicationMailer user = set_by || info_request.user @reported_by = user @url = request_url(info_request) - @admin_url = admin_request_show_url(info_request) + @admin_url = admin_request_url(info_request) @info_request = info_request @message = message diff --git a/app/models/holiday.rb b/app/models/holiday.rb index 4c4941589..34044683a 100644 --- a/app/models/holiday.rb +++ b/app/models/holiday.rb @@ -22,6 +22,8 @@ class Holiday < ActiveRecord::Base + validates_presence_of :day + def self.holidays @@holidays ||= all.collect { |h| h.day }.to_set end diff --git a/app/models/holiday_import.rb b/app/models/holiday_import.rb new file mode 100644 index 000000000..c6019fac0 --- /dev/null +++ b/app/models/holiday_import.rb @@ -0,0 +1,93 @@ +class HolidayImport + + include ActiveModel::Validations + + attr_accessor :holidays, + :ical_feed_url, + :start_year, + :end_year, + :start_date, + :end_date, + :source, + :populated + + validate :all_holidays_valid + validates_inclusion_of :source, :in => %w( suggestions feed ) + validates_presence_of :ical_feed_url, + :if => proc { |holiday_import| holiday_import.source == 'feed' } + + def initialize(opts = {}) + @populated = false + @start_year = opts.fetch(:start_year, Time.now.year).to_i + @end_year = opts.fetch(:end_year, Time.now.year).to_i + @start_date = Date.civil(start_year, 1, 1) + @end_date = Date.civil(end_year, 12, 31) + @source = opts.fetch(:source, 'suggestions') + @ical_feed_url = opts.fetch(:ical_feed_url, nil) + @country_code = AlaveteliConfiguration::iso_country_code.downcase + self.holidays_attributes = opts.fetch(:holidays_attributes, []) + end + + def populate + source == 'suggestions' ? populate_from_suggestions : populate_from_ical_feed + @populated = true + end + + def suggestions_country_name + IsoCountryCodes.find(@country_code).name if @country_code + end + + def period + start_year == end_year ? "#{start_year}" : "#{start_year}-#{end_year}" + end + + def save + holidays.all?(&:save) + end + + def holidays_attributes=(incoming_data) + incoming_data.each{ |offset, incoming| self.holidays << Holiday.new(incoming) } + end + + def holidays + @holidays ||= [] + end + + private + + def all_holidays_valid + errors.add(:base, 'These holidays could not be imported') unless holidays.all?(&:valid?) + end + + def populate_from_ical_feed + begin + cal_file = open(ical_feed_url) + cals = Icalendar.parse(cal_file, strict=false) + cal = cals.first + cal.events.each{ |cal_event| populate_from_ical_event(cal_event) } + rescue Errno::ENOENT, Exception => e + if e.message == 'Invalid line in calendar string!' + errors.add(:ical_feed_url, "Sorry, there's a problem with the format of that feed.") + elsif e.message.starts_with 'No such file or directory' + errors.add(:ical_feed_url, "Sorry we couldn't find that feed.") + else + raise e + end + end + end + + def populate_from_ical_event(cal_event) + if cal_event.dtstart >= start_date and cal_event.dtstart <= end_date + holidays << Holiday.new(:description => cal_event.summary, + :day => cal_event.dtstart) + end + end + + def populate_from_suggestions + holiday_info = Holidays.between(start_date, end_date, @country_code.to_sym, :observed) + holiday_info.each do |holiday_info_hash| + holidays << Holiday.new(:description => holiday_info_hash[:name], + :day => holiday_info_hash[:date]) + end + end +end diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb index db6722976..658ee969a 100644 --- a/app/models/incoming_message.rb +++ b/app/models/incoming_message.rb @@ -52,17 +52,6 @@ class IncomingMessage < ActiveRecord::Base has_prominence - # See binary_mask_stuff function below. It just test for inclusion - # in this hash, not the value of the right hand side. - DoNotBinaryMask = { - 'image/tiff' => 1, - 'image/gif' => 1, - 'image/jpeg' => 1, - 'image/png' => 1, - 'image/bmp' => 1, - 'application/zip' => 1, - } - # Given that there are in theory many info request events, a convenience method for # getting the response event def response_event @@ -218,111 +207,10 @@ class IncomingMessage < ActiveRecord::Base end end - # Converts email addresses we know about into textual descriptions of them - def mask_special_emails!(text) - # TODO: can later display some of these special emails as actual emails, - # if they are public anyway. For now just be precautionary and only - # put in descriptions of them in square brackets. - if self.info_request.public_body.is_followupable? - text.gsub!(self.info_request.public_body.request_email, _("[{{public_body}} request email]", :public_body => self.info_request.public_body.short_or_long_name)) - end - text.gsub!(self.info_request.incoming_email, _('[FOI #{{request}} email]', :request => self.info_request.id.to_s) ) - text.gsub!(AlaveteliConfiguration::contact_email, _("[{{site_name}} contact email]", :site_name => AlaveteliConfiguration::site_name) ) - end - - # Replaces all email addresses in (possibly binary data) with equal length alternative ones. - # Also replaces censor items - def binary_mask_stuff!(text, content_type) - # See if content type is one that we mask - things like zip files and - # images may get broken if we try to. We err on the side of masking too - # much, as many unknown types will really be text. - if DoNotBinaryMask.include?(content_type) - return - end - - # Special cases for some content types - if content_type == 'application/pdf' - uncompressed_text = nil - uncompressed_text = AlaveteliExternalCommand.run("pdftk", "-", "output", "-", "uncompress", :stdin_string => text) - # if we managed to uncompress the PDF... - if !uncompressed_text.nil? && !uncompressed_text.empty? - # then censor stuff (making a copy so can compare again in a bit) - censored_uncompressed_text = uncompressed_text.dup - self._binary_mask_stuff_internal!(censored_uncompressed_text) - # if the censor rule removed something... - if censored_uncompressed_text != uncompressed_text - # then use the altered file (recompressed) - recompressed_text = nil - if AlaveteliConfiguration::use_ghostscript_compression == true - command = ["gs", "-sDEVICE=pdfwrite", "-dCompatibilityLevel=1.4", "-dPDFSETTINGS=/screen", "-dNOPAUSE", "-dQUIET", "-dBATCH", "-sOutputFile=-", "-"] - else - command = ["pdftk", "-", "output", "-", "compress"] - end - recompressed_text = AlaveteliExternalCommand.run(*(command + [{:stdin_string=>censored_uncompressed_text}])) - if recompressed_text.nil? || recompressed_text.empty? - # 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 - logger.warn "Unable to compress PDF; problem with your pdftk version?" - end - if !recompressed_text.nil? && !recompressed_text.empty? - text.replace recompressed_text - end - end - end - return - end - - self._binary_mask_stuff_internal!(text) - end - - # Used by binary_mask_stuff - replace text in place - def _binary_mask_stuff_internal!(text) - # Keep original size, so can check haven't resized it - orig_size = text.mb_chars.size - - # Replace ASCII email addresses... - text.gsub!(MySociety::Validate.email_find_regexp) do |email| - email.gsub(/[^@.]/, 'x') - end - - # And replace UCS-2 ones (for Microsoft Office documents)... - # Find emails, by finding them in parts of text that have ASCII - # equivalents to the UCS-2 - ascii_chars = text.gsub(/\0/, "") - emails = ascii_chars.scan(MySociety::Validate.email_find_regexp) - - # Convert back to UCS-2, making a mask at the same time - if String.method_defined?(:encode) - emails.map! do |email| - # We want the ASCII representation of UCS-2 - [email[0].encode('UTF-16LE').force_encoding('US-ASCII'), - email[0].gsub(/[^@.]/, 'x').encode('UTF-16LE').force_encoding('US-ASCII')] - end - else - emails.map! {|email| [ - Iconv.conv('ucs-2le', 'ascii', email[0]), - Iconv.conv('ucs-2le', 'ascii', email[0].gsub(/[^@.]/, 'x')) - ] } - end - - # Now search and replace the UCS-2 email with the UCS-2 mask - for email, mask in emails - text.gsub!(email, mask) - end - - # Replace censor items - self.info_request.apply_censor_rules_to_binary!(text) - - raise "internal error in binary_mask_stuff" if text.mb_chars.size != orig_size - return text - end - - # Removes censored stuff from from HTML conversion of downloaded binaries - def html_mask_stuff!(html) - self.mask_special_emails!(html) - self.remove_privacy_sensitive_things!(html) + def apply_masks!(text, content_type) + mask_options = { :censor_rules => info_request.applicable_censor_rules, + :masks => info_request.masks } + AlaveteliTextMasker.apply_masks!(text, content_type, mask_options) end # Lotus notes quoting yeuch! @@ -346,26 +234,6 @@ class IncomingMessage < ActiveRecord::Base end - # Remove emails, mobile phones and other details FOI officers ask us to remove. - def remove_privacy_sensitive_things!(text) - # Remove any email addresses - we don't want bounce messages to leak out - # either the requestor's email address or the request's response email - # address out onto the internet - text.gsub!(MySociety::Validate.email_find_regexp, "[email address]") - - # Mobile phone numbers - # http://www.whatdotheyknow.com/request/failed_test_purchases_off_licenc#incoming-1013 - # http://www.whatdotheyknow.com/request/selective_licensing_statistics_i#incoming-550 - # http://www.whatdotheyknow.com/request/common_purpose_training_graduate#incoming-774 - text.gsub!(/(Mobile|Mob)([\s\/]*(Fax|Tel))*\s*:?[\s\d]*\d/, "[mobile number]") - - # Remove WhatDoTheyKnow signup links - text.gsub!(/http:\/\/#{AlaveteliConfiguration::domain}\/c\/[^\s]+/, "[WDTK login link]") - - # Remove things from censor rules - self.info_request.apply_censor_rules_to_text!(text) - end - # Remove quoted sections from emails (eventually the aim would be for this # to do as good a job as GMail does) TODO: bet it needs a proper parser @@ -465,9 +333,8 @@ class IncomingMessage < ActiveRecord::Base raise "main body text more than 1 MB, need to implement clipping like for attachment text, or there is some other MIME decoding problem or similar" end - # remove emails for privacy/anti-spam reasons - self.mask_special_emails!(text) - self.remove_privacy_sensitive_things!(text) + # apply masks for this message + apply_masks!(text, 'text/html') # Remove existing quoted sections folded_quoted_text = self.remove_lotus_quoting(text, 'FOLDED_QUOTED_SECTION') @@ -735,7 +602,14 @@ class IncomingMessage < ActiveRecord::Base text = MySociety::Format.simplify_angle_bracketed_urls(text) text = CGI.escapeHTML(text) text = MySociety::Format.make_clickable(text, :contract => 1) - text.gsub!(/\[(email address|mobile number)\]/, '[<a href="/help/officers#mobiles">\1</a>]') + + # add a helpful link to email addresses and mobile numbers removed + # by apply_masks! + email_pattern = Regexp.escape(_("email address")) + mobile_pattern = Regexp.escape(_("mobile number")) + text.gsub!(/\[(#{email_pattern}|#{mobile_pattern})\]/, + '[<a href="/help/officers#mobiles">\1</a>]') + if collapse_quoted_sections text = text.gsub(/(\s*FOLDED_QUOTED_SECTION\s*)+/m, "FOLDED_QUOTED_SECTION") text.strip! @@ -773,8 +647,8 @@ class IncomingMessage < ActiveRecord::Base # Returns text version of attachment text def get_attachment_text_full text = self._get_attachment_text_internal - self.mask_special_emails!(text) - self.remove_privacy_sensitive_things!(text) + apply_masks!(text, 'text/html') + # This can be useful for memory debugging #STDOUT.puts 'xxx '+ MySociety::DebugHelpers::allocated_string_size_around_gc diff --git a/app/models/info_request.rb b/app/models/info_request.rb index d0052603a..2b60e13d8 100644 --- a/app/models/info_request.rb +++ b/app/models/info_request.rb @@ -292,13 +292,18 @@ public end # Subject lines for emails about the request - def email_subject_request - _('{{law_used_full}} request - {{title}}',:law_used_full=>self.law_used_full,:title=>self.title.html_safe) + def email_subject_request(opts = {}) + html = opts.fetch(:html, true) + _('{{law_used_full}} request - {{title}}', + :law_used_full => self.law_used_full, + :title => (html ? title : title.html_safe)) end - def email_subject_followup(incoming_message = nil) + def email_subject_followup(opts = {}) + incoming_message = opts.fetch(:incoming_message, nil) + html = opts.fetch(:html, true) if incoming_message.nil? || !incoming_message.valid_to_reply_to? || !incoming_message.subject - 'Re: ' + self.email_subject_request + 'Re: ' + self.email_subject_request(:html => html) else if incoming_message.subject.match(/^Re:/i) incoming_message.subject @@ -1148,6 +1153,22 @@ public return binary end + # Masks we apply to text associated with this request convert email addresses + # we know about into textual descriptions of them + def masks + masks = [{ :to_replace => incoming_email, + :replacement => _('[FOI #{{request}} email]', + :request => id.to_s) }, + { :to_replace => AlaveteliConfiguration::contact_email, + :replacement => _("[{{site_name}} contact email]", + :site_name => AlaveteliConfiguration::site_name)} ] + if public_body.is_followupable? + masks << { :to_replace => public_body.request_email, + :replacement => _("[{{public_body}} request email]", + :public_body => public_body.short_or_long_name) } + end + end + def is_owning_user?(user) !user.nil? && (user.id == user_id || user.owns_every_request?) end @@ -1345,9 +1366,9 @@ public end def InfoRequest.find_in_state(state) - find(:all, :select => '*, ' + last_event_time_clause + ' as last_event_time', - :conditions => ["described_state = ?", state], - :order => "last_event_time") + select("*, #{ last_event_time_clause } as last_event_time"). + where(:described_state => state). + order('last_event_time') end private diff --git a/app/models/info_request_event.rb b/app/models/info_request_event.rb index 9dde3ba80..635ba8f58 100644 --- a/app/models/info_request_event.rb +++ b/app/models/info_request_event.rb @@ -161,11 +161,10 @@ class InfoRequestEvent < ActiveRecord::Base end def incoming_message_selective_columns(fields) - 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}" - ) + message = IncomingMessage.select("#{ fields }, incoming_messages.info_request_id"). + joins('INNER JOIN info_request_events ON incoming_messages.id = incoming_message_id'). + where('info_request_events.id = ?', id) + message = message[0] if !message.nil? message.info_request = InfoRequest.find(message.info_request_id) diff --git a/app/models/public_body_category.rb b/app/models/public_body_category.rb index bb83c4c82..c313e5734 100644 --- a/app/models/public_body_category.rb +++ b/app/models/public_body_category.rb @@ -2,11 +2,8 @@ # # Table name: public_body_categories # -# id :integer not null, primary key -# title :text not null -# category_tag :text not null -# description :text not null -# display_order :integer +# id :integer not null, primary key +# category_tag :text not null # require 'forwardable' diff --git a/app/models/public_body_category_link.rb b/app/models/public_body_category_link.rb index ba3ff1f95..8c3eb8060 100644 --- a/app/models/public_body_category_link.rb +++ b/app/models/public_body_category_link.rb @@ -1,10 +1,11 @@ # == Schema Information # -# Table name: public_body_category_link +# Table name: public_body_category_links # -# public_body_category_id :integer not null -# public_body_heading_id :integer not null -# category_display_order :integer +# public_body_category_id :integer not null +# public_body_heading_id :integer not null +# category_display_order :integer +# id :integer not null, primary key # class PublicBodyCategoryLink < ActiveRecord::Base diff --git a/app/models/public_body_heading.rb b/app/models/public_body_heading.rb index f1916d233..f394c37c6 100644 --- a/app/models/public_body_heading.rb +++ b/app/models/public_body_heading.rb @@ -2,8 +2,7 @@ # # Table name: public_body_headings # -# id :integer not null, primary key -# name :text not null +# id :integer not null, primary key # display_order :integer # diff --git a/app/models/user.rb b/app/models/user.rb index 1c6dc0eb0..c953e52f2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -264,11 +264,9 @@ class User < ActiveRecord::Base # Returns list of requests which the user hasn't described (and last # changed more than a day ago) def get_undescribed_requests - info_requests.find( - :all, - :conditions => [ 'awaiting_description = ? and ' + InfoRequest.last_event_time_clause + ' < ?', - true, Time.now() - 1.day - ] + info_requests.where( + "awaiting_description = ? and #{ InfoRequest.last_event_time_clause } < ?", + true, 1.day.ago ) end diff --git a/app/views/admin_censor_rule/_show.html.erb b/app/views/admin_censor_rule/_show.html.erb index 46904b3b9..4b37d0cc9 100644 --- a/app/views/admin_censor_rule/_show.html.erb +++ b/app/views/admin_censor_rule/_show.html.erb @@ -15,7 +15,7 @@ <td><%=h censor_rule.send(column) %></td> <% end %> <td> - <%= link_to "Edit", admin_rule_edit_path(censor_rule) %> + <%= link_to "Edit", edit_admin_censor_rule_path(censor_rule) %> </td> </tr> <% end %> @@ -25,7 +25,7 @@ <% end %> <% if defined? info_request %> - <%= link_to "New censor rule", new_admin_info_request_censor_rule_path(info_request), :class => "btn btn-info" %> + <%= link_to "New censor rule", new_admin_request_censor_rule_path(info_request), :class => "btn btn-info" %> <span class="label label-info">for this request only</span> <% end %> diff --git a/app/views/admin_censor_rule/edit.html.erb b/app/views/admin_censor_rule/edit.html.erb index 230446ed3..ff07987f2 100644 --- a/app/views/admin_censor_rule/edit.html.erb +++ b/app/views/admin_censor_rule/edit.html.erb @@ -2,15 +2,14 @@ <h1><%=@title%></h1> -<%= form_tag admin_rule_update_path(@censor_rule), :class => "form form-horizontal" do %> +<%= form_tag admin_censor_rule_path(@censor_rule), :method => 'put', :class => "form form-horizontal" do %> <%= render :partial => 'form', :locals => { :info_request => @censor_rule.info_request, :user => @censor_rule.user } %> <div class="form-actions"> <%= submit_tag 'Save', :accesskey => 's', :class => "btn btn-primary" %> </div> <% end %> -<%= form_tag admin_rule_destroy_path(@censor_rule), :class => "form form-horizontal" do %> - <%= hidden_field_tag(:censor_rule_id, @censor_rule.id) %> +<%= form_tag admin_censor_rule_path(@censor_rule), :method => 'delete', :class => "form form-horizontal" do %> <div class="form-actions"> Permanent! --> <%= submit_tag "Destroy rule", :class => "btn btn-primary" %> </div> diff --git a/app/views/admin_censor_rule/new.html.erb b/app/views/admin_censor_rule/new.html.erb index 26b3212be..ded31cff2 100644 --- a/app/views/admin_censor_rule/new.html.erb +++ b/app/views/admin_censor_rule/new.html.erb @@ -4,7 +4,6 @@ <%= form_for @censor_rule, :url => @form_url, :class => "form form-horizontal" do %> <%= render :partial => 'form', :locals => { :info_request => @info_request, :user => @censor_user } %> - <div class="form-actions"> <%= submit_tag "Create", :class => "btn btn-primary" %> </div> diff --git a/app/views/admin_request/edit_comment.html.erb b/app/views/admin_comment/edit.html.erb index 2cf49a4a8..e3cbd7472 100644 --- a/app/views/admin_request/edit_comment.html.erb +++ b/app/views/admin_comment/edit.html.erb @@ -2,12 +2,12 @@ <%= error_messages_for 'comment' %> -<%= form_tag admin_request_update_comment_path(@comment) do %> +<%= form_tag admin_comment_path(@comment), :method => 'put' do %> <p><label for="comment_body">Body of annotation</label><br/> <%= text_area 'comment', 'body', :rows => 10, :cols => 60 %></p> - <p><label for="comment_visible">Visible</label> + <p><label for="comment_visible">Visible</label> <%= select('comment', "visible", [["Yes – show comment",true],["No – hide comment",false]]) %> </p> @@ -16,7 +16,7 @@ <% end %> <p> -<%= link_to 'Show', admin_request_show_path(@comment.info_request) %> | -<%= link_to 'List all', admin_request_list_path %> +<%= link_to 'Show request', admin_request_path(@comment.info_request) %> | +<%= link_to 'List all requests', admin_requests_path %> </p> diff --git a/app/views/admin_general/_admin_navbar.html.erb b/app/views/admin_general/_admin_navbar.html.erb index 14fc06092..b6e01f049 100644 --- a/app/views/admin_general/_admin_navbar.html.erb +++ b/app/views/admin_general/_admin_navbar.html.erb @@ -9,11 +9,17 @@ <li><%= link_to 'Timeline', admin_timeline_path %></li> <li><%= link_to 'Stats', admin_stats_path %></li> <li><%= link_to 'Debug', admin_debug_path %></li> - <li><%= link_to 'Authorities', admin_body_list_path %></li> - <li><%= link_to 'Categories', admin_categories_path %></li> - <li><%= link_to 'Requests', admin_request_list_path %></li> - <li><%= link_to 'Users', admin_user_list_path %></li> - <li><%= link_to 'Tracks', admin_track_list_path %></li> + <li class="dropdown"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Authorities<span class="caret"></span></a> + <ul class="dropdown-menu" role="menu"> + <li><%= link_to 'Authorities', admin_bodies_path %></li> + <li><%= link_to 'Categories', admin_categories_path %></li> + </ul> + </li> + <li><%= link_to 'Requests', admin_requests_path %></li> + <li><%= link_to 'Users', admin_users_path %></li> + <li><%= link_to 'Tracks', admin_tracks_path %></li> + <li><%= link_to 'Holidays', admin_holidays_path %></li> <li><%= link_to 'Log out', signout_path %></li> </ul> </div> diff --git a/app/views/admin_general/index.html.erb b/app/views/admin_general/index.html.erb index ba9396ceb..ba0563bb6 100644 --- a/app/views/admin_general/index.html.erb +++ b/app/views/admin_general/index.html.erb @@ -37,9 +37,9 @@ <tr> <td> <% if message.get_body_for_quoting.strip.size == 0 %> - <%= link_to "(no body)", admin_request_show_raw_email_path(message.raw_email_id) %> + <%= link_to "(no body)", admin_raw_email_path(message.raw_email_id) %> <% else %> - <%= link_to excerpt(message.get_body_for_quoting, "", :radius => 60), admin_request_show_raw_email_path(message.raw_email_id) %> + <%= link_to excerpt(message.get_body_for_quoting, "", :radius => 60), admin_raw_email_path(message.raw_email_id) %> <% end %> </td> <td class="span2"> @@ -183,10 +183,10 @@ <div id="new-authorities" class="accordion-body collapse"> <% for @change_request in @new_body_requests %> <%= render :partial => 'change_request_summary'%> - <%= form_tag admin_change_request_update_path(@change_request), :class => "form form-horizontal" do %> + <%= form_tag admin_change_request_path(@change_request), :method => 'put', :class => "form form-horizontal" do %> <%= submit_tag 'Close', :class => "btn btn-danger" %> - <%= link_to("Close and respond", admin_change_request_edit_path(@change_request), :class => 'btn') %> - <%= link_to("Add authority", admin_body_new_path(:change_request_id => @change_request.id), :class => 'btn btn-primary') %> + <%= link_to("Close and respond", edit_admin_change_request_path(@change_request), :class => 'btn') %> + <%= link_to("Add authority", new_admin_body_path(:change_request_id => @change_request.id), :class => 'btn btn-primary') %> <% end %> <% end %> @@ -202,10 +202,10 @@ <div id="update-authorities" class="accordion-body collapse"> <% for @change_request in @body_update_requests %> <%= render :partial => 'change_request_summary' %> - <%= form_tag admin_change_request_update_path(@change_request), :class => "form form-horizontal" do %> + <%= form_tag admin_change_request_path(@change_request), :class => "form form-horizontal", :method => 'put' do %> <%= submit_tag 'Close', :class => "btn btn-danger" %> - <%= link_to("Close and respond", admin_change_request_edit_path(@change_request), :class => 'btn') %> - <%= link_to("Make update", admin_body_edit_path(@change_request.public_body, :change_request_id => @change_request.id), :class => 'btn btn-primary') %> + <%= link_to("Close and respond", edit_admin_change_request_path(@change_request), :class => 'btn') %> + <%= link_to("Make update", edit_admin_body_path(@change_request.public_body, :change_request_id => @change_request.id), :class => 'btn btn-primary') %> <% end %> <% end %> </div> diff --git a/app/views/admin_general/stats.html.erb b/app/views/admin_general/stats.html.erb index 27dc25ee0..03268cc14 100644 --- a/app/views/admin_general/stats.html.erb +++ b/app/views/admin_general/stats.html.erb @@ -53,8 +53,3 @@ </div> </div> </div> -<div class="row"> - <div class="span12"> - <h2>Web analytics</h2> - </div> -</div> diff --git a/app/views/admin_holiday_imports/new.html.erb b/app/views/admin_holiday_imports/new.html.erb new file mode 100644 index 000000000..047f321f9 --- /dev/null +++ b/app/views/admin_holiday_imports/new.html.erb @@ -0,0 +1,81 @@ +<% @title = "Create holidays from suggestions or iCal feed" %> +<h1><%= @title %></h1> + +<%= form_for( @holiday_import, :as => 'holiday_import', :url => '', :method => 'get', :html => { :class => 'form-horizontal form-inline' }) do |f| %> + <% if @holiday_import.holidays.empty? %> + <%= error_messages_for 'holiday_import', :header_message => 'There was a problem with these import settings' %> + <% end %> + <legend>Import settings</legend> + <div> + <div class="control-group"> + <label class="control-label">Choose the years to import holidays for</label> + <div class="controls"> + <label for="import_start_year" class="inline">Start year:</label> + <%= f.select :start_year, (Time.now.year)..(Time.now.year + 5) %> + <label for="import_end_year" class="inline">End year:</label> + <%= f.select :end_year, (Time.now.year)..(Time.now.year + 5) %> + </div> + </div> + + <div class="control-group"> + <label class="control-label">Import from built-in suggestions or iCal feed</label> + <div class="controls"> + <label class="radio inline"> + <%= f.radio_button :source, "suggestions" %>Built-in suggestions + </label> + <label class="radio inline"> + <%= f.radio_button :source, "feed" %>iCal feed + </label> + </div> + </div> + + <div class="control-group"> + <label class="control-label">iCal feed URL:</label> + <div class="controls"> + <%= f.text_field 'ical_feed_url' %> + </div> + </div> + + <div class="control-group"> + <input type="submit" value="Show holidays" class="btn btn-primary"> + </div> + + </div> +<% end %> + +<% if @holiday_import.populated %> + <h2>Holidays to import</h2> + + <table class="table table-striped table-condensed"> + <tbody> + <tr> + <td> + <% if @holiday_import.holidays.empty? %> + <% if @holiday_import.source == 'suggestions' %> + Sorry, we don't have any built-in suggestions for holiday days in <%= @holiday_import.suggestions_country_name %>. + <% else %> + Sorry, we couldn't find any holidays in that iCal feed. + <% end %> + <% else %> + <%= form_for( @holiday_import, :as => 'holiday_import', :url => admin_holiday_imports_path, :html => { :class => 'form-inline' } ) do |f| -%> + <%= error_messages_for 'holiday_import' %> + <legend> + <% if @holiday_import.source == 'suggestions' %> + Suggested holidays for <%= @holiday_import.suggestions_country_name %> (<%= @holiday_import.period %>) + <% else %> + Holidays from feed (<%= @holiday_import.period %>) + <% end %> + </legend> + <%= f.fields_for :holidays do |holiday_fields| %> + <div class="import-holiday-info"> + <%= render :partial => 'admin_holidays/form', :locals => {:f => holiday_fields, :context => :import, :holiday => holiday_fields.object } %> + </div> + <% end%> + <%= f.submit "Import", :class => 'btn btn-warning' %> + <% end %> + <% end %> + </td> + </tr> + </tbody> + </table> +<% end %> diff --git a/app/views/admin_holidays/_edit_form.html.erb b/app/views/admin_holidays/_edit_form.html.erb new file mode 100644 index 000000000..b750dbf4c --- /dev/null +++ b/app/views/admin_holidays/_edit_form.html.erb @@ -0,0 +1,14 @@ +<td> + <%= form_for(@holiday, :url => admin_holiday_path(@holiday), :html => { :class => 'form-inline edit-holiday-form'}) do |f| -%> + <%= render :partial => 'form', :locals => { :f => f, :holiday => @holiday, :context => :edit } %> + <% end %> + + <div class="holiday-destroy "> + <%= form_for @holiday, :url => admin_holiday_path(@holiday), :method => 'delete', :html => { :class => "form form-inline delete-holiday-form" } do |f| %> + <%= f.submit "Destroy", + :class => "btn btn-danger", + :confirm => 'Are you sure you want to destroy this public holiday?' %> + <% end %> + </div> + +</td> diff --git a/app/views/admin_holidays/_form.html.erb b/app/views/admin_holidays/_form.html.erb new file mode 100644 index 000000000..35370e5fc --- /dev/null +++ b/app/views/admin_holidays/_form.html.erb @@ -0,0 +1,22 @@ +<%= error_messages_for 'holiday' %> + +<div class="holiday-description"> + <% if holiday.new_record? %> + <%= f.text_field :description, :class => 'input', :placeholder => 'Enter description here' %> + <% else %> + <%= f.text_field :description, :class => 'input' %> + <% end %> +</div> + +<div class="holiday-day"> + <%= f.date_select :day, { :use_month_numbers => true }, { :class => "day_select" } %> +</div> +<div class="holiday-buttons"> + <% if context == :import %> + <%= f.submit "Remove", :class => 'btn remove-holiday' %> + <% else %> + <%= link_to("Cancel", admin_holidays_path, :class => 'btn') %> + <%= f.submit "Save", :class => 'btn btn-warning' %> +<% end %> +</div> + diff --git a/app/views/admin_holidays/_holiday.html.erb b/app/views/admin_holidays/_holiday.html.erb new file mode 100644 index 000000000..78818f411 --- /dev/null +++ b/app/views/admin_holidays/_holiday.html.erb @@ -0,0 +1,7 @@ +<td> + <div class="holiday-description"><%= holiday.description %></div> + <div class="holiday-day"><%= holiday.day %></div> + <div class="holiday-buttons"> + <%= link_to 'Edit', edit_admin_holiday_path(holiday), :class => "btn edit-button" %> + </div> +</td> diff --git a/app/views/admin_holidays/_new_form.html.erb b/app/views/admin_holidays/_new_form.html.erb new file mode 100644 index 000000000..aee73f426 --- /dev/null +++ b/app/views/admin_holidays/_new_form.html.erb @@ -0,0 +1,10 @@ +<table class="table table-striped table-condensed"> + <tbody> + <tr> + <td><%= form_for(@holiday, :url => admin_holidays_path, :html => { :class => 'form-inline new-holiday-form'}) do |f| -%> + <%= render :partial => 'form', :locals => { :f => f, :holiday => @holiday, :context => :new } %> + <% end %> + </td> + </tr> + </tbody> +</table> diff --git a/app/views/admin_holidays/edit.html.erb b/app/views/admin_holidays/edit.html.erb new file mode 100644 index 000000000..8f29c9a44 --- /dev/null +++ b/app/views/admin_holidays/edit.html.erb @@ -0,0 +1,9 @@ +<% @title = 'Edit public holiday' %> +<h1><%= @title %></h1> +<table class="table table-striped table-condensed"> + <tbody> + <tr> + <%= render :partial => 'edit_form' %> + </tr> + </tbody> +</table> diff --git a/app/views/admin_holidays/index.html.erb b/app/views/admin_holidays/index.html.erb new file mode 100644 index 000000000..d4ee8706b --- /dev/null +++ b/app/views/admin_holidays/index.html.erb @@ -0,0 +1,41 @@ +<% @title = 'Public Holidays' %> +<h1><%= @title %></h1> +<p> + + Alaveteli calculates the due dates of requests taking account of the + public holidays shown here. If you have set the + <code>WORKING_OR_CALENDAR_DAYS</code><a + href="http://alaveteli.org/docs/customising/config/#working_or_calendar_days" + target="_blank">(docs)</a> setting for Alaveteli to + <code>working</code>, the date when a response to a request is + officially overdue will be calculated in days that are not weekends + or public holidays. If you have set + <code>WORKING_OR_CALENDAR_DAYS</code> to <code>calendar</code>, the + date will be calculated in calendar days, but if the due date falls + on a public holiday or weekend day, then the due date is considered + to be the next week day that isn't a holiday. + +</p> +<div class="btn-toolbar"> + <div class="btn-group"> + <%= link_to 'New holiday', new_admin_holiday_path, :class => "btn btn-primary", :id => 'new-holiday-button' %> + </div> + <div class="btn-group"> + <%= link_to 'Create holidays from suggestions or iCal feed', new_admin_holiday_import_path, :class => "btn btn-warning" %> + </div> +</div> + +<div id="existing-holidays"> + <% @years.each do |year| %> + <h2><%= year %></h2> + <table class="table table-striped table-condensed"> + <tbody> + <% @holidays_by_year[year].sort_by(&:day).each do |holiday| %> + <%= content_tag_for(:tr, holiday, prefix=nil, 'data-target' => edit_admin_holiday_path(holiday)) do %> + <%= render :partial => 'holiday', :locals => { :holiday => holiday }%> + <% end %> + <% end %> + </tbody> + </table> + <% end %> +</div> diff --git a/app/views/admin_holidays/new.html.erb b/app/views/admin_holidays/new.html.erb new file mode 100644 index 000000000..792c32f52 --- /dev/null +++ b/app/views/admin_holidays/new.html.erb @@ -0,0 +1,4 @@ +<% @title = 'New public holiday' %> +<h1><%= @title %></h1> + +<%= render :partial => 'new_form' %> diff --git a/app/views/admin_request/_incoming_message_actions.html.erb b/app/views/admin_incoming_message/_actions.html.erb index 22effcce5..4780ad910 100644 --- a/app/views/admin_request/_incoming_message_actions.html.erb +++ b/app/views/admin_incoming_message/_actions.html.erb @@ -1,6 +1,6 @@ <fieldset class="form-horizontal"> <legend>Actions</legend> - <%= form_tag admin_incoming_redeliver_path, :class => "form form-inline" do %> + <%= form_tag redeliver_admin_incoming_message_path(incoming_message), :class => "form form-inline" do %> <div class="control-group"> <label class="control-label" for="url_title_<%= incoming_message.id %>">Redeliver message to one or more other requests</label> <div class="controls"> @@ -9,7 +9,6 @@ <% else %> <%= text_field_tag 'url_title', "", { :size => 20, :id => "url_title_#{incoming_message.id}" } %> <% end %> - <%= hidden_field_tag 'redeliver_incoming_message_id', incoming_message.id, :id => nil %> <%= submit_tag "Redeliver to another request", :class => "btn" %> <p class="help-block"><code>id</code> or <code>url_title</code>; you can supply more than one, separated by commas</p> </div> @@ -18,7 +17,10 @@ <div class="control-group"> <label class="control-label">Generate FOI officer upload URL</label> <div class="controls"> - <%= link_to 'Generate and take me there', admin_request_generate_upload_url_path(incoming_message.info_request, :incoming_message_id => incoming_message.id), :class => "btn" %> + <%= form_tag generate_upload_url_admin_request_path(incoming_message.info_request), :class => "form form-inline" do %> + <%= hidden_field_tag 'incoming_message_id', incoming_message.id, :id => nil %> + <%= submit_tag "Generate and take me there", :class => "btn" %> + <% end %> </div> </div> @@ -29,7 +31,7 @@ </div> </div> - <%= form_tag admin_incoming_destroy_path, :class => "form form-inline" do %> + <%= form_tag admin_incoming_message_path(incoming_message), :method => 'delete', :class => "form form-inline" do %> <div class="control-group"> <label class="control-label" for="destroy_message_<%= incoming_message.id %>">Destroy message</label> <div class="controls"> @@ -44,7 +46,7 @@ <div class="control-group"> <label class="control-label">Inspect email</label> <div class="controls"> - <%= link_to "View raw email", admin_request_show_raw_email_path(incoming_message.raw_email_id), :class => "btn" %> + <%= link_to "View raw email", admin_raw_email_path(incoming_message.raw_email_id), :class => "btn" %> </div> </div> <% end %> diff --git a/app/views/admin_incoming_message/edit.html.erb b/app/views/admin_incoming_message/edit.html.erb index 1088edcab..5e172af76 100644 --- a/app/views/admin_incoming_message/edit.html.erb +++ b/app/views/admin_incoming_message/edit.html.erb @@ -1,8 +1,8 @@ <%= render :partial => 'intro', :locals => {:incoming_message => @incoming_message } %> -<%= render :partial => 'admin_request/incoming_message_actions', :locals => { :incoming_message => @incoming_message } %> +<%= render :partial => 'actions', :locals => { :incoming_message => @incoming_message } %> <fieldset class="form-horizontal"> <legend>Prominence</legend> - <%= form_tag admin_incoming_update_path(@incoming_message), :class => "form form-inline" do %> + <%= form_tag admin_incoming_message_path(@incoming_message), :method => 'put', :class => "form form-inline" do %> <div class="control-group"> <label class="control-label" for="incoming_message_prominence"> Prominence</label> diff --git a/app/views/admin_outgoing_message/edit.html.erb b/app/views/admin_outgoing_message/edit.html.erb index d5f5f43bf..d84a5ba66 100644 --- a/app/views/admin_outgoing_message/edit.html.erb +++ b/app/views/admin_outgoing_message/edit.html.erb @@ -2,7 +2,7 @@ <%= error_messages_for 'outgoing_message' %> -<%= form_tag admin_outgoing_update_path(@outgoing_message) do %> +<%= form_tag admin_outgoing_message_path(@outgoing_message), :method => 'put' do %> <div class="control-group"> <label class="control-label" for="outgoing_message_prominence"> Prominence</label> <div class="controls"> @@ -36,13 +36,12 @@ <% end %> <p> -<%= link_to 'Show', admin_request_show_path(@outgoing_message.info_request) %> | -<%= link_to 'List all', admin_request_list_path %> +<%= link_to 'Show', admin_request_path(@outgoing_message.info_request) %> | +<%= link_to 'List all', admin_requests_path %> </p> -<%= form_tag admin_outgoing_destroy_path do %> +<%= form_tag admin_outgoing_message_path(@outgoing_message), :method => 'delete' do %> <div> - <%= hidden_field_tag 'outgoing_message_id', @outgoing_message.id %> <%= submit_tag "Destroy outgoing message", :class => "btn btn-danger", :confirm => "This is permanent! Are you sure?" %> </div> <% end %> diff --git a/app/views/admin_public_body/_one_list.html.erb b/app/views/admin_public_body/_one_list.html.erb index 8f1d719ec..8d891d16c 100644 --- a/app/views/admin_public_body/_one_list.html.erb +++ b/app/views/admin_public_body/_one_list.html.erb @@ -4,7 +4,7 @@ <div class="accordion-heading accordion-toggle row"> <span class="item-title span6"> <a href="#body_<%=public_body.id%>" data-toggle="collapse" data-parent="requests"><%= chevron_right %></a> - <%= link_to(public_body.name, admin_body_show_path(public_body), :title => "view full details")%> + <%= link_to(public_body.name, admin_body_path(public_body), :title => "view full details")%> </span> <span class="item-metadata span6"> <%= render :partial => 'tags', :locals => { :body => public_body} %> @@ -31,7 +31,7 @@ <% end %> </div> -<%= form_tag(admin_body_mass_tag_add_url, :method => "post", :class => "form form-inline" ) do %> +<%= form_tag(mass_tag_add_admin_bodies_path, :method => "post", :class => "form form-inline" ) do %> <p> <%= text_field_tag 'new_tag', params[:new_tag], { :size => 15, :id => "mass_add_tag_new_tag_" + table_name } %> <%= hidden_field_tag(:query, params[:query], { :id => "mass_add_tag_query_" + table_name } ) %> diff --git a/app/views/admin_public_body/edit.html.erb b/app/views/admin_public_body/edit.html.erb index 11b7eec22..dcafbd270 100644 --- a/app/views/admin_public_body/edit.html.erb +++ b/app/views/admin_public_body/edit.html.erb @@ -3,7 +3,7 @@ <div class="row"> <div class="span8"> <div id="public_body_form"> - <%= form_for @public_body, :url => admin_body_update_path(@public_body), :html => { :class => "form form-horizontal" } do |f| %> + <%= form_for @public_body, :url => admin_body_path(@public_body), :method => 'put', :html => { :class => "form form-horizontal" } do |f| %> <%= render :partial => 'form', :locals => {:f => f} %> <div class="form-actions"> <%= f.submit 'Save', :accesskey => 's', :class => "btn btn-success" %></p> @@ -13,15 +13,14 @@ <div class="row"> <div class="span8"> <div class="well"> - <%= link_to 'Show', admin_body_show_path(@public_body), :class => "btn" %> - <%= link_to 'List all', admin_body_list_path, :class => "btn" %> + <%= link_to 'Show', admin_bodies_path(@public_body), :class => "btn" %> + <%= link_to 'List all', admin_bodies_path, :class => "btn" %> </div> </div> </div> <% if @public_body.info_requests.empty? %> - <%= form_tag(admin_body_destroy_path(@public_body), :class => "form form-inline") do %> - <%= hidden_field_tag(:public_body_id, { :value => @public_body.id } ) %> + <%= form_tag(admin_body_path(@public_body), :class => "form form-inline", :method => 'delete') do %> <%= submit_tag _("Destroy {{name}}", :name => @public_body.name), :class => "btn btn-danger" %> (this is permanent!) <% end %> <% end %> diff --git a/app/views/admin_public_body/list.html.erb b/app/views/admin_public_body/index.html.erb index 3d7d9c4cd..861593bee 100644 --- a/app/views/admin_public_body/list.html.erb +++ b/app/views/admin_public_body/index.html.erb @@ -8,10 +8,10 @@ <div class="btn-toolbar"> <div class="btn-group"> - <%= link_to 'New public authority', admin_body_new_path, :class => "btn btn-primary" %> + <%= link_to 'New public authority', new_admin_body_path, :class => "btn btn-primary" %> </div> <div class="btn-group"> - <%= link_to 'Import from CSV file', admin_body_import_csv_path, :class => "btn btn-warning" %> + <%= link_to 'Import from CSV file', import_csv_admin_bodies_path, :class => "btn btn-warning" %> </div> </div> @@ -19,7 +19,7 @@ <%= text_field_tag 'query', params[:query], { :size => 30, :class => "input-large search-query" } %> <%= submit_tag "Search", :class => "btn" %> <% if !@query.nil? %> - <%= link_to 'Show all', admin_body_list_path, :class => "btn" %> + <%= link_to 'Show all', admin_bodies_path, :class => "btn" %> <% end %><br> (substring search in names and emails; exact match of tags) <% end %> diff --git a/app/views/admin_public_body/new.html.erb b/app/views/admin_public_body/new.html.erb index 24b27d7af..69b1f8503 100644 --- a/app/views/admin_public_body/new.html.erb +++ b/app/views/admin_public_body/new.html.erb @@ -4,7 +4,7 @@ <div class="row"> <div class="span8"> <div id="public_body_form"> - <%= form_for @public_body, :as => :public_body, :url => admin_body_create_path, :html => {:class => "form form-horizontal"} do |f| %> + <%= form_for @public_body, :as => :public_body, :url => admin_bodies_path, :html => {:class => "form form-horizontal"} do |f| %> <%= render :partial => 'form', :locals => {:f => f} %> @@ -14,7 +14,7 @@ <% end %> <div class="row"> <div class="span8 well"> - <%= link_to 'List all', admin_body_list_path, :class => "btn" %> + <%= link_to 'List all', admin_bodies_path, :class => "btn" %> </div> </div> </div> diff --git a/app/views/admin_public_body/show.html.erb b/app/views/admin_public_body/show.html.erb index f8161db26..40f341a22 100644 --- a/app/views/admin_public_body/show.html.erb +++ b/app/views/admin_public_body/show.html.erb @@ -45,7 +45,7 @@ </tr> </tbody> </table> -<%= link_to _("Edit"), admin_body_edit_path(@public_body), :class => "btn btn-primary" %> +<%= link_to _("Edit"), edit_admin_body_path(@public_body), :class => "btn btn-primary" %> <% unless @public_body.url_name.nil? %> <%=link_to _("Public page"), public_body_path(@public_body), :class => "btn" %> <% else %> diff --git a/app/views/admin_public_body_change_requests/edit.html.erb b/app/views/admin_public_body_change_requests/edit.html.erb index cc9c5b5d9..aaf457084 100644 --- a/app/views/admin_public_body_change_requests/edit.html.erb +++ b/app/views/admin_public_body_change_requests/edit.html.erb @@ -1,6 +1,6 @@ <h1><%=@title%></h1> -<%= form_tag admin_change_request_update_path(@change_request), :class => "form form-horizontal" do %> +<%= form_tag admin_change_request_path(@change_request), :method => 'put', :class => "form form-horizontal" do %> <%= render :partial => 'admin_public_body_change_requests/response'%> <div class="form-actions"> <%= submit_tag 'Close', :accesskey => 'c', :class => "btn btn-primary" %> diff --git a/app/views/admin_request/show_raw_email.html.erb b/app/views/admin_raw_email/show.html.erb index da22b6069..f88b00ef0 100644 --- a/app/views/admin_request/show_raw_email.html.erb +++ b/app/views/admin_raw_email/show.html.erb @@ -31,7 +31,7 @@ <tr> <td><strong>Authority:</strong></td> <td> - <%= link_to(info_request.public_body.name, admin_body_show_path(info_request.public_body)) %> + <%= link_to(info_request.public_body.name, admin_body_path(info_request.public_body)) %> </td> </tr> <tr> @@ -52,12 +52,12 @@ </p> <div> - <%= render :partial => 'incoming_message_actions', :locals => { :incoming_message => @raw_email.incoming_message } %> + <%= render :partial => 'admin_incoming_message/actions', :locals => { :incoming_message => @raw_email.incoming_message } %> </div> <h2>Raw email</h2> -<p><%= link_to "Download", admin_request_download_raw_email_path(@raw_email) %></p> +<p><%= link_to "Download", admin_raw_email_path(@raw_email, :format => 'txt') %></p> <pre><%=h(@raw_email.data).gsub(/\n/, '<br>').html_safe %></pre> diff --git a/app/views/admin_request/_some_annotations.html.erb b/app/views/admin_request/_some_annotations.html.erb index 7adcdc5bb..de470d26b 100644 --- a/app/views/admin_request/_some_annotations.html.erb +++ b/app/views/admin_request/_some_annotations.html.erb @@ -1,20 +1,20 @@ <% if comments.any? %> - <%= form_tag admin_user_modify_comment_visibility_path do %> + <%= form_tag modify_comment_visibility_admin_users_path do %> <div class="accordion" id="comments"> <% comments.each do |comment| %> <div class="accordion-group"> <div class="accordion-heading"> <%= check_box_tag 'comment_ids[]', comment.id %> - + <a href="#comment_<%= comment.id %>" data-toggle="collapse" data-parent="#comments"><%= chevron_right %></a> - - <%= link_to admin_request_edit_comment_path(comment) do %> + + <%= link_to edit_admin_comment_path(comment) do %> #<%= comment.id %> -- <%= h(comment.user.name) %> <%= admin_value(comment.created_at) %> <% end %> - + <%= comment_visibility(comment) %> <blockquote class="incoming-message"> @@ -58,4 +58,4 @@ <% end %> <% else %> <p>None yet.</p> -<% end %> +<% end %> diff --git a/app/views/admin_request/_some_requests.html.erb b/app/views/admin_request/_some_requests.html.erb index cff94956d..9a39cd0c0 100644 --- a/app/views/admin_request/_some_requests.html.erb +++ b/app/views/admin_request/_some_requests.html.erb @@ -4,10 +4,10 @@ <div class="accordion-heading accordion-toggle row"> <span class="item-title span6"> <a href="#request_<%=info_request.id%>" data-toggle="collapse" data-parent="requests"><%= chevron_right %></a> - <%= link_to(info_request.title, admin_request_show_path(info_request), :title => "view full details") %> + <%= link_to(info_request.title, admin_request_path(info_request), :title => "view full details") %> </span> <span class="item-metadata span6"> - <%= user_admin_link_for_request(info_request) %> <%= arrow_right %> <%= link_to("#{info_request.public_body.name}", admin_body_show_path(info_request.public_body)) %>, <%= time_ago_in_words(info_request.updated_at) %> ago + <%= user_admin_link_for_request(info_request) %> <%= arrow_right %> <%= link_to("#{info_request.public_body.name}", admin_body_path(info_request.public_body)) %>, <%= time_ago_in_words(info_request.updated_at) %> ago </span> </div> <div id="request_<%=info_request.id%>" class="item-detail accordion-body collapse row"> diff --git a/app/views/admin_request/edit.html.erb b/app/views/admin_request/edit.html.erb index 552b31bbb..db0b54655 100644 --- a/app/views/admin_request/edit.html.erb +++ b/app/views/admin_request/edit.html.erb @@ -2,7 +2,7 @@ <%= error_messages_for 'info_request' %> -<%= form_tag admin_request_update_path(@info_request) do %> +<%= form_tag admin_request_path(@info_request), :method => :put do %> <p><label for="info_request_title"><strong>Title</strong></label> (warning: editing this will break URLs right now)<br/> <%= text_field 'info_request', 'title', :size => 50 %></p> @@ -43,15 +43,15 @@ <% end %> <p> -<%= link_to 'Show', admin_request_show_path(@info_request) %> | -<%= link_to 'List all', admin_request_list_path %> +<%= link_to 'Show', admin_request_path(@info_request) %> | +<%= link_to 'List all', admin_requests_path %> </p> <hr> <div class="well"> -<%= form_tag admin_request_destroy_path(@info_request) do %> +<%= form_tag admin_request_path(@info_request), :method => :delete do %> <p> <strong>This is permanent and irreversible!</strong> <%= submit_tag 'Destroy request entirely', :class => 'btn btn-danger' %> <br>Use it mainly if someone posts private information, e.g. made a Data Protection request. It diff --git a/app/views/admin_request/list.html.erb b/app/views/admin_request/index.html.erb index 2bd3e3326..2bd3e3326 100644 --- a/app/views/admin_request/list.html.erb +++ b/app/views/admin_request/index.html.erb diff --git a/app/views/admin_request/show.html.erb b/app/views/admin_request/show.html.erb index 9c04badeb..dfb911ea7 100644 --- a/app/views/admin_request/show.html.erb +++ b/app/views/admin_request/show.html.erb @@ -2,8 +2,7 @@ <h1><%=@title%></h1> -<%= form_tag admin_request_move_request_path, { :class => "form form-horizontal" } do %> - <%= hidden_field_tag 'info_request_id', @info_request.id %> +<%= form_tag move_admin_request_path(@info_request), { :class => "form form-horizontal" } do %> <div class="accordion" id="info_request"> <div class="accordion-group"> <div class="accordion-heading"> @@ -16,7 +15,7 @@ <thead> <tr> <td colspan="2"> - <%= link_to 'Edit metadata', admin_request_edit_path(@info_request), :class => "btn" %> + <%= link_to 'Edit metadata', edit_admin_request_path(@info_request), :class => "btn" %> </td> </tr> </thead> @@ -101,7 +100,7 @@ <tfoot> <tr> <td colspan="2"> - <%= link_to 'Edit metadata', admin_request_edit_path(@info_request), :class => "btn" %> + <%= link_to 'Edit metadata', edit_admin_request_path(@info_request), :class => "btn" %> </td> </tr> </tfoot> @@ -117,11 +116,13 @@ FOI officer upload URL </label> <div class="controls"> - <%= link_to 'Generate URL', admin_request_generate_upload_url_path(@info_request), :class => "btn" %> + <%= form_tag generate_upload_url_admin_request_path(@info_request), :class => "form form-inline" do %> + <%= submit_tag "Generate URL", :class => "btn" %> + <% end %> <p class="help-block">(see also option to generate URLs for individual incoming messages below)</p> </div> </div> - <%= form_tag admin_request_hide_path(@info_request), :class => "form form-inline", :id => "hide_request_form", 'data-info-request-id' => @info_request.id.to_s do %> + <%= form_tag hide_admin_request_path(@info_request), :class => "form form-inline", :id => "hide_request_form", 'data-info-request-id' => @info_request.id.to_s do %> <div class="control-group"> <% if @info_request.is_external? %> <label class="control-label">Hide the request:</label> @@ -191,8 +192,7 @@ <tr> <td> <% if info_request_event.described_state != 'waiting_clarification' and info_request_event.event_type == 'response' %> - <%= form_tag admin_request_clarification_path, :class => "form form-inline admin-table-form admin-inline-form" do %> - <%= hidden_field_tag 'info_request_event_id', info_request_event.id, :id => nil %> + <%= form_tag admin_info_request_event_path(info_request_event), :method => 'put', :class => "form form-inline admin-table-form admin-inline-form" do %> <%= submit_tag "Was clarification request", :class => "btn btn-mini btn-primary" %> <% end %> <% end %> @@ -233,7 +233,7 @@ <div class="accordion-group"> <div class="accordion-heading"> <a href="#outgoing_<%=outgoing_message.id%>" data-toggle="collapse" data-parent="#outgoing_messages"><%= chevron_right %></a> - <%= link_to admin_outgoing_edit_path(outgoing_message) do %> + <%= link_to edit_admin_outgoing_message_path(outgoing_message) do %> #<%= outgoing_message.id %> -- <%= outgoing_message.status.humanize %> <%= outgoing_message.message_type.humanize %> <% end %> <blockquote> @@ -245,8 +245,7 @@ <tbody> <tr> <td colspan="2"> - <%= form_tag admin_request_resend_path, :class => "admin-table-form" do %> - <%= hidden_field_tag 'outgoing_message_id', outgoing_message.id %> + <%= form_tag resend_admin_outgoing_message_path(outgoing_message), :class => "admin-table-form" do %> <%= submit_tag "Resend", :class => "btn" %> <% end %> </td> @@ -279,7 +278,7 @@ <div class="accordion-group"> <div class="accordion-heading"> <a href="#incoming_<%=incoming_message.id%>" data-toggle="collapse" data-parent="#incoming_messages"><%= chevron_right %></a> - <%= link_to admin_incoming_edit_path(incoming_message) do %> + <%= link_to edit_admin_incoming_message_path(incoming_message) do %> <%=incoming_message.id%> -- <%= h(incoming_message.mail_from) %> @@ -296,7 +295,7 @@ <thead> <tr> <td colspan="2" class="well"> - <%= render :partial => 'incoming_message_actions', :locals => { :incoming_message => incoming_message } %> + <%= render :partial => 'admin_incoming_message/actions', :locals => { :incoming_message => incoming_message } %> </td> </tr> </thead> diff --git a/app/views/admin_track/_some_tracks.html.erb b/app/views/admin_track/_some_tracks.html.erb index e9facfb5d..c17350e4b 100644 --- a/app/views/admin_track/_some_tracks.html.erb +++ b/app/views/admin_track/_some_tracks.html.erb @@ -32,9 +32,8 @@ <% if include_destroy %> <tr> <td colspan="2"> - <%= form_tag admin_user_destroy_track_path, :class => "form form-inline admin-table-form" do %> + <%= form_tag admin_track_path(track_thing), :method => 'delete', :class => "form form-inline admin-table-form" do %> <div> - <%= hidden_field_tag 'track_id', track_thing.id %> <%= submit_tag "Destroy track", :class => "btn btn-warning" %> </div> <% end %> diff --git a/app/views/admin_track/list.html.erb b/app/views/admin_track/index.html.erb index 5e967a926..2b1dc5737 100644 --- a/app/views/admin_track/list.html.erb +++ b/app/views/admin_track/index.html.erb @@ -14,7 +14,7 @@ <h2>Current top tracks:</h2> <ol> <% for row in @popular %> - <li><%= link_to row['title'], admin_request_show_path(row['info_request_id']) %> (<%= row['count'] %> people following)</li> + <li><%= link_to row['title'], admin_request_path(row['info_request_id']) %> (<%= row['count'] %> people following)</li> <% end %> </ol> diff --git a/app/views/admin_user/_form.html.erb b/app/views/admin_user/_form.html.erb index f1edc0927..01273a657 100644 --- a/app/views/admin_user/_form.html.erb +++ b/app/views/admin_user/_form.html.erb @@ -41,7 +41,7 @@ user from filing new requests, making annotations or messaging other users; the text is shown in public on the user's page and when they try to do a forbidden action; write in the second person (you); see - <%= link_to 'banned users', admin_user_list_banned_path %> for examples</small> + <%= link_to 'banned users', banned_admin_users_path %> for examples</small> </div> </div> </div> diff --git a/app/views/admin_user/_user_table.html.erb b/app/views/admin_user/_user_table.html.erb index 57066bf3f..4fd90d5c6 100644 --- a/app/views/admin_user/_user_table.html.erb +++ b/app/views/admin_user/_user_table.html.erb @@ -7,7 +7,7 @@ <% if user.admin_level == "super" %> <span class="label">superuser</span> <% end %> - <%= link_to("#{h(user.name)}", admin_user_show_path(user))%> + <%= link_to("#{h(user.name)}", admin_user_path(user))%> <%= link_to("(#{h(user.email)})", "mailto:#{h(user.email)}")%> </span> <span class="item-metadata"> diff --git a/app/views/admin_user/list_banned.html.erb b/app/views/admin_user/banned.html.erb index e535415e6..ee17d9899 100644 --- a/app/views/admin_user/list_banned.html.erb +++ b/app/views/admin_user/banned.html.erb @@ -2,7 +2,7 @@ <h1><%=@title%></h1> -<p><%= link_to 'List all', admin_user_list_path %></p> +<p><%= link_to 'List all', admin_users_path %></p> <%= render :partial => 'user_table', :locals => { :users => @banned_users, :banned_column => true } %> diff --git a/app/views/admin_user/edit.html.erb b/app/views/admin_user/edit.html.erb index e641a13d6..ed1fb6e08 100644 --- a/app/views/admin_user/edit.html.erb +++ b/app/views/admin_user/edit.html.erb @@ -1,11 +1,11 @@ <h1><%=@title%></h1> -<%= form_tag admin_user_update_path(@admin_user), :class => "form form-horizontal" do %> +<%= form_tag admin_user_path(@admin_user), :method => 'put', :class => "form form-horizontal" do %> <%= render :partial => 'form' %> <div class="form-actions"> <%= submit_tag 'Save', :accesskey => 's', :class => "btn btn-primary" %> </div> <% end %> -<%= link_to 'Show', admin_user_show_path(@admin_user), :class => "btn" %> -<%= link_to 'List all', admin_user_list_path, :class => "btn" %> +<%= link_to 'Show', admin_user_path(@admin_user), :class => "btn" %> +<%= link_to 'List all', admin_users_path, :class => "btn" %> diff --git a/app/views/admin_user/list.html.erb b/app/views/admin_user/index.html.erb index b1238f87a..cfb34fd7b 100644 --- a/app/views/admin_user/list.html.erb +++ b/app/views/admin_user/index.html.erb @@ -5,7 +5,7 @@ <%= form_tag({}, :method => "get", :class => "form form-search") do %> <%= text_field_tag 'query', params[:query], { :size => 30, :class => "input-large search-query"} %> <%= submit_tag "Search", :class => "btn" %> (substring search, names and emails) - <%= link_to 'Banned users', admin_user_list_banned_path, :class => "btn btn-info" %> + <%= link_to 'Banned users', banned_admin_users_path, :class => "btn btn-info" %> <% end %> <%= render :partial => 'user_table', :locals => { :users => @admin_users, :banned_column => false } %> diff --git a/app/views/admin_user/show.html.erb b/app/views/admin_user/show.html.erb index 6d12aeff5..1e7d885c5 100644 --- a/app/views/admin_user/show.html.erb +++ b/app/views/admin_user/show.html.erb @@ -4,7 +4,7 @@ <% if @admin_user.profile_photo %> <div class="user_photo_on_admin"> - <%= form_tag admin_clear_profile_photo_path(@admin_user), :multipart => true, :class => "form" do %> + <%= form_tag clear_profile_photo_admin_user_path(@admin_user), :multipart => true, :class => "form" do %> <img src="<%= get_profile_photo_url(:url_name => @admin_user.url_name) %>"> <br> <%= submit_tag "Clear photo", :class => "btn btn-info" %> @@ -32,13 +32,13 @@ <%=link_to @admin_user.email, "mailto:#{h @admin_user.email}"%> <% elsif column_name == 'email_bounce_message' %> <% unless @admin_user.email_bounce_message.empty? %> - <%= link_to _("See bounce message"), admin_user_show_bounce_path(@admin_user.id) %> + <%= link_to _("See bounce message"), show_bounce_message_admin_user_path(@admin_user) %> <% end %> <% else %> <%=h admin_value(value)%> <% end %> <% if column_name == 'email_bounced_at' && !@admin_user.email_bounced_at.nil? %> - <%= form_tag admin_user_clear_bounce_path(@admin_user), :class => "form form-inline" do %> + <%= form_tag clear_bounce_admin_user_path(@admin_user), :class => "form form-inline" do %> <input type="submit" name="action" value="Clear bounce" class="btn btn-info"> <% end %> <% end %> @@ -48,11 +48,13 @@ </tbody> </table> - -<%= link_to 'Edit', admin_user_edit_path(@admin_user), :class => "btn btn-primary" %> +<div class="btn-toolbar"> +<%= link_to 'Edit', edit_admin_user_path(@admin_user), :class => "btn btn-primary" %> <%= link_to 'Public page', user_path(@admin_user), :class => "btn" %> -<%= link_to "Log in as #{@admin_user.name} (also confirms their email)", admin_user_login_as_path(@admin_user), :class => "btn btn-info" %> - +</div> +<%= form_tag login_as_admin_user_path(@admin_user), :class => "form form-horizontal" do %> + <%= submit_tag "Log in as #{@admin_user.name} (also confirms their email)", :class => "btn btn-info" %> +<% end %> <hr> <h2>Track things</h2> diff --git a/app/views/comment/_single_comment.html.erb b/app/views/comment/_single_comment.html.erb index cdcb3b85f..07017dabf 100644 --- a/app/views/comment/_single_comment.html.erb +++ b/app/views/comment/_single_comment.html.erb @@ -17,7 +17,7 @@ <p class="event_actions"> <% if !comment.id.nil? %> <% if !@user.nil? && @user.admin_page_links? %> - <%= link_to "Admin", admin_request_edit_comment_path(comment) %> | + <%= link_to "Admin", edit_admin_comment_path(comment) %> | <% end %> <%= link_to "Link to this", comment_path(comment), :class => "link_to_this" %> <!-- | <%= link_to _('Report abuse'), comment_path(comment) %> --> diff --git a/app/views/comment/new.html.erb b/app/views/comment/new.html.erb index 578732cdb..48fe055ac 100644 --- a/app/views/comment/new.html.erb +++ b/app/views/comment/new.html.erb @@ -1,4 +1,5 @@ -<% @title = "Make an annotation on '" + h(@info_request.title) + "'" %> +<% @title = _("Add an annotation to “{{request_title}}”", + :request_title => h(@info_request.title)) %> <% if @existing_comment %> <div class="errorExplanation" id="errorExplanation"><ul> @@ -11,8 +12,10 @@ <%= foi_error_messages_for :comment %> -<h1><%= _('Add an annotation')%></h1> -<h2>to “<%=request_link(@info_request)%>”</h2> +<h1> + <%= _("Add an annotation to “{{request_title}}”", + :request_title => request_link(@info_request)) %> +</h1> <p> <%= _('Annotations are so anyone, including you, can help the requester with their request. For example:')%> diff --git a/app/views/contact_mailer/add_public_body.text.erb b/app/views/contact_mailer/add_public_body.text.erb index 5baa1fa1a..306a70432 100644 --- a/app/views/contact_mailer/add_public_body.text.erb +++ b/app/views/contact_mailer/add_public_body.text.erb @@ -13,7 +13,7 @@ <%= @change_request.notes %> <%= _('Add the authority:') %> -<%= admin_body_new_url(:change_request_id => @change_request.id, :only_path => false ) %> +<%= new_admin_body_url(:change_request_id => @change_request.id, :only_path => false ) %> <%= _('Close the request and respond:') %> -<%= admin_change_request_edit_url(:id => @change_request.id, :only_path => false ) %> +<%= edit_admin_change_request_url(@change_request, :only_path => false ) %> diff --git a/app/views/contact_mailer/update_public_body_email.text.erb b/app/views/contact_mailer/update_public_body_email.text.erb index 7d5a3dae0..e849463d0 100644 --- a/app/views/contact_mailer/update_public_body_email.text.erb +++ b/app/views/contact_mailer/update_public_body_email.text.erb @@ -10,7 +10,7 @@ <%= @change_request.notes %> <%= _('Update the address:') %> -<%= admin_body_edit_path(@change_request.public_body, :change_request_id => @change_request.id, :only_path => false) %> +<%= edit_admin_body_path(@change_request.public_body, :change_request_id => @change_request.id, :only_path => false) %> <%= _('Close the request and respond:') %> -<%= admin_change_request_edit_url(:id => @change_request.id, :only_path => false ) %> +<%= edit_admin_change_request_url(@change_request, :only_path => false ) %> diff --git a/app/views/general/exception_caught.html.erb b/app/views/general/exception_caught.html.erb index 8d78e2e92..21223dc1e 100644 --- a/app/views/general/exception_caught.html.erb +++ b/app/views/general/exception_caught.html.erb @@ -12,13 +12,10 @@ <%= submit_tag _("Search") %> <% end %> </li> - </ul> + </ul> <% else %> <h1><%= _("Sorry, there was a problem processing this page") %></h1> <p><%= _('You have found a bug. Please <a href="{{contact_url}}">contact us</a> to tell us about the problem', :contact_url => help_contact_path) %></p> <% end %> - <h2><%= _('Technical details') %></h2> - <p><strong><%= h(@exception_class ? @exception_class : _("Unknown")) %></strong></p> - <p><strong><%= h(@exception_message) %></strong></p> </div> diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index 50abc59c3..56e6b3362 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -20,10 +20,14 @@ <% end %> <%= javascript_include_tag "application" %> + <% if @profile_photo_javascript %> <%= javascript_include_tag "profile-photos" %> <%= stylesheet_link_tag "jquery.Jcrop.css" %> <% end %> + <% if is_admin? %> + <%= javascript_include_tag "bootstrap-dropdown" %> + <% end %> <% if @feed_autodetect %> <% for feed in @feed_autodetect %> diff --git a/app/views/public_body/show.html.erb b/app/views/public_body/show.html.erb index 011aea535..5ac637a78 100644 --- a/app/views/public_body/show.html.erb +++ b/app/views/public_body/show.html.erb @@ -23,7 +23,7 @@ <%=@public_body.type_of_authority(true)%><% if not @public_body.short_name.empty? %>, <%= _('also called {{public_body_short_name}}', :public_body_short_name => h(@public_body.short_name))%><% end %> <% if !@user.nil? && @user.admin_page_links? %> - (<%= link_to _("admin"), admin_body_show_path(@public_body) %>) + (<%= link_to _("admin"), admin_body_path(@public_body) %>) <% end %> </p> diff --git a/app/views/request/_act.html.erb b/app/views/request/_act.html.erb index 1199cb4a2..878cdf4ff 100644 --- a/app/views/request/_act.html.erb +++ b/app/views/request/_act.html.erb @@ -1,7 +1,14 @@ <h2><%= _("Act on what you've learnt") %></h2> <div class="act_link"> - <% tweet_link = "https://twitter.com/share?" + {:url => request.url, :via => AlaveteliConfiguration::twitter_username, :text => "'#{@info_request.title}'", :related => _('alaveteli_foi:The software that runs {{site_name}}', :site_name => site_name)}.to_query %> + <% tweet_link = "https://twitter.com/share?" << { + :url => request.url, + :via => AlaveteliConfiguration.twitter_username, + :text => "'#{ @info_request.title }'", + :related => _('alaveteli_foi:The software that runs {{site_name}}', :site_name => site_name) + }.to_query + %> + <% link_to tweet_link do %> <%= image_tag "twitter-16.png", :alt => "twitter icon" %> <% end %> diff --git a/app/views/request/_incoming_correspondence.html.erb b/app/views/request/_incoming_correspondence.html.erb index f39d650d8..70bd25c7f 100644 --- a/app/views/request/_incoming_correspondence.html.erb +++ b/app/views/request/_incoming_correspondence.html.erb @@ -17,7 +17,7 @@ <p class="event_actions"> <% if !@user.nil? && @user.admin_page_links? %> - <%= link_to "Admin", admin_incoming_edit_path(incoming_message.id) %> | + <%= link_to "Admin", edit_admin_incoming_message_path(incoming_message.id) %> | <% end %> <%= link_to _("Link to this"), incoming_message_path(incoming_message), :class => "link_to_this" %> </p> diff --git a/app/views/request/_search_ahead.html.erb b/app/views/request/_search_ahead.html.erb index 1e65a5458..4fbe06ebc 100644 --- a/app/views/request/_search_ahead.html.erb +++ b/app/views/request/_search_ahead.html.erb @@ -1,14 +1,20 @@ -<div id="request_search_ahead_results"> - <% if !@xapian_requests.nil? %> - <% if @xapian_requests.results.size > 0 %> +<% unless @xapian_requests.nil? %> + <div id="request_search_ahead_results"> + <% if @xapian_requests.results.any? %> + <span class="close-button">X</span> <h3><%= _("Possibly related requests:") %></h3> - <% end %> - <% for result in @xapian_requests.results %> - <%= render :partial => 'request/request_listing_short_via_event', :locals => { :event => result[:model], :info_request => result[:model].info_request } %> - <% end %> - <p> - <a id="body-site-search-link"><%= _("Or search in their website for this information.") %></a> - </p> - <% end %> -</div> + <% @xapian_requests.results.each do |result| %> + <%= render :partial => 'request/request_listing_short_via_event', + :locals => { :event => result[:model], + :info_request => result[:model].info_request } %> + <% end %> + + <p> + <a id="body-site-search-link"> + <%= _("Search in their website for this information →") %> + </a> + </p> + <% end %> + </div> +<% end %> diff --git a/app/views/request/details.html.erb b/app/views/request/details.html.erb index 3cb2f5afe..194820da6 100644 --- a/app/views/request/details.html.erb +++ b/app/views/request/details.html.erb @@ -1,5 +1,10 @@ -<% @title = _("Details of request '") + h(@info_request.title) + "'" %> -<h1><%= _("Details of request '") + request_link(@info_request) + "'" %></h1> +<% @title = _("Details of request “{{request_title}}”", + :request_title => h(@info_request.title)) %> + +<h1> + <%= _("Details of request “{{request_title}}”", + :request_title => request_link(@info_request)) %> +</h1> <h2><%= _('Event history') %></h2> diff --git a/app/views/request/followup_preview.html.erb b/app/views/request/followup_preview.html.erb index 55afc0245..83978a2f5 100644 --- a/app/views/request/followup_preview.html.erb +++ b/app/views/request/followup_preview.html.erb @@ -3,7 +3,7 @@ <div id="followup"> <%= form_for(@outgoing_message, :html => { :id => 'preview_form' }, :url => (@incoming_message.nil? ? show_response_no_followup_url(:id => @info_request.id) : show_response_url(:id => @info_request.id, :incoming_message_id => @incoming_message.id)) + "#followup" ) do |o| %> - + <% if @internal_review %> <h1><%= _('Now preview your message asking for an internal review') %></h1> <% else %> @@ -20,7 +20,7 @@ <div class="correspondence" id="outgoing-0"> <p class="preview_subject"> <strong><%= _('To:') %></strong> <%=h OutgoingMailer.name_for_followup(@info_request, @incoming_message) %> - <br><strong><%= _('Subject:') %></strong> <%=h OutgoingMailer.subject_for_followup(@info_request, @outgoing_message) %> + <br><strong><%= _('Subject:') %></strong> <%= OutgoingMailer.subject_for_followup(@info_request, @outgoing_message, :html => true) %> </p> <div class="correspondence_text"> diff --git a/app/views/request/new.html.erb b/app/views/request/new.html.erb index 7f1332464..51224129e 100644 --- a/app/views/request/new.html.erb +++ b/app/views/request/new.html.erb @@ -1,19 +1,33 @@ <% unless @batch %> <script type="text/javascript"> $(document).ready(function(){ - // Avoid triggering too often (on each keystroke) by using the debounce jQuery plugin: + // Avoid triggering too often (on each keystroke) by using the + // debounce jQuery plugin: // http://benalman.com/projects/jquery-throttle-debounce-plugin/ $("#typeahead_search").keypress($.debounce( 300, function() { - $("#typeahead_response").load("<%=search_ahead_url%>?q="+encodeURI(this.value), function() { - // When following links in typeahead results, open new tab/window - $("#typeahead_response a").attr("target","_blank"); - - // Update the public body site search link - $("#body-site-search-link").attr("href", "http://www.google.com/#q="+encodeURI($("#typeahead_search").val())+ - "+site:<%= @info_request.public_body.calculated_home_page %>"); + if ( $('#request_search_ahead_results').text().trim().length > 0) { + $('#typeahead_response').slideUp('fast'); + } + + $("#typeahead_response").load("<%= search_ahead_url %>?q="+encodeURI(this.value)+ + "&requested_from=<%= @info_request.public_body.url_name %>"+ + "&per_page=3", function() { + + if ( $('#request_search_ahead_results').text().trim().length > 0) { + $('#typeahead_response').hide().slideDown('fast'); + + // When following links in typeahead results, open new + // tab/window + $("#typeahead_response a").attr("target","_blank"); + + // Update the public body site search link + $("#body-site-search-link").attr("href", "http://www.google.com/#q="+encodeURI($("#typeahead_search").val())+ + "+site:<%= @info_request.public_body.calculated_home_page %>"); + + $('.close-button').click(function() { $(this).parent().hide() }); + } }); })); - }); </script> <% end %> diff --git a/app/views/request/preview.html.erb b/app/views/request/preview.html.erb index 0265d0328..ddd5ab30c 100644 --- a/app/views/request/preview.html.erb +++ b/app/views/request/preview.html.erb @@ -23,7 +23,7 @@ <% else %> <%=h(@info_request.public_body.name)%> <% end %> - <br><strong><%= _('Subject:') %></strong> <%=h @info_request.email_subject_request %> + <br><strong><%= _('Subject:') %></strong> <%= @info_request.email_subject_request %> </p> <div class="correspondence_text"> diff --git a/app/views/request/show.html.erb b/app/views/request/show.html.erb index 153b0b861..1383030cd 100644 --- a/app/views/request/show.html.erb +++ b/app/views/request/show.html.erb @@ -37,9 +37,9 @@ :user => request_user_link(@info_request, _('An anonymous user')), :law_used_full => h(@info_request.law_used_full), :user_admin_link => user_admin_link_for_request(@info_request, _('external'), _('admin')), - :request_admin_url => admin_request_show_url(@info_request), + :request_admin_url => admin_request_url(@info_request), :public_body_link => public_body_link(@info_request.public_body), - :public_body_admin_url => admin_body_show_url(@info_request.public_body)) %> + :public_body_admin_url => admin_body_url(@info_request.public_body)) %> <% else %> <%= _('{{user}} made this {{law_used_full}} request', :user=>request_user_link(@info_request, _('An anonymous user')), diff --git a/app/views/request/show_response.html.erb b/app/views/request/show_response.html.erb index ace86cf4c..7571f147b 100644 --- a/app/views/request/show_response.html.erb +++ b/app/views/request/show_response.html.erb @@ -35,7 +35,7 @@ </dt> <dd> <%= _('To do that please send a private email to ') %><%=h(@postal_email_name)%> - <<%=link_to h(@postal_email), "mailto:" + @postal_email%>> + <<%=mail_to h(@postal_email) %>> <%= _('containing your postal address, and asking them to reply to this request. Or you could phone them.') %> diff --git a/app/views/user/_signin.html.erb b/app/views/user/_signin.html.erb index afc55d249..e86791aaf 100644 --- a/app/views/user/_signin.html.erb +++ b/app/views/user/_signin.html.erb @@ -14,11 +14,11 @@ <p> <label class="form_label" for="user_signin_password"><%= _('Password:')%></label> - <%= password_field 'user_signin', 'password', { :size => 15, :tabindex => 20 } %> + <%= password_field 'user_signin', 'password', { :size => 15, :tabindex => 20, :autocomplete => 'off' } %> </p> <p class="form_note"> - <%= link_to _('Forgotten your password?'), signchangepassword_path + "?pretoken=" + h(params[:token]), :tabindex => 30 %> + <%= link_to _('Forgotten your password?'), signchangepassword_path(:pretoken => h(params[:token])), :tabindex => 30 %> </p> <p class="form_checkbox"> diff --git a/app/views/user/show.html.erb b/app/views/user/show.html.erb index b23f74326..51882b8ef 100644 --- a/app/views/user/show.html.erb +++ b/app/views/user/show.html.erb @@ -66,7 +66,7 @@ <p class="subtitle"> <%= _('Joined {{site_name}} in', :site_name=>site_name) %> <%= @display_user.created_at.year %> <% if !@user.nil? && @user.admin_page_links? %> - (<%= link_to "admin", admin_user_show_path(@display_user) %>) + (<%= link_to "admin", admin_user_path(@display_user) %>) <% end %> </p> diff --git a/app/views/user/signchangeemail.html.erb b/app/views/user/signchangeemail.html.erb index 7308179f4..a99bcb785 100644 --- a/app/views/user/signchangeemail.html.erb +++ b/app/views/user/signchangeemail.html.erb @@ -23,7 +23,7 @@ <p> <label class="form_label" for="signchangeemail_password"> <%= _('Your password:')%></label> - <%= password_field 'signchangeemail', 'password', { :size => 15 } %> + <%= password_field 'signchangeemail', 'password', { :size => 15, :autocomplete => 'off' } %> </p> <p class="form_note"> diff --git a/app/views/user/signchangepassword.html.erb b/app/views/user/signchangepassword.html.erb index 51bcb466d..60f5d2c62 100644 --- a/app/views/user/signchangepassword.html.erb +++ b/app/views/user/signchangepassword.html.erb @@ -13,12 +13,12 @@ <p> <label class="form_label" for="user_password"><%= _('New password:')%></label> - <%= password_field 'user', 'password', { :size => 15 } %> + <%= password_field 'user', 'password', { :size => 15, :autocomplete => 'off' } %> </p> <p> <label class="form_label" for="user_password_confirmation"><%= _('New password: (again)')%></label> - <%= password_field 'user', 'password_confirmation', { :size => 15 } %> + <%= password_field 'user', 'password_confirmation', { :size => 15, :autocomplete => 'off' } %> </p> <div class="form_button"> diff --git a/config/brakeman.ignore b/config/brakeman.ignore new file mode 100644 index 000000000..391013a5a --- /dev/null +++ b/config/brakeman.ignore @@ -0,0 +1,63 @@ +{ + "ignored_warnings": [ + { + "location": { + "type": "method", + "method": "list_all_csv", + "class": "PublicBodyController" + }, + "file": "app/controllers/public_body_controller.rb", + "warning_code": 16, + "render_path": null, + "link": "http://brakemanscanner.org/docs/warning_types/file_access/", + "warning_type": "File Access", + "code": "File.open(Tempfile.new(\"all-authorities.csv\", File.join(InfoRequest.download_zip_dir, \"download\")).path, \"w\")", + "line": 211, + "confidence": "Weak", + "user_input": "InfoRequest.download_zip_dir", + "message": "Model attribute used in file name", + "fingerprint": "00ce9cdd1d2c3f220bae94cb854393b5072ee1da064ca7a3af693fe2867d51c8", + "note": "InfoRequest.download_zip_dir does not contain user input" + }, + { + "location": { + "type": "method", + "method": "list_all_csv", + "class": "PublicBodyController" + }, + "file": "app/controllers/public_body_controller.rb", + "warning_code": 16, + "render_path": null, + "link": "http://brakemanscanner.org/docs/warning_types/file_access/", + "warning_type": "File Access", + "code": "File.rename(Tempfile.new(\"all-authorities.csv\", File.join(InfoRequest.download_zip_dir, \"download\")).path, File.join(File.join(InfoRequest.download_zip_dir, \"download\"), \"all-authorities.csv\"))", + "line": 213, + "confidence": "Weak", + "user_input": "InfoRequest.download_zip_dir", + "message": "Model attribute used in file name", + "fingerprint": "6078628aa47451d597e211629d80dcea0fdc7600dc066cabf2c0a4b9e07a75cc", + "note": "InfoRequest.download_zip_dir does not contain user input" + }, + { + "location": { + "type": "method", + "method": "list_all_csv", + "class": "PublicBodyController" + }, + "file": "app/controllers/public_body_controller.rb", + "warning_code": 16, + "render_path": null, + "link": "http://brakemanscanner.org/docs/warning_types/file_access/", + "warning_type": "File Access", + "code": "FileUtils.mkdir_p(File.join(InfoRequest.download_zip_dir, \"download\"))", + "line": 194, + "confidence": "Weak", + "user_input": "InfoRequest.download_zip_dir", + "message": "Model attribute used in file name", + "fingerprint": "5ed20f867c17c814cfe117906161a26f37b986d694996c9fd0089d4f971dc1d0", + "note": "InfoRequest.download_zip_dir does not contain user input" + } + ], + "updated": "Thu Oct 02 10:43:19 +0000 2014", + "brakeman_version": "2.6.2" +} diff --git a/config/brakeman.yml b/config/brakeman.yml new file mode 100644 index 000000000..1f95903fd --- /dev/null +++ b/config/brakeman.yml @@ -0,0 +1,4 @@ +--- +:output_files: +- tmp/brakeman.html +- tmp/brakeman.json diff --git a/config/httpd.conf-example b/config/httpd.conf-example index e010ac22f..00722fbdf 100644 --- a/config/httpd.conf-example +++ b/config/httpd.conf-example @@ -34,7 +34,7 @@ # Passenger's default MaxPoolSize is 6. At the time of writing # normal instances of Alaveteli seem to take 150-200MB per # process, so we've set this conservatively at 3. Read the guides - # above to tune this for your system + # above to tune this for your system PassengerMaxPoolSize 3 # The RAILS_ENV that the app is running in. This can be any of @@ -97,6 +97,11 @@ # # The condition means that the rule will fire only if the cached # file exists. + # + # The second condition-rule pair handles the same transformation for + # files served from a non-default locale, 'cy'. You will need one + # set of rules for each non-default locale. + RewriteMap escape int:escape RewriteCond %{DOCUMENT_ROOT}/views_cache/request/$2/$1/${escape:$3} -f RewriteRule ^/request/((\d{1,3})\d*)/(response/\d+/attach/(html/)?\d+/.+) /views_cache/request/$2/$1/${escape:$3} [L] diff --git a/config/initializers/alaveteli.rb b/config/initializers/alaveteli.rb index 2ca85579a..128f6bc5a 100644 --- a/config/initializers/alaveteli.rb +++ b/config/initializers/alaveteli.rb @@ -56,6 +56,7 @@ require 'public_body_csv' require 'category_and_heading_migrator' require 'public_body_categories' require 'routing_filters' +require 'alaveteli_text_masker' AlaveteliLocalization.set_locales(AlaveteliConfiguration::available_locales, AlaveteliConfiguration::default_locale) diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb new file mode 100644 index 000000000..99730e6b2 --- /dev/null +++ b/config/initializers/secure_headers.rb @@ -0,0 +1,24 @@ +::SecureHeaders::Configuration.configure do |config| + + # https://tools.ietf.org/html/rfc6797 + if AlaveteliConfiguration::force_ssl + config.hsts = { :max_age => 20.years.to_i, :include_subdomains => true } + else + config.hsts = false + end + # https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-02 + config.x_frame_options = "sameorigin" + + # http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx + config.x_content_type_options = "nosniff" + + # http://msdn.microsoft.com/en-us/library/dd565647%28v=vs.85%29.aspx + config.x_xss_protection = { :value => 1 } + + # https://w3c.github.io/webappsec/specs/content-security-policy/ + config.csp = false + + # https://www.nwebsec.com/HttpHeaders/SecurityHeaders/XDownloadOptions + config.x_download_options = false +end + diff --git a/config/routes.rb b/config/routes.rb index 4b2eb5695..c975d6007 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -169,17 +169,15 @@ Alaveteli::Application.routes.draw do #### #### AdminPublicBody controller - match '/admin/missing_scheme' => 'admin_public_body#missing_scheme', :as => :admin_body_missing - match '/admin/body' => 'admin_public_body#index', :as => :admin_body_index - match '/admin/body/list' => 'admin_public_body#list', :as => :admin_body_list - match '/admin/body/show/:id' => 'admin_public_body#show', :as => :admin_body_show - match '/admin/body/new' => 'admin_public_body#new', :as => :admin_body_new - match '/admin/body/edit/:id' => 'admin_public_body#edit', :as => :admin_body_edit - match '/admin/body/update/:id' => 'admin_public_body#update', :as => :admin_body_update - match '/admin/body/create' => 'admin_public_body#create', :as => :admin_body_create - match '/admin/body/destroy/:id' => 'admin_public_body#destroy', :as => :admin_body_destroy - match '/admin/body/import_csv' => 'admin_public_body#import_csv', :as => :admin_body_import_csv - match '/admin/body/mass_tag_add' => 'admin_public_body#mass_tag_add', :as => :admin_body_mass_tag_add + scope '/admin', :as => 'admin' do + resources :bodies, + :controller => 'admin_public_body' do + get 'missing_scheme', :on => :collection + post 'mass_tag_add', :on => :collection + get 'import_csv', :on => :collection + post 'import_csv', :on => :collection + end + end #### #### AdminPublicBodyCategory controller @@ -200,9 +198,27 @@ Alaveteli::Application.routes.draw do end #### + #### AdminHoliday controller + scope '/admin', :as => 'admin' do + resources :holidays, + :controller => 'admin_holidays' + end + #### + + #### AdminHolidayImports controller + scope '/admin', :as => 'admin' do + resources :holiday_imports, + :controller => 'admin_holiday_imports', + :only => [:new, :create] + end + #### + #### AdminPublicBodyChangeRequest controller - match '/admin/change_request/edit/:id' => 'admin_public_body_change_requests#edit', :as => :admin_change_request_edit - match '/admin/change_request/update/:id' => 'admin_public_body_change_requests#update', :as => :admin_change_request_update + scope '/admin', :as => 'admin' do + resources :change_requests, + :controller => 'admin_public_body_change_requests', + :only => [:edit, :update] + end #### #### AdminGeneral controller @@ -213,81 +229,99 @@ Alaveteli::Application.routes.draw do #### #### AdminRequest controller - match '/admin/request' => 'admin_request#index', :as => :admin_request_index - match '/admin/request/list' => 'admin_request#list', :as => :admin_request_list - match '/admin/request/show/:id' => 'admin_request#show', :as => :admin_request_show - match '/admin/request/resend' => 'admin_request#resend', :as => :admin_request_resend - match '/admin/request/edit/:id' => 'admin_request#edit', :as => :admin_request_edit - match '/admin/request/update/:id' => 'admin_request#update', :as => :admin_request_update - match '/admin/request/destroy/:id' => 'admin_request#fully_destroy', :as => :admin_request_destroy - match '/admin/request/edit_comment/:id' => 'admin_request#edit_comment', :as => :admin_request_edit_comment - match '/admin/request/update_comment/:id' => 'admin_request#update_comment', :as => :admin_request_update_comment - match '/admin/request/move_request' => 'admin_request#move_request', :as => :admin_request_move_request - match '/admin/request/generate_upload_url/:id' => 'admin_request#generate_upload_url', :as => :admin_request_generate_upload_url - match '/admin/request/show_raw_email/:id' => 'admin_request#show_raw_email', :as => :admin_request_show_raw_email - match '/admin/request/download_raw_email/:id' => 'admin_request#download_raw_email', :as => :admin_request_download_raw_email - match '/admin/request/mark_event_as_clarification' => 'admin_request#mark_event_as_clarification', :as => :admin_request_clarification - match '/admin/request/hide/:id' => 'admin_request#hide_request', :as => :admin_request_hide - #### + scope '/admin', :as => 'admin' do + resources :requests, + :controller => 'admin_request', + :except => [:new, :create] do + post 'move', :on => :member + post 'generate_upload_url', :on => :member + post 'hide', :on => :member + resources :censor_rules, + :controller => 'admin_censor_rule', + :only => [:new, :create], + :name_prefix => 'request_' - #### AdminIncomingMessage controller - match '/admin/incoming/destroy' => 'admin_incoming_message#destroy', :as => :admin_incoming_destroy - match '/admin/incoming/redeliver' => 'admin_incoming_message#redeliver', :as => :admin_incoming_redeliver - match '/admin/incoming/edit/:id' => 'admin_incoming_message#edit', :as => :admin_incoming_edit - match '/admin/incoming/update/:id' => 'admin_incoming_message#update', :as => :admin_incoming_update + end + end #### - #### AdminOutgoingMessage controller - match '/admin/outgoing/edit/:id' => 'admin_outgoing_message#edit', :as => :admin_outgoing_edit - match '/admin/outgoing/destroy/:id' => 'admin_outgoing_message#destroy', :as => :admin_outgoing_destroy - match '/admin/outgoing/update/:id' => 'admin_outgoing_message#update', :as => :admin_outgoing_update + #### AdminComment controller + scope '/admin', :as => 'admin' do + resources :comments, + :controller => 'admin_comment', + :only => [:edit, :update] + end #### - #### AdminUser controller - match '/admin/user' => 'admin_user#index', :as => :admin_user_index - match '/admin/user/list' => 'admin_user#list', :as => :admin_user_list - match '/admin/user/banned' => 'admin_user#list_banned', :as => :admin_user_list_banned - match '/admin/user/show/:id' => 'admin_user#show', :as => :admin_user_show - match '/admin/user/edit/:id' => 'admin_user#edit', :as => :admin_user_edit - match '/admin/user/show_bounce_message/:id' => 'admin_user#show_bounce_message', :as => :admin_user_show_bounce - match '/admin/user/update/:id' => 'admin_user#update', :as => :admin_user_update - match '/admin/user/clear_bounce/:id' => 'admin_user#clear_bounce', :as => :admin_user_clear_bounce - match '/admin/user/destroy_track' => 'admin_user#destroy_track', :as => :admin_user_destroy_track - match '/admin/user/login_as/:id' => 'admin_user#login_as', :as => :admin_user_login_as - match '/admin/user/clear_profile_photo/:id' => 'admin_user#clear_profile_photo', :as => :admin_clear_profile_photo - match '/admin/user/modify_comment_visibility/:id' => 'admin_user#modify_comment_visibility', :as => 'admin_user_modify_comment_visibility' + #### AdminRawEmail controller + scope '/admin', :as => 'admin' do + resources :raw_emails, + :controller => 'admin_raw_email', + :only => [:show] + end #### - #### AdminTrack controller - match '/admin/track/list' => 'admin_track#list', :as => :admin_track_list - #### + #### AdminInfoRequestEvent controller + scope '/admin', :as => 'admin' do + resources :info_request_events, + :controller => 'admin_info_request_event', + :only => [:update] + end - #### AdminCensorRule controller - match '/admin/censor/new' => 'admin_censor_rule#new', :as => :admin_rule_new - match '/admin/censor/create' => 'admin_censor_rule#create', :as => :admin_rule_create - match '/admin/censor/edit/:id' => 'admin_censor_rule#edit', :as => :admin_rule_edit - match '/admin/censor/update/:id' => 'admin_censor_rule#update', :as => :admin_rule_update - match '/admin/censor/destroy/:censor_rule_id' => 'admin_censor_rule#destroy', :as => :admin_rule_destroy + #### AdminIncomingMessage controller + scope '/admin', :as => 'admin' do + resources :incoming_messages, + :controller => 'admin_incoming_message', + :only => [:edit, :update, :destroy] do + post 'redeliver', :on => :member + end + end + #### + #### AdminOutgoingMessage controller scope '/admin', :as => 'admin' do - resources :info_requests, :only => [] do - resources :censor_rules, - :controller => 'admin_censor_rule', - :only => [:new, :create], - :name_prefix => 'info_request_' + resources :outgoing_messages, + :controller => 'admin_outgoing_message', + :only => [:edit, :update, :destroy] do + post 'resend', :on => :member end end + #### + #### AdminUser controller scope '/admin', :as => 'admin' do - resources :users, :only => [] do - resources :censor_rules, - :controller => 'admin_censor_rule', - :only => [:new, :create], - :name_prefix => 'user_' + resources :users, + :controller => 'admin_user', + :except => [:new, :create, :destroy] do + get 'banned', :on => :collection + get 'show_bounce_message', :on => :member + post 'clear_bounce', :on => :member + post 'login_as', :on => :member + post 'clear_profile_photo', :on => :member + post 'modify_comment_visibility', :on => :collection + resources :censor_rules, + :controller => 'admin_censor_rule', + :only => [:new, :create], + :name_prefix => 'user_' end end #### + #### AdminTrack controller + scope '/admin', :as => 'admin' do + resources :tracks, + :controller => 'admin_track', + :only => [:index, :destroy] + end + #### + + #### AdminCensorRule controller + scope '/admin', :as => 'admin' do + resources :censor_rules, + :controller => 'admin_censor_rule', + :except => [:index, :new, :create] + end + #### AdminSpamAddresses controller scope '/admin', :as => 'admin' do resources :spam_addresses, diff --git a/doc/CHANGES.md b/doc/CHANGES.md index 8ee29a9f6..421099604 100644 --- a/doc/CHANGES.md +++ b/doc/CHANGES.md @@ -1,3 +1,29 @@ +# rails-3-develop + +## Highlighted Features + +* State changing admin actions are now restricted to PUT or POST methods + to protect against CSRF attacks, and now use more standard RESTful + routing. +* Global request forgery protection is now used +* The dynamic list of possibly related requests for a new request is now + limited to requests to the same authority and capped at three requests +* Some standard security headers are added by default +* New rake task for cleaning theme translations - `rake + gettext:clean_theme` +* A TTL is enforced on session cookies +* There's a new admin interface for adding public holidays for the site, + to be used in calculating request due dates. +* Autocomplete has been turned off on password fields +* Some interface phrases have been grouped together for easier + translation + +## Upgrade notes + +* Admin route names have been standardised so if you have overridden + templates that refer to admin routes, check the original templates to + see if these need to be changed. + # Version 0.20 ## Highlighted Features diff --git a/lib/alaveteli_text_masker.rb b/lib/alaveteli_text_masker.rb new file mode 100644 index 000000000..68ff0d318 --- /dev/null +++ b/lib/alaveteli_text_masker.rb @@ -0,0 +1,127 @@ +module AlaveteliTextMasker + extend self + DoNotBinaryMask = [ 'image/tiff', + 'image/gif', + 'image/jpeg', + 'image/png', + 'image/bmp', + 'application/zip' ] + + # Replaces all email addresses in (possibly binary) data + # Also applies custom masks and censor items + def apply_masks!(text, content_type, options = {}) + # See if content type is one that we mask - things like zip files and + # images may get broken if we try to. We err on the side of masking too + # much, as many unknown types will really be text. + + # Special cases for some content types + case content_type + when *DoNotBinaryMask + # do nothing + when 'text/html' + apply_text_masks!(text, options) + when 'application/pdf' + apply_pdf_masks!(text, options) + else + apply_binary_masks!(text, options) + end + end + + def apply_pdf_masks!(text, options = {}) + uncompressed_text = nil + uncompressed_text = AlaveteliExternalCommand.run("pdftk", "-", "output", "-", "uncompress", + :stdin_string => text) + # if we managed to uncompress the PDF... + if !uncompressed_text.blank? + # then censor stuff (making a copy so can compare again in a bit) + censored_uncompressed_text = uncompressed_text.dup + apply_binary_masks!(censored_uncompressed_text, options) + # if the censor rule removed something... + if censored_uncompressed_text != uncompressed_text + # then use the altered file (recompressed) + recompressed_text = nil + if AlaveteliConfiguration::use_ghostscript_compression == true + command = ["gs", "-sDEVICE=pdfwrite", "-dCompatibilityLevel=1.4", "-dPDFSETTINGS=/screen", "-dNOPAUSE", "-dQUIET", "-dBATCH", "-sOutputFile=-", "-"] + else + command = ["pdftk", "-", "output", "-", "compress"] + end + recompressed_text = AlaveteliExternalCommand.run(*(command + [{:stdin_string=>censored_uncompressed_text}])) + if recompressed_text.blank? + # 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 + logger.warn "Unable to compress PDF; problem with your pdftk version?" + end + if !recompressed_text.blank? + text.replace recompressed_text + end + end + end + end + + private + + # Replace text in place + def apply_binary_masks!(text, options = {}) + # Keep original size, so can check haven't resized it + orig_size = text.mb_chars.size + + # Replace ASCII email addresses... + text.gsub!(MySociety::Validate.email_find_regexp) do |email| + email.gsub(/[^@.]/, 'x') + end + + # And replace UCS-2 ones (for Microsoft Office documents)... + # Find emails, by finding them in parts of text that have ASCII + # equivalents to the UCS-2 + ascii_chars = text.gsub(/\0/, "") + emails = ascii_chars.scan(MySociety::Validate.email_find_regexp) + + # Convert back to UCS-2, making a mask at the same time + if String.method_defined?(:encode) + emails.map! do |email| + # We want the ASCII representation of UCS-2 + [email[0].encode('UTF-16LE').force_encoding('US-ASCII'), + email[0].gsub(/[^@.]/, 'x').encode('UTF-16LE').force_encoding('US-ASCII')] + end + else + emails.map! {|email| [ + Iconv.conv('ucs-2le', 'ascii', email[0]), + Iconv.conv('ucs-2le', 'ascii', email[0].gsub(/[^@.]/, 'x')) + ] } + end + + # Now search and replace the UCS-2 email with the UCS-2 mask + for email, mask in emails + text.gsub!(email, mask) + end + + # Replace censor items + censor_rules = options[:censor_rules] || [] + censor_rules.each{ |censor_rule| censor_rule.apply_to_binary!(text) } + raise "internal error in apply_binary_masks!" if text.mb_chars.size != orig_size + return text + end + + # Remove any email addresses, login links and mobile phone numbers + def default_text_masks + [{ :to_replace => MySociety::Validate.email_find_regexp, + :replacement => "[#{_("email address")}]" }, + { :to_replace => /(Mobile|Mob)([\s\/]*(Fax|Tel))*\s*:?[\s\d]*\d/, + :replacement => "[#{_("mobile number")}]" }, + { :to_replace => /https?:\/\/#{AlaveteliConfiguration::domain}\/c\/[^\s]+/, + :replacement => "[#{_("{{site_name}} login link", + :site_name => AlaveteliConfiguration::site_name)}]" }] + end + + def apply_text_masks!(text, options = {}) + masks = options[:masks] || [] + masks += default_text_masks + censor_rules = options[:censor_rules] || [] + masks.each{ |mask| text.gsub!(mask[:to_replace], mask[:replacement]) } + censor_rules.each{ |censor_rule| censor_rule.apply_to_text!(text) } + text + end + +end diff --git a/lib/quiet_opener.rb b/lib/quiet_opener.rb index 16ea27b8e..c6e259b93 100644 --- a/lib/quiet_opener.rb +++ b/lib/quiet_opener.rb @@ -7,8 +7,19 @@ end def quietly_try_to_open(url) begin result = open(url).read.strip - rescue OpenURI::HTTPError, SocketError, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNRESET - Rails.logger.warn("Unable to open third-party URL #{url}") + rescue OpenURI::HTTPError, + SocketError, + Errno::ETIMEDOUT, + Errno::ECONNREFUSED, + Errno::EHOSTUNREACH, + Errno::ECONNRESET, + Timeout::Error => exception + e = Exception.new("Unable to open third-party URL #{url}: #{exception.message}") + e.set_backtrace(exception.backtrace) + if !AlaveteliConfiguration.exception_notifications_from.blank? && !AlaveteliConfiguration.exception_notifications_to.blank? + ExceptionNotifier::Notifier.exception_notification(request.env, e).deliver + end + Rails.logger.warn(e.message) result = "" end return result diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake index 3f357213f..9bdb6169b 100644 --- a/lib/tasks/gettext.rake +++ b/lib/tasks/gettext.rake @@ -11,11 +11,7 @@ namespace :gettext do desc "Update pot/po files for a theme." task :find_theme => :environment do - theme = ENV['THEME'] - unless theme - puts "Usage: Specify an Alaveteli-theme with THEME=[theme directory name]" - exit(0) - end + theme = find_theme(ENV['THEME']) load_gettext msgmerge = Rails.application.config.gettext_i18n_rails.msgmerge msgmerge ||= %w[--sort-output --no-location --no-wrap] @@ -28,6 +24,25 @@ namespace :gettext do ) end + desc 'Rewrite theme .po files into a consistent msgmerge format' + task :clean_theme do + theme = find_theme(ENV['THEME']) + load_gettext + + Dir.glob("#{ theme_locale_path(theme) }/*/app.po") do |po_file| + GetText::msgmerge(po_file, po_file, 'alaveteli', + :msgmerge => [:sort_output, :no_location, :no_wrap]) + end + end + + def find_theme(theme) + unless theme + puts "Usage: Specify an Alaveteli-theme with THEME=[theme directory name]" + exit(0) + end + theme + end + def theme_files_to_translate(theme) Dir.glob("{lib/themes/#{theme}/lib}/**/*.{rb,erb}") end diff --git a/spec/controllers/admin_censor_rule_controller_spec.rb b/spec/controllers/admin_censor_rule_controller_spec.rb index 4df56a92b..68eaecd6a 100644 --- a/spec/controllers/admin_censor_rule_controller_spec.rb +++ b/spec/controllers/admin_censor_rule_controller_spec.rb @@ -5,76 +5,68 @@ describe AdminCensorRuleController do describe 'GET new' do - it 'returns a successful response' do - get :new - expect(response).to be_success - end + context 'request_id param' do - it 'initializes a new censor rule' do - get :new - expect(assigns[:censor_rule]).to be_new_record - end + before do + @info_request = FactoryGirl.create(:info_request) + get :new, :request_id => @info_request.id, :name_prefix => 'request_' + end - it 'renders the correct template' do - get :new - expect(response).to render_template('new') - end + it 'returns a successful response' do + expect(response).to be_success + end - it 'sets the URL for the form to POST to' do - get :new - expect(assigns[:form_url]).to eq(admin_rule_create_path) - end + it 'initializes a new censor rule' do + expect(assigns[:censor_rule]).to be_new_record + end - context 'info_request_id param' do + it 'renders the correct template' do + expect(response).to render_template('new') + end - it 'finds an info request if the info_request_id param is supplied' do - info_request = FactoryGirl.create(:info_request) - get :new, :info_request_id => info_request.id - expect(assigns[:info_request]).to eq(info_request) + it 'finds an info request if the request_id param is supplied' do + expect(assigns[:info_request]).to eq(@info_request) end it 'associates the info request with the new censor rule' do - info_request = FactoryGirl.create(:info_request) - get :new, :info_request_id => info_request.id - expect(assigns[:censor_rule].info_request).to eq(info_request) + expect(assigns[:censor_rule].info_request).to eq(@info_request) end it 'sets the URL for the form to POST to' do - info_request = FactoryGirl.create(:info_request) - get :new, :info_request_id => info_request.id - expect(assigns[:form_url]).to eq(admin_info_request_censor_rules_path(info_request)) - end - - it 'does not find an info request if no info_request_id param is supplied' do - get :new - expect(assigns[:info_request]).to be_nil + expect(assigns[:form_url]).to eq(admin_request_censor_rules_path(@info_request)) end end context 'user_id param' do + before do + @user = FactoryGirl.create(:user) + get :new, :user_id => @user.id, :name_prefix => 'user_' + end + + it 'returns a successful response' do + expect(response).to be_success + end + + it 'initializes a new censor rule' do + expect(assigns[:censor_rule]).to be_new_record + end + + it 'renders the correct template' do + expect(response).to render_template('new') + end + it 'finds a user if the user_id param is supplied' do - user = FactoryGirl.create(:user) - get :new, :user_id => user.id - expect(assigns[:censor_user]).to eq(user) + expect(assigns[:censor_user]).to eq(@user) end it 'associates the user with the new censor rule' do - user = FactoryGirl.create(:user) - get :new, :user_id => user.id - expect(assigns[:censor_rule].user).to eq(user) + expect(assigns[:censor_rule].user).to eq(@user) end it 'sets the URL for the form to POST to' do - user = FactoryGirl.create(:user) - get :new, :user_id => user.id - expect(assigns[:form_url]).to eq(admin_user_censor_rules_path(user)) - end - - it 'does not find a user if no user_id param is supplied' do - get :new - expect(assigns[:censor_user]).to be_nil + expect(assigns[:form_url]).to eq(admin_user_censor_rules_path(@user)) end end @@ -83,256 +75,292 @@ describe AdminCensorRuleController do describe 'POST create' do - before(:each) do - @censor_rule_params = FactoryGirl.build(:global_censor_rule).serializable_hash - # last_edit_editor gets set in the controller - @censor_rule_params.delete(:last_edit_editor) - end - - it 'sets the last_edit_editor to the current admin' do - post :create, :censor_rule => @censor_rule_params - expect(assigns[:censor_rule].last_edit_editor).to eq('*unknown*') - end + context 'request_id param' do - it 'sets the URL for the form to POST to' do - post :create, :censor_rule => @censor_rule_params - expect(assigns[:form_url]).to eq(admin_rule_create_path) - end + before(:each) do + @censor_rule_params = FactoryGirl.build(:info_request_censor_rule).serializable_hash + # last_edit_editor gets set in the controller + @censor_rule_params.delete(:last_edit_editor) + @info_request = FactoryGirl.create(:info_request) + post :create, :request_id => @info_request.id, + :censor_rule => @censor_rule_params, + :name_prefix => 'request_' + end - context 'info_request_id param' do + it 'sets the last_edit_editor to the current admin' do + expect(assigns[:censor_rule].last_edit_editor).to eq('*unknown*') + end - it 'finds an info request if the info_request_id param is supplied' do - info_request = FactoryGirl.create(:info_request) - post :create, :info_request_id => info_request.id, - :censor_rule => @censor_rule_params - expect(assigns[:info_request]).to eq(info_request) + it 'finds an info request if the request_id param is supplied' do + expect(assigns[:info_request]).to eq(@info_request) end it 'associates the info request with the new censor rule' do - info_request = FactoryGirl.create(:info_request) - post :create, :info_request_id => info_request.id, - :censor_rule => @censor_rule_params - expect(assigns[:censor_rule].info_request).to eq(info_request) + expect(assigns[:censor_rule].info_request).to eq(@info_request) end it 'sets the URL for the form to POST to' do - info_request = FactoryGirl.create(:info_request) - post :create, :info_request_id => info_request.id, - :censor_rule => @censor_rule_params - expect(assigns[:form_url]).to eq(admin_info_request_censor_rules_path(info_request)) + expect(assigns[:form_url]).to eq(admin_request_censor_rules_path(@info_request)) end - it 'does not find an info request if no info_request_id param is supplied' do - post :create, :censor_rule => @censor_rule_params - expect(assigns[:info_request]).to be_nil - end + context 'successfully saving the censor rule' do - end + it 'persists the censor rule' do + post :create, :censor_rule => @censor_rule_params, + :request_id => @info_request.id, + :name_prefix => 'request_' + expect(assigns[:censor_rule]).to be_persisted + end - context 'user_id param' do + it 'confirms the censor rule is created' do + post :create, :censor_rule => @censor_rule_params, + :request_id => @info_request.id, + :name_prefix => 'request_' + msg = 'CensorRule was successfully created.' + expect(flash[:notice]).to eq(msg) + end - it 'finds a user if the user_id param is supplied' do - user = FactoryGirl.create(:user) - post :create, :user_id => user.id, - :censor_rule => @censor_rule_params - expect(assigns[:censor_user]).to eq(user) - end + it 'purges the cache for the info request' do + @controller.should_receive(:expire_for_request). + with(@info_request) - it 'associates the user with the new censor rule' do - user = FactoryGirl.create(:user) - post :create, :user_id => user.id, - :censor_rule => @censor_rule_params - expect(assigns[:censor_rule].user).to eq(user) - end + post :create, :censor_rule => @censor_rule_params, + :request_id => @info_request.id, + :name_prefix => 'request_' + end - it 'sets the URL for the form to POST to' do - user = FactoryGirl.create(:user) - post :create, :user_id => user.id, - :censor_rule => @censor_rule_params - expect(assigns[:form_url]).to eq(admin_user_censor_rules_path(user)) + it 'redirects to the associated info request' do + post :create, :censor_rule => @censor_rule_params, + :request_id => @info_request.id, + :name_prefix => 'request_' + expect(response).to redirect_to( + admin_request_path(assigns[:censor_rule].info_request) + ) + end end - it 'does not find a user if no user_id param is supplied' do - post :create, :censor_rule => @censor_rule_params - expect(assigns[:censor_user]).to be_nil - end + context 'unsuccessfully saving the censor rule' do + + before(:each) do + CensorRule.any_instance.stub(:save).and_return(false) + end + + it 'does not persist the censor rule' do + post :create, :censor_rule => @censor_rule_params, + :request_id => @info_request.id, + :name_prefix => 'request_' + expect(assigns[:censor_rule]).to be_new_record + end + it 'renders the form' do + post :create, :censor_rule => @censor_rule_params, + :request_id => @info_request.id, + :name_prefix => 'request_' + expect(response).to render_template('new') + end + + end end - context 'successfully saving the censor rule' do + context 'user_id param' do before(:each) do - CensorRule.any_instance.stub(:save).and_return(true) + @censor_rule_params = FactoryGirl.build(:user_censor_rule).serializable_hash + # last_edit_editor gets set in the controller + @censor_rule_params.delete(:last_edit_editor) + @user = FactoryGirl.create(:user) + post :create, :user_id => @user.id, + :censor_rule => @censor_rule_params, + :name_prefix => 'user_' end - it 'persists the censor rule' do - pending("This raises an internal error in most cases") - post :create, :censor_rule => @censor_rule_params - expect(assigns[:censor_rule]).to be_persisted + it 'sets the last_edit_editor to the current admin' do + expect(assigns[:censor_rule].last_edit_editor).to eq('*unknown*') end - it 'confirms the censor rule is created' do - pending("This raises an internal error in most cases") - post :create, :censor_rule => @censor_rule_params - msg = 'CensorRule was successfully created.' - expect(flash[:notice]).to eq(msg) + it 'finds a user if the user_id param is supplied' do + expect(assigns[:censor_user]).to eq(@user) end - it 'raises an error after creating the rule' do - expect { - post :create, :censor_rule => @censor_rule_params - }.to raise_error 'internal error' + it 'associates the user with the new censor rule' do + expect(assigns[:censor_rule].user).to eq(@user) end - context 'a CensorRule with an associated InfoRequest' do + it 'sets the URL for the form to POST to' do + expect(assigns[:form_url]).to eq(admin_user_censor_rules_path(@user)) + end - before(:each) do - @censor_rule_params = FactoryGirl.build(:info_request_censor_rule).serializable_hash - # last_edit_editor gets set in the controller - @censor_rule_params.delete(:last_edit_editor) - end + context 'successfully saving the censor rule' do - it 'purges the cache for the info request' do + it 'purges the cache for the info request' do censor_rule = CensorRule.new(@censor_rule_params) - @controller.should_receive(:expire_for_request). - with(censor_rule.info_request) + @controller.should_receive(:expire_requests_for_user). + with(@user) - post :create, :censor_rule => @censor_rule_params + post :create, :censor_rule => @censor_rule_params, + :user_id => @user.id, + :name_prefix => 'user_' end it 'redirects to the associated info request' do - post :create, :censor_rule => @censor_rule_params + post :create, :censor_rule => @censor_rule_params, + :user_id => @user.id, + :name_prefix => 'user_' expect(response).to redirect_to( - admin_request_show_path(assigns[:censor_rule].info_request) + admin_user_path(assigns[:censor_rule].user) ) end end - context 'a CensorRule with an associated User' do + context 'unsuccessfully saving the censor rule' do before(:each) do - @censor_rule_params = FactoryGirl.build(:user_censor_rule).serializable_hash - # last_edit_editor gets set in the controller - @censor_rule_params.delete(:last_edit_editor) + CensorRule.any_instance.stub(:save).and_return(false) end - it 'purges the cache for the info request' do - censor_rule = CensorRule.new(@censor_rule_params) - @controller.should_receive(:expire_requests_for_user). - with(censor_rule.user) - - post :create, :censor_rule => @censor_rule_params + it 'does not persist the censor rule' do + post :create, :censor_rule => @censor_rule_params, + :user_id => @user.id, + :name_prefix => 'user_' + expect(assigns[:censor_rule]).to be_new_record end - it 'redirects to the associated info request' do - post :create, :censor_rule => @censor_rule_params - expect(response).to redirect_to( - admin_user_show_path(assigns[:censor_rule].user) - ) + it 'renders the form' do + post :create, :censor_rule => @censor_rule_params, + :user_id => @user.id, + :name_prefix => 'user_' + expect(response).to render_template('new') end end end - context 'unsuccessfully saving the censor rule' do + end + + describe 'GET edit' do + + context 'a CensorRule with an associated InfoRequest' do before(:each) do - CensorRule.any_instance.stub(:save).and_return(false) + @censor_rule = FactoryGirl.create(:info_request_censor_rule) end - it 'does not persist the censor rule' do - post :create, :censor_rule => @censor_rule_params - expect(assigns[:censor_rule]).to be_new_record + it 'returns a successful response' do + get :edit, :id => @censor_rule.id + expect(response).to be_success end - it 'renders the form' do - post :create, :censor_rule => @censor_rule_params - expect(response).to render_template('new') + it 'renders the correct template' do + get :edit, :id => @censor_rule.id + expect(response).to render_template('edit') + end + + it 'finds the correct censor rule to edit' do + get :edit, :id => @censor_rule.id + expect(assigns[:censor_rule]).to eq(@censor_rule) end end - end + context 'a CensorRule with an associated User' do - describe 'GET edit' do + before(:each) do + @censor_rule = FactoryGirl.create(:user_censor_rule) + end - before(:each) do - @censor_rule = FactoryGirl.create(:global_censor_rule) - end + it 'returns a successful response' do + get :edit, :id => @censor_rule.id + expect(response).to be_success + end - it 'returns a successful response' do - get :edit, :id => @censor_rule.id - expect(response).to be_success - end + it 'renders the correct template' do + get :edit, :id => @censor_rule.id + expect(response).to render_template('edit') + end + + it 'finds the correct censor rule to edit' do + get :edit, :id => @censor_rule.id + expect(assigns[:censor_rule]).to eq(@censor_rule) + end - it 'renders the correct template' do - get :edit, :id => @censor_rule.id - expect(response).to render_template('edit') end - it 'finds the correct censor rule to edit' do - get :edit, :id => @censor_rule.id - expect(assigns[:censor_rule]).to eq(@censor_rule) + context 'when editing a global rule' do + + before(:each) do + @censor_rule = FactoryGirl.create(:global_censor_rule) + end + + it 'shows an error notice' do + get :edit, :id => @censor_rule.id + flash[:notice].should == 'Only user and request censor rules can be edited' + end + + it 'redirects to the admin index' do + get :edit, :id => @censor_rule.id + expect(response).to redirect_to(admin_general_index_path) + end + end end describe 'PUT update' do - before(:each) do - @censor_rule = FactoryGirl.create(:global_censor_rule) - end + context 'a global CensorRule' do - it 'finds the correct censor rule to edit' do - put :update, :id => @censor_rule.id, - :censor_rule => { :text => 'different text' } + before(:each) do + @censor_rule = FactoryGirl.create(:global_censor_rule) + end - expect(assigns[:censor_rule]).to eq(@censor_rule) - end + it 'shows an error notice' do + get :edit, :id => @censor_rule.id + flash[:notice].should == 'Only user and request censor rules can be edited' + end - it 'sets the last_edit_editor to the current admin' do - put :update, :id => @censor_rule.id, - :censor_rule => { :text => 'different text' } + it 'redirects to the admin index' do + get :edit, :id => @censor_rule.id + expect(response).to redirect_to(admin_general_index_path) + end - expect(assigns[:censor_rule].last_edit_editor).to eq('*unknown*') end - context 'successfully saving the censor rule' do + context 'a CensorRule with an associated InfoRequest' do before(:each) do - CensorRule.any_instance.stub(:save).and_return(true) + @censor_rule = FactoryGirl.create(:info_request_censor_rule) end - it 'updates the censor rule' do - pending("This raises an internal error in most cases") + it 'finds the correct censor rule to edit' do put :update, :id => @censor_rule.id, :censor_rule => { :text => 'different text' } - @censor_rule.reload - expect(@censor_rule.text).to eq('different text') + + expect(assigns[:censor_rule]).to eq(@censor_rule) end - it 'confirms the censor rule is updated' do - pending("This raises an internal error in most cases") + it 'sets the last_edit_editor to the current admin' do put :update, :id => @censor_rule.id, :censor_rule => { :text => 'different text' } - msg = 'CensorRule was successfully updated.' - expect(flash[:notice]).to eq(msg) + expect(assigns[:censor_rule].last_edit_editor).to eq('*unknown*') end - it 'raises an error after updating the rule' do - expect { + context 'successfully saving the censor rule' do + + it 'updates the censor rule' do put :update, :id => @censor_rule.id, :censor_rule => { :text => 'different text' } - }.to raise_error 'internal error' - end - - context 'a CensorRule with an associated InfoRequest' do + @censor_rule.reload + expect(@censor_rule.text).to eq('different text') + end - before(:each) do - @censor_rule = FactoryGirl.create(:info_request_censor_rule) + it 'confirms the censor rule is updated' do + put :update, :id => @censor_rule.id, + :censor_rule => { :text => 'different text' } + msg = 'CensorRule was successfully updated.' + expect(flash[:notice]).to eq(msg) end it 'purges the cache for the info request' do @@ -348,87 +376,137 @@ describe AdminCensorRuleController do :censor_rule => { :text => 'different text' } expect(response).to redirect_to( - admin_request_show_path(assigns[:censor_rule].info_request) + admin_request_path(assigns[:censor_rule].info_request) ) end end - context 'a CensorRule with an associated User' do + context 'unsuccessfully saving the censor rule' do before(:each) do - @censor_rule = FactoryGirl.create(:user_censor_rule) + CensorRule.any_instance.stub(:save).and_return(false) end - it 'purges the cache for the info request' do - @controller.should_receive(:expire_requests_for_user). - with(@censor_rule.user) - + it 'does not update the censor rule' do put :update, :id => @censor_rule.id, :censor_rule => { :text => 'different text' } + @censor_rule.reload + expect(@censor_rule.text).to eq('some text to redact') end - it 'redirects to the associated info request' do + it 'renders the form' do put :update, :id => @censor_rule.id, :censor_rule => { :text => 'different text' } - expect(response).to redirect_to( - admin_user_show_path(assigns[:censor_rule].user) - ) + expect(response).to render_template('edit') end - end + end + end - context 'unsuccessfully saving the censor rule' do + context 'a CensorRule with an associated User' do before(:each) do - CensorRule.any_instance.stub(:save).and_return(false) + @censor_rule = FactoryGirl.create(:user_censor_rule) end - it 'does not update the censor rule' do + it 'finds the correct censor rule to edit' do put :update, :id => @censor_rule.id, :censor_rule => { :text => 'different text' } - @censor_rule.reload - expect(@censor_rule.text).to eq('some text to redact') + + expect(assigns[:censor_rule]).to eq(@censor_rule) end - it 'renders the form' do + it 'sets the last_edit_editor to the current admin' do put :update, :id => @censor_rule.id, :censor_rule => { :text => 'different text' } - expect(response).to render_template('edit') + expect(assigns[:censor_rule].last_edit_editor).to eq('*unknown*') + + end + + + context 'successfully saving the censor rule' do + + it 'updates the censor rule' do + put :update, :id => @censor_rule.id, + :censor_rule => { :text => 'different text' } + @censor_rule.reload + expect(@censor_rule.text).to eq('different text') + end + + it 'confirms the censor rule is updated' do + put :update, :id => @censor_rule.id, + :censor_rule => { :text => 'different text' } + msg = 'CensorRule was successfully updated.' + expect(flash[:notice]).to eq(msg) + end + + it 'purges the cache for the info request' do + @controller.should_receive(:expire_requests_for_user). + with(@censor_rule.user) + + put :update, :id => @censor_rule.id, + :censor_rule => { :text => 'different text' } + end + + it 'redirects to the associated info request' do + put :update, :id => @censor_rule.id, + :censor_rule => { :text => 'different text' } + + expect(response).to redirect_to( + admin_user_path(assigns[:censor_rule].user) + ) + end end + context 'unsuccessfully saving the censor rule' do + + before(:each) do + CensorRule.any_instance.stub(:save).and_return(false) + end + + it 'does not update the censor rule' do + put :update, :id => @censor_rule.id, + :censor_rule => { :text => 'different text' } + @censor_rule.reload + expect(@censor_rule.text).to eq('some text to redact') + end + + it 'renders the form' do + put :update, :id => @censor_rule.id, + :censor_rule => { :text => 'different text' } + + expect(response).to render_template('edit') + end + + end + end end describe 'DELETE destroy' do - before(:each) do - @censor_rule = FactoryGirl.create(:global_censor_rule) - end + context 'a global CensorRule' do - it 'finds the correct censor rule to destroy' do - pending("This raises an internal error in most cases") - # TODO: Replace :censor_rule_id with :id - delete :destroy, :censor_rule_id => @censor_rule.id - expect(assigns[:censor_rule]).to eq(@censor_rule) - end + before(:each) do + @censor_rule = FactoryGirl.create(:global_censor_rule) + end - it 'raises an error after destroying the rule' do - expect { - delete :destroy, :censor_rule_id => @censor_rule.id - }.to raise_error 'internal error' - end + it 'shows an error notice' do + get :edit, :id => @censor_rule.id + flash[:notice].should == 'Only user and request censor rules can be edited' + end + + it 'redirects to the admin index' do + get :edit, :id => @censor_rule.id + expect(response).to redirect_to(admin_general_index_path) + end - it 'confirms the censor rule is destroyed in all cases' do - pending("This actually raises an internal error anyway") - delete :destroy, :censor_rule_id => @censor_rule.id - msg = 'CensorRule was successfully destroyed.' - expect(flash[:notice]).to eq(msg) end context 'a CensorRule with an associated InfoRequest' do @@ -437,14 +515,25 @@ describe AdminCensorRuleController do @censor_rule = FactoryGirl.create(:info_request_censor_rule) end + it 'finds the correct censor rule to destroy' do + delete :destroy, :id => @censor_rule.id + expect(assigns[:censor_rule]).to eq(@censor_rule) + end + + it 'confirms the censor rule is destroyed in all cases' do + delete :destroy, :id => @censor_rule.id + msg = 'CensorRule was successfully destroyed.' + expect(flash[:notice]).to eq(msg) + end + it 'purges the cache for the info request' do @controller.should_receive(:expire_for_request).with(@censor_rule.info_request) - delete :destroy, :censor_rule_id => @censor_rule.id + delete :destroy, :id => @censor_rule.id end it 'redirects to the associated info request' do - delete :destroy, :censor_rule_id => @censor_rule.id - expect(response).to redirect_to(admin_request_show_path(@censor_rule.info_request)) + delete :destroy, :id => @censor_rule.id + expect(response).to redirect_to(admin_request_path(@censor_rule.info_request)) end end @@ -455,14 +544,25 @@ describe AdminCensorRuleController do @censor_rule = FactoryGirl.create(:user_censor_rule) end + it 'finds the correct censor rule to destroy' do + delete :destroy, :id => @censor_rule.id + expect(assigns[:censor_rule]).to eq(@censor_rule) + end + + it 'confirms the censor rule is destroyed in all cases' do + delete :destroy, :id => @censor_rule.id + msg = 'CensorRule was successfully destroyed.' + expect(flash[:notice]).to eq(msg) + end + it 'purges the cache for the user' do @controller.should_receive(:expire_requests_for_user).with(@censor_rule.user) - delete :destroy, :censor_rule_id => @censor_rule.id + delete :destroy, :id => @censor_rule.id end it 'redirects to the associated info request' do - delete :destroy, :censor_rule_id => @censor_rule.id - expect(response).to redirect_to(admin_user_show_path(@censor_rule.user)) + delete :destroy, :id => @censor_rule.id + expect(response).to redirect_to(admin_user_path(@censor_rule.user)) end end @@ -474,17 +574,17 @@ end describe AdminCensorRuleController, "when making censor rules from the admin interface" do render_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 => { + ir = info_requests(:fancy_dog_request) + post :create, :request_id => ir.id, + :name_prefix => 'request_', + :censor_rule => { :text => "meat", :replacement => "tofu", - :last_edit_comment => "none", - :info_request_id => ir + :last_edit_comment => "none" } PurgeRequest.all().first.model_id.should == ir.id end - end diff --git a/spec/controllers/admin_comment_controller_spec.rb b/spec/controllers/admin_comment_controller_spec.rb new file mode 100644 index 000000000..f87231e3b --- /dev/null +++ b/spec/controllers/admin_comment_controller_spec.rb @@ -0,0 +1,66 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe AdminCommentController do + + describe :edit do + + before do + @comment = FactoryGirl.create(:comment) + get :edit, :id => @comment.id + end + + it 'renders the edit template' do + expect(response).to render_template('edit') + end + + it 'gets the comment' do + assigns[:comment].should == @comment + end + + end + + describe :update do + + context 'on valid data submission' do + + before do + @comment = FactoryGirl.create(:comment) + atts = FactoryGirl.attributes_for(:comment, :body => 'I am new') + put :update, :id => @comment.id, :comment => atts + end + + it 'gets the comment' do + assigns[:comment].should == @comment + end + + it 'updates the comment' do + Comment.find(@comment.id).body.should == 'I am new' + end + + it 'logs the update event' do + most_recent_event = Comment.find(@comment.id).info_request_events.last + most_recent_event.event_type.should == 'edit_comment' + most_recent_event.comment_id.should == @comment.id + end + + it 'shows a success notice' do + flash[:notice].should == "Comment successfully updated." + end + + it 'redirects to the request page' do + response.should redirect_to(admin_request_path(@comment.info_request)) + end + end + + context 'on invalid data submission' do + + it 'renders the edit template' do + @comment = FactoryGirl.create(:comment) + put :update, :id => @comment.id, :comment => {:body => ''} + response.should render_template('edit') + end + + end + end + +end diff --git a/spec/controllers/admin_holiday_imports_controller_spec.rb b/spec/controllers/admin_holiday_imports_controller_spec.rb new file mode 100644 index 000000000..dd23a022f --- /dev/null +++ b/spec/controllers/admin_holiday_imports_controller_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe AdminHolidayImportsController do + + describe :new do + + it 'renders the new template' do + get :new + expect(response).to render_template('new') + end + + it 'creates an import' do + get :new + assigns[:holiday_import].should be_instance_of(HolidayImport) + end + + describe 'if the import is valid' do + + it 'populates the import' do + mock_import = mock(HolidayImport, :valid? => true, + :populate => nil) + HolidayImport.stub!(:new).and_return(mock_import) + mock_import.should_receive(:populate) + get :new + end + + end + + end + + describe :create do + + it 'creates an import' do + post :create + assigns[:holiday_import].should be_instance_of(HolidayImport) + end + + describe 'if the import can be saved' do + + before do + mock_import = mock(HolidayImport, :save => true) + HolidayImport.stub!(:new).and_return(mock_import) + post :create + end + + it 'should show a success notice' do + flash[:notice].should == 'Holidays successfully imported' + end + + it 'should redirect to the index' do + response.should redirect_to(admin_holidays_path) + end + + end + + describe 'if the import cannot be saved' do + + before do + mock_import = mock(HolidayImport, :save => false) + HolidayImport.stub!(:new).and_return(mock_import) + post :create + end + + it 'should render the new template' do + expect(response).to render_template('new') + end + + end + + end + + +end diff --git a/spec/controllers/admin_holidays_controller_spec.rb b/spec/controllers/admin_holidays_controller_spec.rb new file mode 100644 index 000000000..21cb51d29 --- /dev/null +++ b/spec/controllers/admin_holidays_controller_spec.rb @@ -0,0 +1,192 @@ +require 'spec_helper' + +describe AdminHolidaysController do + + describe :index do + + before do + @holiday_one = FactoryGirl.create(:holiday, :day => Date.new(2010, 1, 1)) + @holiday_two = FactoryGirl.create(:holiday, :day => Date.new(2011, 2, 2)) + @holiday_three = FactoryGirl.create(:holiday, :day => Date.new(2011, 3, 3)) + end + + it 'gets a hash of holidays keyed by year' do + get :index + assigns(:holidays_by_year)[2010].should include(@holiday_one) + assigns(:holidays_by_year)[2011].should include(@holiday_two) + assigns(:holidays_by_year)[2011].should include(@holiday_three) + end + + it 'gets a list of years with holidays' do + get :index + assigns(:years).should include(2010) + assigns(:years).should include(2011) + end + + it 'renders the index template' do + get :index + expect(response).to render_template('index') + end + + end + + describe :new do + + + describe 'when not using ajax' do + + it 'renders the new template' do + get :new + expect(response).to render_template('new') + end + + end + + describe 'when using ajax' do + + it 'renders the new form partial' do + xhr :get, :new + expect(response).to render_template('new_form') + end + end + + it 'creates a new holiday' do + get :new + assigns[:holiday].should be_instance_of(Holiday) + end + + end + + describe :create do + + before do + @holiday_params = { :description => "New Year's Day", + 'day(1i)' => '2010', + 'day(2i)' => '1', + 'day(3i)' => '1' } + post :create, :holiday => @holiday_params + end + + it 'creates a new holiday' do + assigns(:holiday).description.should == @holiday_params[:description] + assigns(:holiday).day.should == Date.new(2010, 1, 1) + assigns(:holiday).should be_persisted + end + + it 'shows the admin a success message' do + flash[:notice].should == 'Holiday successfully created.' + end + + it 'redirects to the index' do + response.should redirect_to admin_holidays_path + end + + context 'when there are errors' do + + before do + Holiday.any_instance.stub(:save).and_return(false) + post :create, :holiday => @holiday_params + end + + it 'renders the new template' do + expect(response).to render_template('new') + end + end + + end + + describe :edit do + + before do + @holiday = FactoryGirl.create(:holiday) + end + + describe 'when not using ajax' do + + it 'renders the edit template' do + get :edit, :id => @holiday.id + expect(response).to render_template('edit') + end + + end + + describe 'when using ajax' do + + it 'renders the edit form partial' do + xhr :get, :edit, :id => @holiday.id + expect(response).to render_template('edit_form') + end + + end + + it 'gets the holiday in the id param' do + get :edit, :id => @holiday.id + assigns[:holiday].should == @holiday + end + + end + + describe :update do + + before do + @holiday = FactoryGirl.create(:holiday, :day => Date.new(2010, 1, 1), + :description => "Test Holiday") + put :update, :id => @holiday.id, :holiday => { :description => 'New Test Holiday' } + end + + it 'gets the holiday in the id param' do + assigns[:holiday].should == @holiday + end + + it 'updates the holiday' do + holiday = Holiday.find(@holiday.id).description.should == 'New Test Holiday' + end + + it 'shows the admin a success message' do + flash[:notice].should == 'Holiday successfully updated.' + end + + it 'redirects to the index' do + response.should redirect_to admin_holidays_path + end + + context 'when there are errors' do + + before do + Holiday.any_instance.stub(:update_attributes).and_return(false) + put :update, :id => @holiday.id, :holiday => { :description => 'New Test Holiday' } + end + + it 'renders the edit template' do + expect(response).to render_template('edit') + end + end + + end + + describe :destroy do + + before(:each) do + @holiday = FactoryGirl.create(:holiday) + delete :destroy, :id => @holiday.id + end + + it 'finds the holiday to destroy' do + assigns(:holiday).should == @holiday + end + + it 'destroys the holiday' do + assigns(:holiday).should be_destroyed + end + + it 'tells the admin the holiday has been destroyed' do + msg = "Holiday successfully destroyed" + flash[:notice].should == msg + end + + it 'redirects to the index action' do + expect(response).to redirect_to(admin_holidays_path) + end + end + + end diff --git a/spec/controllers/admin_incoming_message_controller_spec.rb b/spec/controllers/admin_incoming_message_controller_spec.rb index 21c744e5b..24a526ca4 100644 --- a/spec/controllers/admin_incoming_message_controller_spec.rb +++ b/spec/controllers/admin_incoming_message_controller_spec.rb @@ -17,19 +17,19 @@ describe AdminIncomingMessageController, "when administering incoming messages" it "destroys the raw email file" do raw_email = @im.raw_email.filepath assert_equal File.exists?(raw_email), true - post :destroy, :incoming_message_id => @im.id + post :destroy, :id => @im.id assert_equal File.exists?(raw_email), false end it 'asks the incoming message to fully destroy itself' do IncomingMessage.stub!(:find).and_return(@im) @im.should_receive(:fully_destroy) - post :destroy, :incoming_message_id => @im.id + post :destroy, :id => @im.id end it 'expires the file cache for the associated info_request' do @controller.should_receive(:expire_for_request).with(@im.info_request) - post :destroy, :incoming_message_id => @im.id + post :destroy, :id => @im.id end end @@ -46,7 +46,7 @@ describe AdminIncomingMessageController, "when administering incoming messages" destination_info_request = info_requests(:naughty_chicken_request) incoming_message = incoming_messages(:useless_incoming_message) @controller.should_receive(:expire_for_request).with(current_info_request) - post :redeliver, :redeliver_incoming_message_id => incoming_message.id, + post :redeliver, :id => incoming_message.id, :url_title => destination_info_request.url_title end @@ -56,7 +56,7 @@ describe AdminIncomingMessageController, "when administering incoming messages" current_info_request = info_requests(:fancy_dog_request) destination_info_request = info_requests(:naughty_chicken_request) incoming_message = incoming_messages(:useless_incoming_message) - post :redeliver, :redeliver_incoming_message_id => incoming_message.id, + post :redeliver, :id => incoming_message.id, :url_title => destination_info_request.url_title end @@ -130,7 +130,7 @@ describe AdminIncomingMessageController, "when administering incoming messages" it 'should redirect to the admin info request view' do make_request - response.should redirect_to admin_request_show_url(@incoming.info_request) + response.should redirect_to admin_request_url(@incoming.info_request) end it 'should show a message that the incoming message has been updated' do diff --git a/spec/controllers/admin_info_request_event_controller_spec.rb b/spec/controllers/admin_info_request_event_controller_spec.rb new file mode 100644 index 000000000..23300a0b8 --- /dev/null +++ b/spec/controllers/admin_info_request_event_controller_spec.rb @@ -0,0 +1,41 @@ +# coding: utf-8 +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe AdminInfoRequestEventController do + + describe :update do + + describe 'when handling valid data' do + + before do + @info_request_event = FactoryGirl.create(:info_request_event) + put :update, :id => @info_request_event + end + + it 'gets the info request event' do + assigns[:info_request_event].should == @info_request_event + end + + it 'sets the described and calculated states on the event' do + event = InfoRequestEvent.find(@info_request_event.id) + event.described_state.should == 'waiting_clarification' + event.calculated_state.should == 'waiting_clarification' + end + + it 'shows a success notice' do + flash[:notice].should == 'Old response marked as having been a clarification' + end + + it 'redirects to the request admin page' do + response.should redirect_to(admin_request_url(@info_request_event.info_request)) + end + end + + it 'raises an exception if the event is not a response' do + @info_request_event = FactoryGirl.create(:sent_event) + lambda{ put :update, :id => @info_request_event }.should raise_error + end + + end + +end diff --git a/spec/controllers/admin_outgoing_message_controller_spec.rb b/spec/controllers/admin_outgoing_message_controller_spec.rb index 0dde53b86..a46a077da 100644 --- a/spec/controllers/admin_outgoing_message_controller_spec.rb +++ b/spec/controllers/admin_outgoing_message_controller_spec.rb @@ -79,7 +79,7 @@ describe AdminOutgoingMessageController do it 'should redirect to the admin info request view' do make_request - response.should redirect_to admin_request_show_url(@info_request) + response.should redirect_to admin_request_url(@info_request) end it 'should show a message that the incoming message has been updated' do diff --git a/spec/controllers/admin_public_body_controller_spec.rb b/spec/controllers/admin_public_body_controller_spec.rb index 095d23245..7de292303 100644 --- a/spec/controllers/admin_public_body_controller_spec.rb +++ b/spec/controllers/admin_public_body_controller_spec.rb @@ -272,7 +272,7 @@ describe AdminPublicBodyController, "when destroying a public body" do it "destroys a public body" do n = PublicBody.count post :destroy, { :id => public_bodies(:forlorn_public_body).id } - response.should redirect_to(:controller=>'admin_public_body', :action=>'list') + response.should redirect_to admin_bodies_path PublicBody.count.should == n - 1 end @@ -286,7 +286,7 @@ describe AdminPublicBodyController, "when assigning public body tags" do n = PublicBody.joins(:translations).where([condition, "en"]).count post :mass_tag_add, { :new_tag => "department", :table_name => "substring" } request.flash[:notice].should == "Added tag to table of bodies." - response.should redirect_to(:action=>'list') + response.should redirect_to admin_bodies_path PublicBody.find_by_tag("department").count.should == n end end diff --git a/spec/controllers/admin_raw_email_controller_spec.rb b/spec/controllers/admin_raw_email_controller_spec.rb new file mode 100644 index 000000000..77c57c38b --- /dev/null +++ b/spec/controllers/admin_raw_email_controller_spec.rb @@ -0,0 +1,30 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe AdminRawEmailController do + + describe :show do + + before do + @raw_email = FactoryGirl.create(:incoming_message).raw_email + end + + describe 'html version' do + + it 'renders the show template' do + get :show, :id => @raw_email.id + end + + end + + describe 'text version' do + + it 'sends the email as an RFC-822 attachment' do + get :show, :id => @raw_email.id, :format => 'txt' + response.content_type.should == 'message/rfc822' + response.body.should == @raw_email.data + end + end + + end + +end diff --git a/spec/controllers/admin_request_controller_spec.rb b/spec/controllers/admin_request_controller_spec.rb index 7c5253f49..4eb463963 100644 --- a/spec/controllers/admin_request_controller_spec.rb +++ b/spec/controllers/admin_request_controller_spec.rb @@ -57,12 +57,12 @@ describe AdminRequestController, "when administering requests" do it 'expires the file cache for that request' do info_request = info_requests(:badger_request) @controller.should_receive(:expire_for_request).with(info_request) - get :fully_destroy, { :id => info_request } + get :destroy, { :id => info_request } end it 'uses a different flash message to avoid trying to fetch a non existent user record' do info_request = info_requests(:external_request) - post :fully_destroy, { :id => info_request.id } + post :destroy, { :id => info_request.id } request.flash[:notice].should include('external') end @@ -77,34 +77,6 @@ describe AdminRequestController, "when administering the holding pen" do load_raw_emails_data end - it "shows a rejection reason for an incoming message from an invalid address" do - ir = info_requests(:fancy_dog_request) - ir.allow_new_responses_from = 'authority_only' - ir.handle_rejected_responses = 'holding_pen' - ir.save! - receive_incoming_mail('incoming-request-plain.email', ir.incoming_email, "frob@nowhere.com") - get :show_raw_email, :id => InfoRequest.holding_pen_request.get_last_public_response.raw_email.id - response.should contain "Only the authority can reply to this request" - end - - it "guesses a misdirected request" do - ir = info_requests(:fancy_dog_request) - ir.handle_rejected_responses = 'holding_pen' - ir.allow_new_responses_from = 'authority_only' - ir.save! - mail_to = "request-#{ir.id}-asdfg@example.com" - receive_incoming_mail('incoming-request-plain.email', mail_to) - interesting_email = InfoRequest.holding_pen_request.get_last_public_response.raw_email.id - # now we add another message to the queue, which we're not interested in - receive_incoming_mail('incoming-request-plain.email', ir.incoming_email, "") - InfoRequest.holding_pen_request.incoming_messages.length.should == 2 - get :show_raw_email, :id => interesting_email - response.should contain "Could not identify the request" - assigns[:info_requests][0].should == ir - end - - - it "shows a suitable default 'your email has been hidden' message" do ir = info_requests(:fancy_dog_request) get :show, :id => ir.id @@ -119,7 +91,7 @@ describe AdminRequestController, "when administering the holding pen" do it "hides requests and sends a notification email that it has done so" do ir = info_requests(:fancy_dog_request) - post :hide_request, :id => ir.id, :explanation => "Foo", :reason => "vexatious" + post :hide, :id => ir.id, :explanation => "Foo", :reason => "vexatious" ir.reload ir.prominence.should == "requester_only" ir.described_state.should == "vexatious" @@ -132,7 +104,7 @@ describe AdminRequestController, "when administering the holding pen" do it 'expires the file cache for the request' do ir = info_requests(:fancy_dog_request) @controller.should_receive(:expire_for_request).with(ir) - post :hide_request, :id => ir.id, :explanation => "Foo", :reason => "vexatious" + post :hide, :id => ir.id, :explanation => "Foo", :reason => "vexatious" end describe 'when hiding an external request' do @@ -153,7 +125,7 @@ describe AdminRequestController, "when administering the holding pen" do end def make_request(params=@default_params) - post :hide_request, params + post :hide, params end it 'should redirect the the admin page for the request' do diff --git a/spec/controllers/admin_track_controller_spec.rb b/spec/controllers/admin_track_controller_spec.rb index f2de6c0d3..d29db4966 100644 --- a/spec/controllers/admin_track_controller_spec.rb +++ b/spec/controllers/admin_track_controller_spec.rb @@ -1,9 +1,8 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe AdminTrackController, "when administering tracks" do - render_views - - it "shows the list page" do - get :list + + it "shows the index page" do + get :index end end diff --git a/spec/controllers/admin_user_controller_spec.rb b/spec/controllers/admin_user_controller_spec.rb index 8b89506f9..e979355cf 100644 --- a/spec/controllers/admin_user_controller_spec.rb +++ b/spec/controllers/admin_user_controller_spec.rb @@ -2,13 +2,13 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe AdminUserController, "when administering users" do render_views - - it "shows the index/list page" do + + it "shows the index page" do get :index end it "searches for 'bob'" do - get :list, :query => "bob" + get :index, :query => "bob" assigns[:admin_users].should == [ users(:bob_smith_user) ] end @@ -51,7 +51,7 @@ describe AdminUserController do before(:each) do @user = FactoryGirl.create(:user) - request.env["HTTP_REFERER"] = admin_user_show_path(@user) + request.env["HTTP_REFERER"] = admin_user_path(@user) end it 'redirects to the page the admin was previously on' do @@ -61,7 +61,7 @@ describe AdminUserController do :comment_ids => comment.id, :hide_selected => 'hidden' } - response.should redirect_to(admin_user_show_path(@user)) + response.should redirect_to(admin_user_path(@user)) end it 'sets the given comments visibility to hidden' do diff --git a/spec/controllers/general_controller_spec.rb b/spec/controllers/general_controller_spec.rb index cb00b301c..28dac7b96 100644 --- a/spec/controllers/general_controller_spec.rb +++ b/spec/controllers/general_controller_spec.rb @@ -138,6 +138,35 @@ describe GeneralController, "when showing the frontpage" do end + describe 'when handling logged-in users' do + + before do + @user = FactoryGirl.create(:user) + session[:user_id] = @user.id + end + + it 'should set a time to live on a non "remember me" session' do + get :frontpage + response.body.should match @user.name + session[:ttl].should be_within(1).of(Time.now) + end + + it 'should not set a time to live on a "remember me" session' do + session[:remember_me] = true + get :frontpage + response.body.should match @user.name + session[:ttl].should be_nil + end + + it 'should end a logged-in session whose ttl has expired' do + session[:ttl] = Time.now - 4.hours + get :frontpage + response.should redirect_to signin_path + session[:user_id].should be_nil + end + + end + end diff --git a/spec/controllers/public_body_change_requests_controller_spec.rb b/spec/controllers/public_body_change_requests_controller_spec.rb index 8fe7befeb..4053b2f40 100644 --- a/spec/controllers/public_body_change_requests_controller_spec.rb +++ b/spec/controllers/public_body_change_requests_controller_spec.rb @@ -28,6 +28,7 @@ describe PublicBodyChangeRequestsController, "creating a change request" do it "should send an email to the site contact address" do post :create, {:public_body_change_request => @change_request_params} + change_request_id = assigns[:change_request].id deliveries = ActionMailer::Base.deliveries deliveries.size.should == 1 mail = deliveries[0] @@ -37,8 +38,8 @@ describe PublicBodyChangeRequestsController, "creating a change request" do mail.body.should include('new_body@example.com') mail.body.should include('New Body') mail.body.should include("Please") - mail.body.should include('http://test.host/admin/body/new?change_request_id=') - mail.body.should include('http://test.host/admin/change_request/edit/') + mail.body.should include("http://test.host/admin/bodies/new?change_request_id=#{change_request_id}") + mail.body.should include("http://test.host/admin/change_requests/#{change_request_id}/edit") end it 'should show a notice' do @@ -83,6 +84,7 @@ describe PublicBodyChangeRequestsController, "creating a change request" do it 'should send an email to the site contact address' do post :create, {:public_body_change_request => @change_request_params} + change_request_id = assigns[:change_request].id deliveries = ActionMailer::Base.deliveries deliveries.size.should == 1 mail = deliveries[0] @@ -92,8 +94,8 @@ describe PublicBodyChangeRequestsController, "creating a change request" do mail.body.should include('new_body@example.com') mail.body.should include(@public_body.name) mail.body.should include("Please") - mail.body.should include("http://test.host/admin/body/edit/#{@public_body.id}?change_request_id=") - mail.body.should include('http://test.host/admin/change_request/edit/') + mail.body.should include("http://test.host/admin/bodies/#{@public_body.id}/edit?change_request_id=#{change_request_id}") + mail.body.should include("http://test.host/admin/change_requests/#{change_request_id}/edit") end it 'should show a notice' do diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb index 26e46a966..2d3ccfa63 100644 --- a/spec/controllers/request_controller_spec.rb +++ b/spec/controllers/request_controller_spec.rb @@ -2392,6 +2392,23 @@ describe RequestController, "when doing type ahead searches" do get :search_typeahead, :q => "dog -chicken" assigns[:xapian_requests].results.size.should == 1 end + + it 'can filter search results by public body' do + get :search_typeahead, :q => 'boring', :requested_from => 'dfh' + expect(assigns[:query]).to eq('requested_from:dfh boring') + end + + it 'defaults to 25 results per page' do + get :search_typeahead, :q => 'boring' + expect(assigns[:per_page]).to eq(25) + end + + it 'can limit the number of searches returned' do + get :search_typeahead, :q => 'boring', :per_page => '1' + expect(assigns[:per_page]).to eq(1) + expect(assigns[:xapian_requests].results.size).to eq(1) + end + end describe RequestController, "when showing similar requests" do @@ -2442,7 +2459,7 @@ describe RequestController, "when caching fragments" do :info_request_id => 132, :id => 44, :get_attachments_for_display => nil, - :html_mask_stuff! => nil, + :apply_masks! => nil, :user_can_view? => true, :all_can_view? => true) attachment = FactoryGirl.build(:body_text, :filename => long_name) @@ -2655,7 +2672,7 @@ describe RequestController, "#select_authorities" do end - context 'when asked for JSON', :focus => true do + context 'when asked for JSON' do it 'should be successful' do get :select_authorities, {:public_body_query => "Quan", :format => 'json'}, {:user_id => @user.id} diff --git a/spec/controllers/services_controller_spec.rb b/spec/controllers/services_controller_spec.rb index 14731f090..248c97ad4 100644 --- a/spec/controllers/services_controller_spec.rb +++ b/spec/controllers/services_controller_spec.rb @@ -60,21 +60,25 @@ describe ServicesController, "when returning a message for people in other count response.should be_success response.body.should == 'Hello! We have an <a href="/help/alaveteli?country_name=Deutschland">important message</a> for visitors outside Deutschland' end + it "should default to no message if the country_from_ip domain doesn't exist" do AlaveteliConfiguration.stub!(:gaze_url).and_return('http://12123sdf14qsd.com') get :other_country_message response.should be_success response.body.should == '' end + it "should default to no message if the country_from_ip service doesn't exist" do AlaveteliConfiguration.stub!(:gaze_url).and_return('http://www.google.com') get :other_country_message response.should be_success response.body.should == '' end - it "should default to no message if the country_from_ip service returns an error" do + + it "should default to no message and log the error with url if the country_from_ip service returns an error" do FakeWeb.register_uri(:get, %r|500.com|, :body => "Error", :status => ["500", "Error"]) AlaveteliConfiguration.stub!(:gaze_url).and_return('http://500.com') + Rails.logger.should_receive(:warn).with /500\.com.*500 Error/ get :other_country_message response.should be_success response.body.should == '' diff --git a/spec/factories/holidays.rb b/spec/factories/holidays.rb new file mode 100644 index 000000000..531130c8a --- /dev/null +++ b/spec/factories/holidays.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + + factory :holiday do + day Date.new(2010, 1, 1) + description "New Year's Day" + end + +end diff --git a/spec/factories/incoming_messages.rb b/spec/factories/incoming_messages.rb index ec0afdcd0..b29fe8ce9 100644 --- a/spec/factories/incoming_messages.rb +++ b/spec/factories/incoming_messages.rb @@ -10,6 +10,9 @@ FactoryGirl.define do FactoryGirl.create(:body_text, :incoming_message => incoming_message, :url_part_number => 1) + + incoming_message.raw_email.incoming_message = incoming_message + incoming_message.raw_email.data = "somedata" end factory :plain_incoming_message do diff --git a/spec/factories/info_request_events.rb b/spec/factories/info_request_events.rb new file mode 100644 index 000000000..cdd303ad6 --- /dev/null +++ b/spec/factories/info_request_events.rb @@ -0,0 +1,12 @@ +FactoryGirl.define do + + factory :info_request_event do + info_request + event_type 'response' + params_yaml '' + factory :sent_event do + event_type 'sent' + end + end + +end diff --git a/spec/fixtures/files/ical-holidays.ics b/spec/fixtures/files/ical-holidays.ics new file mode 100644 index 000000000..6ccf31202 --- /dev/null +++ b/spec/fixtures/files/ical-holidays.ics @@ -0,0 +1,22 @@ +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:PUBLISH +PRODID:-//uk.gov/GOVUK calendars//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +DTEND;VALUE=DATE:20140102 +DTSTART;VALUE=DATE:20140101 +SUMMARY:New Year's Day +UID:ca6af7456b0088abad9a69f9f620f5ac-17@gov.uk +SEQUENCE:0 +DTSTAMP:20140916T090346Z +END:VEVENT +BEGIN:VEVENT +DTEND;VALUE=DATE:20150102 +DTSTART;VALUE=DATE:20150101 +SUMMARY:New Year's Day +UID:ca6af7456b00a69f9f620f5ac-17@gov.uk +SEQUENCE:0 +DTSTAMP:20140916T090346Z +END:VEVENT +END:VCALENDAR diff --git a/spec/integration/admin_spec.rb b/spec/integration/admin_spec.rb index 8e6351d2c..bdd6e9d8c 100644 --- a/spec/integration/admin_spec.rb +++ b/spec/integration/admin_spec.rb @@ -5,29 +5,25 @@ describe "When administering the site" do before do AlaveteliConfiguration.stub!(:skip_admin_auth).and_return(false) + confirm(:admin_user) + @admin = login(:admin_user) end it "allows an admin to log in as another user" do - # First log in as Joe Admin - confirm(:admin_user) - admin = login(:admin_user) - - # Now fetch the "log in as" link to log in as Bob - admin.get_via_redirect "/en/admin/user/login_as/#{users(:bob_smith_user).id}" - admin.response.should be_success - admin.session[:user_id].should == users(:bob_smith_user).id + # post to the "log in as" url to log in as Bob + @admin.post_via_redirect "/en/admin/users/#{users(:bob_smith_user).id}/login_as" + @admin.response.should be_success + @admin.session[:user_id].should == users(:bob_smith_user).id end it 'does not allow a non-admin user to login as another user' do robin = login(:robin_user) - robin.get_via_redirect "/en/admin/user/login_as/#{users(:bob_smith_user).id}" + robin.post_via_redirect "/en/admin/users/#{users(:bob_smith_user).id}/login_as" robin.response.should be_success robin.session[:user_id].should_not == users(:bob_smith_user).id end it "allows redelivery of an incoming message to a closed request" do - confirm(:admin_user) - admin = login(:admin_user) ir = info_requests(:fancy_dog_request) close_request(ir) InfoRequest.holding_pen_request.incoming_messages.length.should == 0 @@ -36,10 +32,9 @@ describe "When administering the site" do InfoRequest.holding_pen_request.incoming_messages.length.should == 1 new_im = InfoRequest.holding_pen_request.incoming_messages[0] ir.incoming_messages.length.should == 1 - post_params = {'redeliver_incoming_message_id' => new_im.id, - 'url_title' => ir.url_title} - admin.post '/en/admin/incoming/redeliver', post_params - admin.response.location.should == 'http://www.example.com/en/admin/request/show/101' + post_params = { 'url_title' => ir.url_title } + @admin.post "/en/admin/incoming_messages/#{new_im.id}/redeliver", post_params + @admin.response.location.should == 'http://www.example.com/en/admin/requests/101' ir = InfoRequest.find_by_url_title(ir.url_title) ir.incoming_messages.length.should == 2 @@ -47,8 +42,6 @@ describe "When administering the site" do end it "allows redelivery of an incoming message to more than one request" do - confirm(:admin_user) - admin = login(:admin_user) ir1 = info_requests(:fancy_dog_request) close_request(ir1) @@ -60,15 +53,41 @@ describe "When administering the site" do InfoRequest.holding_pen_request.incoming_messages.length.should == 1 new_im = InfoRequest.holding_pen_request.incoming_messages[0] - post_params = {'redeliver_incoming_message_id' => new_im.id, - 'url_title' => "#{ir1.url_title},#{ir2.url_title}"} - admin.post '/en/admin/incoming/redeliver', post_params + post_params = { 'url_title' => "#{ir1.url_title},#{ir2.url_title}" } + @admin.post "/en/admin/incoming_messages/#{new_im.id}/redeliver", post_params ir1.reload ir1.incoming_messages.length.should == 2 ir2.reload ir2.incoming_messages.length.should == 2 - admin.response.location.should == 'http://www.example.com/en/admin/request/show/106' + @admin.response.location.should == 'http://www.example.com/en/admin/requests/106' InfoRequest.holding_pen_request.incoming_messages.length.should == 0 end + describe 'when administering the holding pen' do + + it "shows a rejection reason for an incoming message from an invalid address" do + ir = FactoryGirl.create(:info_request, :allow_new_responses_from => 'authority_only', + :handle_rejected_responses => 'holding_pen') + receive_incoming_mail('incoming-request-plain.email', ir.incoming_email, "frob@nowhere.com") + raw_email = InfoRequest.holding_pen_request.get_last_public_response.raw_email + @admin.get "/en/admin/raw_emails/#{raw_email.id}" + @admin.response.should contain "Only the authority can reply to this request" + end + + it "guesses a misdirected request" do + ir = FactoryGirl.create(:info_request, :allow_new_responses_from => 'authority_only', + :handle_rejected_responses => 'holding_pen') + mail_to = "request-#{ir.id}-asdfg@example.com" + receive_incoming_mail('incoming-request-plain.email', mail_to) + interesting_email = InfoRequest.holding_pen_request.get_last_public_response.raw_email + # now we add another message to the queue, which we're not interested in + receive_incoming_mail('incoming-request-plain.email', ir.incoming_email, "") + InfoRequest.holding_pen_request.incoming_messages.length.should == 2 + @admin.get "/en/admin/raw_emails/#{interesting_email.id}" + @admin.response.should contain "Could not identify the request" + @admin.response.should contain ir.title + end + + + end end diff --git a/spec/integration/download_request_spec.rb b/spec/integration/download_request_spec.rb index 638198cde..48b42b11d 100644 --- a/spec/integration/download_request_spec.rb +++ b/spec/integration/download_request_spec.rb @@ -56,7 +56,7 @@ describe 'when making a zipfile available' do admin = login(FactoryGirl.create(:admin_user)) post_data = {:incoming_message => {:prominence => 'requester_only', :prominence_reason => 'boring'}} - admin.post_via_redirect "/en/admin/incoming/update/#{info_request.incoming_messages.first.id}", post_data + admin.put_via_redirect "/en/admin/incoming_messages/#{info_request.incoming_messages.first.id}", post_data admin.response.should be_success # Admin retains the requester only things @@ -104,7 +104,7 @@ describe 'when making a zipfile available' do post_data = {:outgoing_message => {:prominence => 'requester_only', :prominence_reason => 'boring', :body => 'Some information please'}} - admin.post_via_redirect "/en/admin/outgoing/update/#{info_request.outgoing_messages.first.id}", post_data + admin.put_via_redirect "/en/admin/outgoing_messages/#{info_request.outgoing_messages.first.id}", post_data admin.response.should be_success # Admin retains the requester only things @@ -237,7 +237,7 @@ describe 'when making a zipfile available' do admin = login(FactoryGirl.create(:admin_user)) post_data = {:incoming_message => {:prominence => 'requester_only', :prominence_reason => 'boring'}} - admin.post_via_redirect "/en/admin/incoming/update/#{info_request.incoming_messages.first.id}", post_data + admin.put_via_redirect "/en/admin/incoming_messages/#{info_request.incoming_messages.first.id}", post_data admin.response.should be_success # Admin retains the requester only things @@ -285,7 +285,7 @@ describe 'when making a zipfile available' do post_data = {:outgoing_message => {:prominence => 'requester_only', :prominence_reason => 'boring', :body => 'Some information please'}} - admin.post_via_redirect "/en/admin/outgoing/update/#{info_request.outgoing_messages.first.id}", post_data + admin.put_via_redirect "/en/admin/outgoing_messages/#{info_request.outgoing_messages.first.id}", post_data admin.response.should be_success # Admin retains the requester only things diff --git a/spec/integration/errors_spec.rb b/spec/integration/errors_spec.rb index 4fa12fb21..39f1279ce 100644 --- a/spec/integration/errors_spec.rb +++ b/spec/integration/errors_spec.rb @@ -59,7 +59,6 @@ describe "When errors occur" do response.should render_template('general/exception_caught') response.code.should == '404' response.body.should match("Sorry, we couldn't find that page") - response.body.should match(%Q(invalid value for Integer)) end # it 'should handle non utf-8 parameters' do @@ -76,7 +75,6 @@ describe "When errors occur" do InfoRequest.stub!(:find_by_url_title!).and_raise("An example error") get("/request/example") response.should render_template('general/exception_caught') - response.body.should match('An example error') response.code.should == "500" end @@ -111,7 +109,6 @@ describe "When errors occur" do get("/es/request/example") response.should render_template('general/exception_caught') response.body.should match('Lo sentimos, hubo un problema procesando esta página') - response.body.should match('An example error') end it "should render a 403 with text body for attempts at directory listing for attachments" do @@ -136,7 +133,7 @@ describe "When errors occur" do it 'should show a full trace for general errors' do InfoRequest.stub!(:find).and_raise("An example error") - get("/admin/request/show/333") + get("/admin/requests/333") response.body.should have_selector('div[id=traces]') response.body.should match('An example error') end diff --git a/spec/integration/view_request_spec.rb b/spec/integration/view_request_spec.rb index eecb984f5..4d04c97d7 100644 --- a/spec/integration/view_request_spec.rb +++ b/spec/integration/view_request_spec.rb @@ -33,7 +33,7 @@ describe "When viewing requests" do # Admin makes the incoming message requester only post_data = {:incoming_message => {:prominence => 'hidden', :prominence_reason => 'boring'}} - admin.post_via_redirect "/admin/incoming/update/#{info_request.incoming_messages.first.id}", post_data + admin.put_via_redirect "/admin/incoming_messages/#{info_request.incoming_messages.first.id}", post_data admin.response.should be_success cache_directories_exist?(info_request).should be_false diff --git a/spec/lib/alaveteli_text_masker_spec.rb b/spec/lib/alaveteli_text_masker_spec.rb new file mode 100644 index 000000000..1a4782a83 --- /dev/null +++ b/spec/lib/alaveteli_text_masker_spec.rb @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe AlaveteliTextMasker do + include AlaveteliTextMasker + + describe :apply_masks! do + + describe 'when applying censor rules' do + + before do + @cheese_censor_rule = FactoryGirl.build(:censor_rule, :text => 'Stilton', + :replacement => 'Jarlsberg') + @colour_censor_rule = FactoryGirl.build(:censor_rule, :text => 'blue', + :replacement => 'yellow') + @regex_censor_rule = FactoryGirl.build(:censor_rule, :text => 'm[a-z][a-z][a-z]e', + :replacement => 'cat', + :regexp => true) + @censor_rules = [@cheese_censor_rule, @colour_censor_rule, @regex_censor_rule] + end + + it "should do nothing to a JPEG" do + data = "There was a mouse called Stilton, he wished that he was blue." + apply_masks!(data, "image/jpeg", :censor_rules => @censor_rules) + data.should == "There was a mouse called Stilton, he wished that he was blue." + end + + it "should replace censor text in Word documents" do + data = "There was a mouse called Stilton, he wished that he was blue." + apply_masks!(data, "application/vnd.ms-word", :censor_rules => @censor_rules) + data.should == "There was a xxxxx called xxxxxxx, he wished that he was xxxx." + end + + it 'should handle multibyte characters correctly' do + data = 'á mouse' + @regex_censor_rule.text = 'á' + apply_masks!(data, "application/octet-stream", :censor_rules => @censor_rules).should == 'x mouse' + end + + it "should apply censor rules to HTML files" do + data = "There was a mouse called Stilton, he wished that he was blue." + apply_masks!(data, 'text/html', :censor_rules => @censor_rules) + data.should == "There was a cat called Jarlsberg, he wished that he was yellow." + end + + end + + it "should replace ASCII email addresses in Word documents" do + data = "His email was foo@bar.com" + expected = "His email was xxx@xxx.xxx" + apply_masks!(data, "application/vnd.ms-word") + data.should == expected + end + + + it "should replace UCS-2 addresses in Word documents" do + data = "His email was f\000o\000o\000@\000b\000a\000r\000.\000c\000o\000m\000, indeed" + apply_masks!(data, "application/vnd.ms-word") + data.should == "His email was x\000x\000x\000@\000x\000x\000x\000.\000x\000x\000x\000, indeed" + end + + def pdf_replacement_test(use_ghostscript_compression) + config = MySociety::Config.load_default() + previous = config['USE_GHOSTSCRIPT_COMPRESSION'] + config['USE_GHOSTSCRIPT_COMPRESSION'] = use_ghostscript_compression + orig_pdf = load_file_fixture('tfl.pdf') + pdf = orig_pdf.dup + + orig_text = MailHandler.get_attachment_text_one_file('application/pdf', pdf) + orig_text.should match(/foi@tfl.gov.uk/) + + apply_masks!(pdf, "application/pdf") + + masked_text = MailHandler.get_attachment_text_one_file('application/pdf', pdf) + masked_text.should_not match(/foi@tfl.gov.uk/) + masked_text.should match(/xxx@xxx.xxx.xx/) + config['USE_GHOSTSCRIPT_COMPRESSION'] = previous + end + + it "should replace everything in PDF files using pdftk" do + pdf_replacement_test(false) + end + + it "should replace everything in PDF files using ghostscript" do + pdf_replacement_test(true) + end + + it "should not produce zero length output if pdftk silently fails" do + orig_pdf = load_file_fixture('psni.pdf') + pdf = orig_pdf.dup + apply_masks!(pdf, "application/pdf") + pdf.should_not == "" + end + + it "should apply hard-coded privacy rules to HTML files" do + data = "http://test.host/c/cheese" + apply_masks!(data, 'text/html') + data.should == "[Alaveteli login link]" + end + + it 'should replace a simple email address' do + expected = "the address is [email address]" + apply_masks!("the address is test@example.com", 'text/html', {}).should == expected + end + + it 'should replace a mobile phone number prefixed with "Mobile"' do + expected = "the mobile is [mobile number]" + apply_masks!("the mobile is Mobile 55555 555555", 'text/html', {}).should == expected + end + + it 'should replace a mobile phone number prefixed with "Mob Tel"' do + expected = "the mobile is [mobile number]" + apply_masks!("the mobile is Mob Tel: 55555 555 555", 'text/html', {}).should == expected + end + + it 'should replace a mobile phone number prefixed with "Mob/Fax:"' do + expected = "the mobile is [mobile number]" + apply_masks!("the mobile is Mob/Fax: 55555 555555", 'text/html', {}).should == expected + end + + it "should replace an Alaveteli login link" do + expected = "the login link is [Alaveteli login link]" + apply_masks!("the login link is http://test.host/c/ekfmsdfkm", 'text/html', {}).should == expected + end + + it "should replace a https Alaveteli login link" do + expected = "the login link is [Alaveteli login link]" + apply_masks!("the login link is https://test.host/c/ekfmsdfkm", 'text/html', {}).should == expected + end + + it "should apply censor rules to text" do + censor_rule = FactoryGirl.build(:censor_rule, :text => 'mouse', :replacement => 'cat') + expected = "here is a cat" + apply_masks!("here is a mouse", 'text/html', {:censor_rules => [ censor_rule ]}).should == expected + end + + it 'should apply extra masks to text' do + mask = {:to_replace => 'mouse', :replacement => 'cat'} + expected = "here is a cat" + apply_masks!("here is a mouse", 'text/html', {:masks => [ mask ]}).should == expected + end + + end + +end + diff --git a/spec/mailers/outgoing_mailer_spec.rb b/spec/mailers/outgoing_mailer_spec.rb index a11d56dd3..3df5018fe 100644 --- a/spec/mailers/outgoing_mailer_spec.rb +++ b/spec/mailers/outgoing_mailer_spec.rb @@ -75,14 +75,14 @@ describe OutgoingMailer, "when working out follow up subjects" do ir = info_requests(:fancy_dog_request) im = ir.incoming_messages[0] - ir.email_subject_request.should == "Freedom of Information request - Why do you have & such a fancy dog?" + ir.email_subject_request(:html => false).should == "Freedom of Information request - Why do you have & such a fancy dog?" end it "should use 'Re:' and inital request subject for followups which aren't replies to particular messages" do ir = info_requests(:fancy_dog_request) om = outgoing_messages(:useless_outgoing_message) - OutgoingMailer.subject_for_followup(ir, om).should == "Re: Freedom of Information request - Why do you have & such a fancy dog?" + OutgoingMailer.subject_for_followup(ir, om, :html => false).should == "Re: Freedom of Information request - Why do you have & such a fancy dog?" end it "should prefix with Re: the subject of the message being replied to" do @@ -91,7 +91,7 @@ describe OutgoingMailer, "when working out follow up subjects" do om = outgoing_messages(:useless_outgoing_message) om.incoming_message_followup = im - OutgoingMailer.subject_for_followup(ir, om).should == "Re: Geraldine FOI Code AZXB421" + OutgoingMailer.subject_for_followup(ir, om, :html => false).should == "Re: Geraldine FOI Code AZXB421" end it "should not add Re: prefix if there already is such a prefix" do @@ -101,7 +101,7 @@ describe OutgoingMailer, "when working out follow up subjects" do om.incoming_message_followup = im im.raw_email.data = im.raw_email.data.sub("Subject: Geraldine FOI Code AZXB421", "Subject: Re: Geraldine FOI Code AZXB421") - OutgoingMailer.subject_for_followup(ir, om).should == "Re: Geraldine FOI Code AZXB421" + OutgoingMailer.subject_for_followup(ir, om, :html => false).should == "Re: Geraldine FOI Code AZXB421" end it "should not add Re: prefix if there already is a lower case re: prefix" do @@ -113,7 +113,7 @@ describe OutgoingMailer, "when working out follow up subjects" do im.raw_email.data = im.raw_email.data.sub("Subject: Geraldine FOI Code AZXB421", "Subject: re: Geraldine FOI Code AZXB421") im.parse_raw_email! true - OutgoingMailer.subject_for_followup(ir, om).should == "re: Geraldine FOI Code AZXB421" + OutgoingMailer.subject_for_followup(ir, om, :html => false).should == "re: Geraldine FOI Code AZXB421" end it "should use 'Re:' and initial request subject when replying to failed delivery notifications" do @@ -126,7 +126,7 @@ describe OutgoingMailer, "when working out follow up subjects" do im.raw_email.data = im.raw_email.data.sub("Subject: Geraldine FOI Code AZXB421", "Subject: Delivery Failed") im.parse_raw_email! true - OutgoingMailer.subject_for_followup(ir, om).should == "Re: Freedom of Information request - Why do you have & such a fancy dog?" + OutgoingMailer.subject_for_followup(ir, om, :html => false).should == "Re: Freedom of Information request - Why do you have & such a fancy dog?" end end diff --git a/spec/mailers/request_mailer_spec.rb b/spec/mailers/request_mailer_spec.rb index 8ba2a7bec..9e98dbc00 100644 --- a/spec/mailers/request_mailer_spec.rb +++ b/spec/mailers/request_mailer_spec.rb @@ -427,8 +427,7 @@ describe RequestMailer, 'requires_admin' do it 'body should contain the full admin URL' do mail = RequestMailer.requires_admin(@info_request).deliver - - mail.body.should include('http://test.host/en/admin/request/show/123') + mail.body.should include('http://test.host/en/admin/requests/123') end it "body should contain the message from the user" do diff --git a/spec/models/holiday_import_spec.rb b/spec/models/holiday_import_spec.rb new file mode 100644 index 000000000..21061f63f --- /dev/null +++ b/spec/models/holiday_import_spec.rb @@ -0,0 +1,157 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe HolidayImport do + + it 'validates the presence of a feed if the source is a feed' do + holiday_import = HolidayImport.new(:source => 'feed') + holiday_import.valid?.should be_false + holiday_import.errors[:ical_feed_url].should == ["can't be blank"] + end + + it 'does not validate the presence of a feed if the source is suggestions' do + holiday_import = HolidayImport.new(:source => 'suggestions') + holiday_import.valid?.should be_true + end + + it 'validates that the source is either "feed" or "suggestions"' do + holiday_import = HolidayImport.new(:source => 'something') + holiday_import.valid?.should be_false + holiday_import.errors[:source].should == ["is not included in the list"] + end + + it 'validates that all holidays create from attributes are valid' do + holiday_import = HolidayImport.new(:source => 'suggestions', + :holidays_attributes => {"0" => {:description => '', + "day(1i)"=>"", + "day(2i)"=>"", + "day(3i)"=>""}}) + holiday_import.valid?.should be_false + holiday_import.errors[:base].should == ["These holidays could not be imported"] + end + + it 'validates that all holidays to import are valid' do + holiday_import = HolidayImport.new + holiday_import.holidays = [ Holiday.new ] + holiday_import.valid?.should be_false + holiday_import.errors[:base].should == ['These holidays could not be imported'] + end + + it 'defaults to importing holidays for the current year' do + holiday_import = HolidayImport.new + holiday_import.start_year.should == Time.now.year + holiday_import.end_year.should == Time.now.year + end + + it 'allows the start and end year to be set' do + holiday_import = HolidayImport.new(:start_year => 2011, :end_year => 2012) + holiday_import.start_year.should == 2011 + holiday_import.end_year.should == 2012 + end + + it 'sets the start and end dates to the beginning and end of the year' do + holiday_import = HolidayImport.new(:start_year => 2011, :end_year => 2012) + holiday_import.start_date.should == Date.new(2011, 1, 1) + holiday_import.end_date.should == Date.new(2012, 12, 31) + end + + it 'sets a default source of suggestions' do + holiday_import = HolidayImport.new + holiday_import.source.should == 'suggestions' + end + + it 'allows the source to be set' do + holiday_import = HolidayImport.new(:source => 'feed') + holiday_import.source.should == 'feed' + end + + it 'allows an iCal feed URL to be set' do + holiday_import = HolidayImport.new(:ical_feed_url => 'http://www.example.com') + holiday_import.ical_feed_url.should == 'http://www.example.com' + end + + it 'sets a default populated flag to false' do + holiday_import = HolidayImport.new + holiday_import.populated.should == false + end + + it 'returns a readable description of the period for multiple years' do + HolidayImport.new(:start_year => 2011, :end_year => 2012).period.should == '2011-2012' + end + + it 'returns a readable description of the period for a single year' do + HolidayImport.new(:start_year => 2011, :end_year => 2011).period.should == '2011' + end + + it 'returns the country name for which suggestions are generated' do + HolidayImport.new.suggestions_country_name.should == 'Germany' + end + + describe 'when populating a set of holidays to import from suggestions' do + + before do + holidays = [ { :date => Date.new(2014, 1, 1), :name => "New Year's Day", :regions => [:gb] } ] + Holidays.stub!(:between).and_return(holidays) + @holiday_import = HolidayImport.new(:source => 'suggestions') + @holiday_import.populate + end + + it 'should populate holidays from the suggestions' do + @holiday_import.holidays.size.should == 1 + holiday = @holiday_import.holidays.first + holiday.description.should == "New Year's Day" + holiday.day.should == Date.new(2014, 1, 1) + end + + it 'should return a flag that it has been populated' do + @holiday_import.populated.should == true + end + + end + + describe 'when populating a set of holidays to import from a feed' do + + before do + @holiday_import = HolidayImport.new(:source => 'feed', + :ical_feed_url => 'http://www.example.com', + :start_year => 2014, + :end_year => 2014) + end + + it 'should populate holidays from the feed that are between the dates' do + @holiday_import.stub!(:open).and_return(load_file_fixture('ical-holidays.ics')) + @holiday_import.populate + @holiday_import.holidays.size.should == 1 + holiday = @holiday_import.holidays.first + holiday.description.should == "New Year's Day" + holiday.day.should == Date.new(2014, 1, 1) + end + + it 'should add an error if the calendar cannot be parsed' do + @holiday_import.stub!(:open).and_return('some invalid data') + @holiday_import.populate + expected = ["Sorry, there's a problem with the format of that feed."] + @holiday_import.errors[:ical_feed_url].should == expected + end + + it 'should add an error if the calendar cannot be found' do + @holiday_import.stub!(:open).and_raise Errno::ENOENT.new('No such file or directory') + @holiday_import.populate + expected = ["Sorry we couldn't find that feed."] + @holiday_import.errors[:ical_feed_url].should == expected + end + + end + + describe 'when saving' do + + it 'saves all holidays' do + holiday = Holiday.new + holiday_import = HolidayImport.new + holiday_import.holidays = [ holiday ] + holiday.should_receive(:save) + holiday_import.save + end + + end + +end diff --git a/spec/models/holiday_spec.rb b/spec/models/holiday_spec.rb index 89849abb7..2f8eeabd9 100644 --- a/spec/models/holiday_spec.rb +++ b/spec/models/holiday_spec.rb @@ -9,87 +9,98 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') -describe Holiday, " when calculating due date" do +describe Holiday do - def due_date(ymd) - return Holiday.due_date_from_working_days(Date.strptime(ymd), 20).strftime("%F") - end + describe :new do - context "in working days" do - it "handles no holidays" do - due_date('2008-10-01').should == '2008-10-29' + it 'should require a day' do + holiday = Holiday.new + holiday.valid?.should be_false + holiday.errors[:day].should == ["can't be blank"] end + end - it "handles non leap years" do - due_date('2007-02-01').should == '2007-03-01' - end + describe " when calculating due date" do - it "handles leap years" do - due_date('2008-02-01').should == '2008-02-29' + def due_date(ymd) + return Holiday.due_date_from_working_days(Date.strptime(ymd), 20).strftime("%F") end - it "handles Thursday start" do - due_date('2009-03-12').should == '2009-04-14' - end + context "in working days" do + it "handles no holidays" do + due_date('2008-10-01').should == '2008-10-29' + end - it "handles Friday start" do - due_date('2009-03-13').should == '2009-04-15' - end + it "handles non leap years" do + due_date('2007-02-01').should == '2007-03-01' + end - # Delivery at the weekend ends up the same due day as if it had arrived on - # the Friday before. This is because the next working day (Monday) counts - # as day 1. - # See http://www.whatdotheyknow.com/help/officers#days - it "handles Saturday start" do - due_date('2009-03-14').should == '2009-04-15' - end - it "handles Sunday start" do - due_date('2009-03-15').should == '2009-04-15' - end + it "handles leap years" do + due_date('2008-02-01').should == '2008-02-29' + end - it "handles Monday start" do - due_date('2009-03-16').should == '2009-04-16' - end + it "handles Thursday start" do + due_date('2009-03-12').should == '2009-04-14' + end - it "handles Time objects" do - Holiday.due_date_from_working_days(Time.utc(2009, 03, 16, 12, 0, 0), 20).strftime('%F').should == '2009-04-16' - end - end + it "handles Friday start" do + due_date('2009-03-13').should == '2009-04-15' + end - context "in calendar days" do - it "handles no holidays" do - Holiday.due_date_from_calendar_days(Date.new(2008, 10, 1), 20).should == Date.new(2008, 10, 21) - end + # Delivery at the weekend ends up the same due day as if it had arrived on + # the Friday before. This is because the next working day (Monday) counts + # as day 1. + # See http://www.whatdotheyknow.com/help/officers#days + it "handles Saturday start" do + due_date('2009-03-14').should == '2009-04-15' + end + it "handles Sunday start" do + due_date('2009-03-15').should == '2009-04-15' + end - it "handles the due date falling on a Friday" do - Holiday.due_date_from_calendar_days(Date.new(2008, 10, 4), 20).should == Date.new(2008, 10, 24) - end + it "handles Monday start" do + due_date('2009-03-16').should == '2009-04-16' + end - # If the due date would fall on a Saturday it should in fact fall on the next day that isn't a weekend - # or a holiday - it "handles the due date falling on a Saturday" do - Holiday.due_date_from_calendar_days(Date.new(2008, 10, 5), 20).should == Date.new(2008, 10, 27) + it "handles Time objects" do + Holiday.due_date_from_working_days(Time.utc(2009, 03, 16, 12, 0, 0), 20).strftime('%F').should == '2009-04-16' + end end - it "handles the due date falling on a Sunday" do - Holiday.due_date_from_calendar_days(Date.new(2008, 10, 6), 20).should == Date.new(2008, 10, 27) - end + context "in calendar days" do + it "handles no holidays" do + Holiday.due_date_from_calendar_days(Date.new(2008, 10, 1), 20).should == Date.new(2008, 10, 21) + end - it "handles the due date falling on a Monday" do - Holiday.due_date_from_calendar_days(Date.new(2008, 10, 7), 20).should == Date.new(2008, 10, 27) - end + it "handles the due date falling on a Friday" do + Holiday.due_date_from_calendar_days(Date.new(2008, 10, 4), 20).should == Date.new(2008, 10, 24) + end - it "handles the due date falling on a day before a Holiday" do - Holiday.due_date_from_calendar_days(Date.new(2008, 12, 4), 20).should == Date.new(2008, 12, 24) - end + # If the due date would fall on a Saturday it should in fact fall on the next day that isn't a weekend + # or a holiday + it "handles the due date falling on a Saturday" do + Holiday.due_date_from_calendar_days(Date.new(2008, 10, 5), 20).should == Date.new(2008, 10, 27) + end - it "handles the due date falling on a Holiday" do - Holiday.due_date_from_calendar_days(Date.new(2008, 12, 5), 20).should == Date.new(2008, 12, 29) - end + it "handles the due date falling on a Sunday" do + Holiday.due_date_from_calendar_days(Date.new(2008, 10, 6), 20).should == Date.new(2008, 10, 27) + end + + it "handles the due date falling on a Monday" do + Holiday.due_date_from_calendar_days(Date.new(2008, 10, 7), 20).should == Date.new(2008, 10, 27) + end + + it "handles the due date falling on a day before a Holiday" do + Holiday.due_date_from_calendar_days(Date.new(2008, 12, 4), 20).should == Date.new(2008, 12, 24) + end - it "handles Time objects" do - Holiday.due_date_from_calendar_days(Time.utc(2009, 03, 17, 12, 0, 0), 20).should == Date.new(2009, 4, 6) + it "handles the due date falling on a Holiday" do + Holiday.due_date_from_calendar_days(Date.new(2008, 12, 5), 20).should == Date.new(2008, 12, 29) + end + + it "handles Time objects" do + Holiday.due_date_from_calendar_days(Time.utc(2009, 03, 17, 12, 0, 0), 20).should == Date.new(2009, 4, 6) + end end end end - diff --git a/spec/models/incoming_message_spec.rb b/spec/models/incoming_message_spec.rb index 3b6887f76..f6e524de3 100644 --- a/spec/models/incoming_message_spec.rb +++ b/spec/models/incoming_message_spec.rb @@ -423,127 +423,50 @@ describe IncomingMessage, " checking validity to reply to with real emails" do end -describe IncomingMessage, " when censoring data" do - - before(:each) do - @test_data = "There was a mouse called Stilton, he wished that he was blue." - - @im = incoming_messages(:useless_incoming_message) - - @censor_rule_1 = CensorRule.new() - @censor_rule_1.text = "Stilton" - @censor_rule_1.replacement = "Jarlsberg" - @censor_rule_1.last_edit_editor = "unknown" - @censor_rule_1.last_edit_comment = "none" - @im.info_request.censor_rules << @censor_rule_1 - - @censor_rule_2 = CensorRule.new() - @censor_rule_2.text = "blue" - @censor_rule_2.replacement = "yellow" - @censor_rule_2.last_edit_editor = "unknown" - @censor_rule_2.last_edit_comment = "none" - @im.info_request.censor_rules << @censor_rule_2 - - @regex_censor_rule = CensorRule.new() - @regex_censor_rule.text = 'm[a-z][a-z][a-z]e' - @regex_censor_rule.regexp = true - @regex_censor_rule.replacement = 'cat' - @regex_censor_rule.last_edit_editor = 'unknown' - @regex_censor_rule.last_edit_comment = 'none' - @im.info_request.censor_rules << @regex_censor_rule - load_raw_emails_data - end - - it "should do nothing to a JPEG" do - data = @test_data.dup - @im.binary_mask_stuff!(data, "image/jpeg") - data.should == @test_data - end - - it "should replace censor text in Word documents" do - data = @test_data.dup - @im.binary_mask_stuff!(data, "application/vnd.ms-word") - data.should == "There was a xxxxx called xxxxxxx, he wished that he was xxxx." - end - - it "should replace ASCII email addresses in Word documents" do - orig_data = "His email was foo@bar.com" - data = orig_data.dup - @im.binary_mask_stuff!(data, "application/vnd.ms-word") - data.should == "His email was xxx@xxx.xxx" - end - - it "should replace UCS-2 addresses in Word documents" do - orig_data = "His email was f\000o\000o\000@\000b\000a\000r\000.\000c\000o\000m\000, indeed" - data = orig_data.dup - @im.binary_mask_stuff!(data, "application/vnd.ms-word") - data.should == "His email was x\000x\000x\000@\000x\000x\000x\000.\000x\000x\000x\000, indeed" - end - - it 'should handle multibyte characters correctly' do - orig_data = 'á' - data = orig_data.dup - @regex_censor_rule = CensorRule.new() - @regex_censor_rule.text = 'á' - @regex_censor_rule.regexp = true - @regex_censor_rule.replacement = 'cat' - @regex_censor_rule.last_edit_editor = 'unknown' - @regex_censor_rule.last_edit_comment = 'none' - @im.info_request.censor_rules << @regex_censor_rule - lambda{ @im.binary_mask_stuff!(data, "text/plain") }.should_not raise_error - end - def pdf_replacement_test(use_ghostscript_compression) - config = MySociety::Config.load_default() - previous = config['USE_GHOSTSCRIPT_COMPRESSION'] - config['USE_GHOSTSCRIPT_COMPRESSION'] = use_ghostscript_compression - orig_pdf = load_file_fixture('tfl.pdf') - pdf = orig_pdf.dup - - orig_text = MailHandler.get_attachment_text_one_file('application/pdf', pdf) - orig_text.should match(/foi@tfl.gov.uk/) - - @im.binary_mask_stuff!(pdf, "application/pdf") - - masked_text = MailHandler.get_attachment_text_one_file('application/pdf', pdf) - masked_text.should_not match(/foi@tfl.gov.uk/) - masked_text.should match(/xxx@xxx.xxx.xx/) - config['USE_GHOSTSCRIPT_COMPRESSION'] = previous - end - - it "should replace everything in PDF files using pdftk" do - pdf_replacement_test(false) - end - - it "should replace everything in PDF files using ghostscript" do - pdf_replacement_test(true) - end - - it "should not produce zero length output if pdftk silently fails" do - orig_pdf = load_file_fixture('psni.pdf') - pdf = orig_pdf.dup - @im.binary_mask_stuff!(pdf, "application/pdf") - pdf.should_not == "" - end - - it "should apply censor rules to HTML files" do - data = @test_data.dup - @im.html_mask_stuff!(data) - data.should == "There was a cat called Jarlsberg, he wished that he was yellow." - end - - it "should apply hard-coded privacy rules to HTML files" do - data = "http://#{AlaveteliConfiguration::domain}/c/cheese" - @im.html_mask_stuff!(data) - data.should == "[WDTK login link]" - end +describe IncomingMessage, " when censoring data" do - it "should apply censor rules to From: addresses" do - @im.stub!(:mail_from).and_return("Stilton Mouse") - @im.stub!(:last_parsed).and_return(Time.now) - safe_mail_from = @im.safe_mail_from - safe_mail_from.should == "Jarlsberg Mouse" - end + before(:each) do + @test_data = "There was a mouse called Stilton, he wished that he was blue." + + @im = incoming_messages(:useless_incoming_message) + + @censor_rule_1 = CensorRule.new() + @censor_rule_1.text = "Stilton" + @censor_rule_1.replacement = "Jarlsberg" + @censor_rule_1.last_edit_editor = "unknown" + @censor_rule_1.last_edit_comment = "none" + @im.info_request.censor_rules << @censor_rule_1 + + @censor_rule_2 = CensorRule.new() + @censor_rule_2.text = "blue" + @censor_rule_2.replacement = "yellow" + @censor_rule_2.last_edit_editor = "unknown" + @censor_rule_2.last_edit_comment = "none" + @im.info_request.censor_rules << @censor_rule_2 + + @regex_censor_rule = CensorRule.new() + @regex_censor_rule.text = 'm[a-z][a-z][a-z]e' + @regex_censor_rule.regexp = true + @regex_censor_rule.replacement = 'cat' + @regex_censor_rule.last_edit_editor = 'unknown' + @regex_censor_rule.last_edit_comment = 'none' + @im.info_request.censor_rules << @regex_censor_rule + load_raw_emails_data + end + + it "should replace censor text" do + data = "There was a mouse called Stilton, he wished that he was blue." + @im.apply_masks!(data, "application/vnd.ms-word") + data.should == "There was a xxxxx called xxxxxxx, he wished that he was xxxx." + end + + it "should apply censor rules to From: addresses" do + @im.stub!(:mail_from).and_return("Stilton Mouse") + @im.stub!(:last_parsed).and_return(Time.now) + safe_mail_from = @im.safe_mail_from + safe_mail_from.should == "Jarlsberg Mouse" + end end @@ -565,15 +488,16 @@ describe IncomingMessage, " when censoring whole users" do it "should apply censor rules to HTML files" do data = @test_data.dup - @im.html_mask_stuff!(data) + @im.apply_masks!(data, 'text/html') data.should == "There was a mouse called Gorgonzola, he wished that he was blue." end it "should replace censor text to Word documents" do data = @test_data.dup - @im.binary_mask_stuff!(data, "application/vnd.ms-word") + @im.apply_masks!(data, "application/vnd.ms-word") data.should == "There was a mouse called xxxxxxx, he wished that he was blue." end + end @@ -770,3 +694,16 @@ describe IncomingMessage, "when extracting attachments" do end end + +describe IncomingMessage, 'when getting the body of a message for html display' do + + it 'should replace any masked email addresses with a link to the help page' do + incoming_message = IncomingMessage.new + body_text = 'there was an [email address] here' + incoming_message.stub!(:get_main_body_text_folded).and_return(body_text) + incoming_message.stub!(:get_main_body_text_unfolded).and_return(body_text) + expected = 'there was an [<a href="/help/officers#mobiles">email address</a>] here' + incoming_message.get_body_for_html_display.should == expected + end + +end diff --git a/spec/models/info_request_spec.rb b/spec/models/info_request_spec.rb index 9ad616ea5..70947584b 100644 --- a/spec/models/info_request_spec.rb +++ b/spec/models/info_request_spec.rb @@ -824,7 +824,7 @@ describe InfoRequest do im = mock_model(IncomingMessage, :subject => nil, :valid_to_reply_to? => true) - subject = ir.email_subject_followup im + subject = ir.email_subject_followup(:incoming_message => im, :html => false) subject.should match(/^Re: Freedom of Information request.*fancy dog/) end diff --git a/spec/models/public_body_category_link_spec.rb b/spec/models/public_body_category_link_spec.rb index 8d91f02d5..fd5608480 100644 --- a/spec/models/public_body_category_link_spec.rb +++ b/spec/models/public_body_category_link_spec.rb @@ -1,10 +1,11 @@ # == Schema Information # -# Table name: public_body_category_link +# Table name: public_body_category_links # -# public_body_category_id :integer not null -# public_body_heading_id :integer not null -# category_display_order :integer +# public_body_category_id :integer not null +# public_body_heading_id :integer not null +# category_display_order :integer +# id :integer not null, primary key # require 'spec_helper' diff --git a/spec/models/public_body_category_spec.rb b/spec/models/public_body_category_spec.rb index c185a3169..96fe5686b 100644 --- a/spec/models/public_body_category_spec.rb +++ b/spec/models/public_body_category_spec.rb @@ -2,12 +2,8 @@ # # Table name: public_body_categories # -# id :integer not null, primary key -# locale :string -# title :text not null -# category_tag :text not null -# description :text not null -# display_order :integer +# id :integer not null, primary key +# category_tag :text not null # require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') diff --git a/spec/models/public_body_heading_spec.rb b/spec/models/public_body_heading_spec.rb index add2cac60..9372e0a07 100644 --- a/spec/models/public_body_heading_spec.rb +++ b/spec/models/public_body_heading_spec.rb @@ -2,9 +2,7 @@ # # Table name: public_body_headings # -# id :integer not null, primary key -# locale :string -# name :text not null +# id :integer not null, primary key # display_order :integer # |