diff options
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/ec2-rewrite-conf | 20 | ||||
-rwxr-xr-x | bin/fetch-comments | 4 | ||||
-rw-r--r-- | bin/fetch-comments-24hs | 26 | ||||
-rwxr-xr-x | bin/gettext-extract | 12 | ||||
-rwxr-xr-x | bin/gettext-nget-patch | 18 | ||||
-rwxr-xr-x | bin/install-as-user | 112 | ||||
-rwxr-xr-x | bin/install_perl_modules | 8 | ||||
-rwxr-xr-x | bin/make_css | 17 | ||||
-rwxr-xr-x | bin/make_emptyhomes_welsh_po | 14 | ||||
-rwxr-xr-x | bin/make_po (renamed from bin/make_emptyhomes_po) | 18 | ||||
-rwxr-xr-x | bin/open311-populate-service-list | 16 | ||||
-rwxr-xr-x | bin/oxfordshire/open311_service_request.cgi | 535 | ||||
-rwxr-xr-x | bin/problem-creation-graph | 14 | ||||
-rwxr-xr-x | bin/problems-filed-graph | 6 | ||||
-rwxr-xr-x | bin/rotate-photos | 34 | ||||
-rwxr-xr-x | bin/send-comments | 26 | ||||
-rw-r--r-- | bin/site-specific-install.sh | 53 |
17 files changed, 874 insertions, 59 deletions
diff --git a/bin/ec2-rewrite-conf b/bin/ec2-rewrite-conf new file mode 100755 index 000000000..0163ef511 --- /dev/null +++ b/bin/ec2-rewrite-conf @@ -0,0 +1,20 @@ +#!/bin/sh + +# This is a helper script for writing the current EC2 hostname into +# the FixMyStreet configuration file. Its intended usage is for lines +# like these to be added to /etc/rc.local: +# +# su -l -c /home/fms/fixmystreet/bin/ec2-rewrite-conf fms +# /etc/init.d/apache2 restart + +set -e + +BIN_DIR=$(dirname $(readlink -f $0)) +CONF_DIR=$BIN_DIR/../conf + +HOST=`curl -s http://169.254.169.254/latest/meta-data/public-hostname` + +sed -i -r \ + -e "s,^( *BASE_URL:).*,\\1 'http://$HOST'," \ + -e "s,^( *EMAIL_DOMAIN:).*,\\1 '$HOST'," \ + $CONF_DIR/general.yml diff --git a/bin/fetch-comments b/bin/fetch-comments index 4bbcc9d21..ef099fcc9 100755 --- a/bin/fetch-comments +++ b/bin/fetch-comments @@ -5,11 +5,13 @@ use warnings; require 5.8.0; use FixMyStreet::App; +use CronFns; +my ($verbose, $nomail) = CronFns::options(); use Open311; use Open311::GetServiceRequestUpdates; -my $updates = Open311::GetServiceRequestUpdates->new; +my $updates = Open311::GetServiceRequestUpdates->new( verbose => $verbose ); $updates->fetch; diff --git a/bin/fetch-comments-24hs b/bin/fetch-comments-24hs new file mode 100644 index 000000000..b84f09ba7 --- /dev/null +++ b/bin/fetch-comments-24hs @@ -0,0 +1,26 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use DateTime; +use DateTime::Format::W3CDTF; +require 5.8.0; + +use FixMyStreet::App; +use CronFns; +my ($verbose, $nomail) = CronFns::options(); + +use Open311; +use Open311::GetServiceRequestUpdates; + +my $dt = DateTime->now(); +my $dt_24hrs_ago = $dt->clone; +$dt_24hrs_ago->add( hours => -24 ); + +my $updates = Open311::GetServiceRequestUpdates->new( + verbose => 1, + start_date => DateTime::Format::W3CDTF->format_datetime( $dt_24hrs_ago ), + end_date => DateTime::Format::W3CDTF->format_datetime( $dt ) +); + +$updates->fetch; diff --git a/bin/gettext-extract b/bin/gettext-extract index 55623e86c..e77cf9cb0 100755 --- a/bin/gettext-extract +++ b/bin/gettext-extract @@ -31,23 +31,27 @@ rm -f $PO xgettext.pl --gnu-gettext --verbose --output $PO --plugin perl=* --plugin tt2 --directory perllib --directory templates/web --directory db --directory bin # Fix headers -TEMP=`tempfile` +# no such thing as tempfile on OS X +TEMP=`tempfile 2>/dev/null || mktemp /tmp/gettext-extract.XXXXXX` NOW=`date +"%Y-%m-%d %H:%M%z"` +# strictly POSIX sed on e.g. OS X doesn't let you used \n in replacements so we do this +nl=$'\n'; cat $PO | sed " s/SOME DESCRIPTIVE TITLE/FixMyStreet original .po file, autogenerated by gettext-extract/; s/YEAR THE PACKAGE'S COPYRIGHT HOLDER/2011 UK Citizens Online Democracy/; s/PACKAGE package/main FixMyStreet code/; s/FIRST AUTHOR <EMAIL@ADDRESS>, YEAR./Matthew Somerville <matthew@mysociety.org>, 2011-06-03./; - s/PACKAGE VERSION/1.0\\\n\"\n\"Report-Msgid-Bugs-To: matthew@mysociety.org/; + s/PACKAGE VERSION/1.0\\\n\"\\$nl\"Report-Msgid-Bugs-To: matthew@mysociety.org/; s/POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE/POT-Creation-Date: $NOW/; s/LL@li.org/team@fixmystreet.com/; s/charset=CHARSET/charset=UTF-8/; - s/8bit/8bit\\\n\"\n\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;/; + s/8bit/8bit\\\n\"\\$nl\"Plural-Forms: nplurals=2; plural=n != 1;/; " >> $TEMP mv $TEMP $PO echo "$( bin/gettext-nget-patch )" >> $PO -bin/make_emptyhomes_po +bin/make_po FixMyStreet-EmptyHomes +bin/make_po FixMyBarangay diff --git a/bin/gettext-nget-patch b/bin/gettext-nget-patch index 223bcc816..5ebd8bbcb 100755 --- a/bin/gettext-nget-patch +++ b/bin/gettext-nget-patch @@ -9,6 +9,7 @@ my %out; find( sub { next unless -f; + next if $File::Find::name =~ /ttc$/; open (FP, $_) or die $!; while (<FP>) { next unless /nget/; @@ -17,16 +18,17 @@ find( sub { do { $text .= <FP>; } until $text =~ /\)/; - $text =~ /nget\(\s*"(.*?)"\s*,\s*"(.*?)"\s*,\s*(.*?)\s*\)/s; - $out{$1} = { - file => $File::Find::name, - line => $line, - s => $1, - p => $2, - }; + if ($text =~ /nget\(\s*"(.*?)"\s*,\s*"(.*?)"\s*,\s*(.*?)\s*\)/s) { + $out{$1} = { + file => $File::Find::name, + line => $line, + s => $1, + p => $2, + }; + } } close FP; -}, 'templates'); +}, 'templates', 'perllib'); foreach (values %out) { print <<EOF; diff --git a/bin/install-as-user b/bin/install-as-user new file mode 100755 index 000000000..2e0816966 --- /dev/null +++ b/bin/install-as-user @@ -0,0 +1,112 @@ +#!/bin/sh + +set -e +set -x + +if [ $# -ne 3 ] +then + cat >&2 <<EOUSAGE +Usage: $0 <UNIX-USER> <HOST> <INSTALLATION-DIRECTORY> +EOUSAGE + exit 1 +fi + +UNIX_USER="$1" +HOST="$2" +DIRECTORY="$3" +DB_NAME="fixmystreet" + +# Check that the arguments we've been passed are sensible: + +IP_ADDRESS_FOR_HOST="$(dig +short $HOST)" + +if [ x = x"$IP_ADDRESS_FOR_HOST" ] +then + echo "The hostname $HOST didn't resolve to an IP address" + exit 1 +fi + +if ! id "$UNIX_USER" 2> /dev/null > /dev/null +then + echo "The user '$UNIX_USER' didn't exist." + exit 1 +fi + +if [ "$(whoami)" != "$UNIX_USER" ] +then + echo "This script should be run by the user '$UNIX_USER'." + exit 1 +fi + +REPOSITORY="$DIRECTORY/fixmystreet" +LINK_DESTINATION="$HOME/fixmystreet" + +ln -sfn "$REPOSITORY" $LINK_DESTINATION +cd "$REPOSITORY" + +# Add regularly scheduled tasks to cron: + +TEMPORARY_CRONTAB=$(mktemp) + +echo crontab file is $TEMPORARY_CRONTAB + +cp "$REPOSITORY"/conf/crontab.example "$TEMPORARY_CRONTAB" + +sed -i \ + -e 's,$FMS,'"$REPOSITORY,g" \ + -e 's,$LOCK_DIR,'"$DIRECTORY,g" \ + "$TEMPORARY_CRONTAB" + +crontab $TEMPORARY_CRONTAB + +# Install the compass gem locally - it's required for generating the +# CSS: + +export GEM_HOME="$DIRECTORY/gems" +mkdir -p "$GEM_HOME" +export GEM_PATH= +export PATH="$GEM_HOME/bin:$PATH" + +gem install --no-ri --no-rdoc compass + +# Use compass to generate the CSS, if it doesn't seem to already +# exist: + +if [ ! -f web/cobrands/default/base.css ] +then + bin/make_css +fi + +# Write sensible values into the config file: + +sed -r \ + -e "s,^( *FMS_DB_HOST:).*,\\1 ''," \ + -e "s,^( *FMS_DB_NAME:).*,\\1 '$DB_NAME'," \ + -e "s,^( *FMS_DB_USER:).*,\\1 '$UNIX_USER'," \ + -e "s,^( *BASE_URL:).*,\\1 'http://$HOST'," \ + -e "s,^( *EMAIL_DOMAIN:).*,\\1 '$HOST'," \ + -e "s,^( *CONTACT_EMAIL:).*,\\1 'help@$HOST'," \ + -e "s,^( *DO_NOT_REPLY_EMAIL:).*,\\1 'help@$HOST'," \ + conf/general.yml-example > conf/general.yml + +# Create the database if it doesn't exist: +if ! psql -l | egrep "^ *$DB_NAME *\|" > /dev/null +then + createdb --owner "$UNIX_USER" "$DB_NAME" + echo 'CREATE LANGUAGE plpgsql;' | psql -U "$UNIX_USER" "$DB_NAME" || true + psql -U "$UNIX_USER" "$DB_NAME" < "$REPOSITORY"/db/schema.sql + psql -U "$UNIX_USER" "$DB_NAME" < "$REPOSITORY"/db/alert_types.sql + psql -U "$UNIX_USER" "$DB_NAME" < "$REPOSITORY"/db/generate_secret.sql +fi + +# Install the required Perl modules - this may take a very long time: + +cd "$FMS_REPOSITORY" +bin/install_perl_modules + +# Generate po and mo files (these invocations taken from Kagee's script): + +bin/cron-wrapper bin/make_po FixMyStreet-EmptyHomes +bin/cron-wrapper bin/make_emptyhomes_welsh_po + +commonlib/bin/gettext-makemo FixMyStreet diff --git a/bin/install_perl_modules b/bin/install_perl_modules index 2df4ffbf8..2311ae5f1 100755 --- a/bin/install_perl_modules +++ b/bin/install_perl_modules @@ -1,5 +1,7 @@ #!/bin/bash +set -e + DIR="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd | sed -e 's/\/bin$//' )" $DIR/bin/cpanm -l $DIR/local Carton @@ -9,11 +11,7 @@ export PERL5LIB=$DIR/local/lib/perl5 carton install --deployment -perl -MImage::Magick -e 'exit()' >/dev/null 2>&1 - -HAVE_IM=$? - -if [ $HAVE_IM -ne 0 ] +if ! perl -MImage::Magick -e 'exit()' >/dev/null 2>&1 then read -p "Image::Magick is not installed. Do you want to attempt to install it?" yn case $yn in diff --git a/bin/make_css b/bin/make_css index 9d033b5dc..cd78e7f13 100755 --- a/bin/make_css +++ b/bin/make_css @@ -14,14 +14,19 @@ DIRECTORY=$(cd `dirname $0`/../web && pwd) # FixMyStreet uses compass -compass compile --output-style compressed $DIRECTORY/cobrands/fixmystreet -compass compile --output-style compressed $DIRECTORY/cobrands/bromley -compass compile --output-style compressed $DIRECTORY/cobrands/barnet -compass compile --output-style compressed $DIRECTORY/cobrands/zurich -compass compile --output-style compressed $DIRECTORY/cobrands/default +NEWSTYLE=${1:-"fixmystreet bromley fixmybarangay barnet zurich default stevenage oxfordshire"} +NEWSTYLE_REGEX=${NEWSTYLE// /\\|} +for site in $NEWSTYLE; do + compass compile --output-style compressed $DIRECTORY/cobrands/$site +done + +# If given a command line argument, assume was a compass directory and exit +if [ -n "$1" ]; then + exit 0 +fi # The rest are plain sass -for scss in `find $DIRECTORY -name "*.scss" -exec dirname {} \; | uniq | grep -v "cobrands/\(fixmystreet\|bromley\|barnet\|default\|zurich\)"` +for scss in `find $DIRECTORY -name "*.scss" -exec dirname {} \; | uniq | grep -v "cobrands/\($NEWSTYLE_REGEX\)"` do sass --scss --update --style compressed $scss done diff --git a/bin/make_emptyhomes_welsh_po b/bin/make_emptyhomes_welsh_po index b3dd441a0..f4f6850d6 100755 --- a/bin/make_emptyhomes_welsh_po +++ b/bin/make_emptyhomes_welsh_po @@ -87,15 +87,21 @@ while(<MAINPO>) { $new_buffer =~ s/"\n"//g; if ($lookup{$new_buffer} && $lookup{$new_buffer}[1]) { - print OUTPO "#, fuzzy\n"; - } + print OUTPO "#, fuzzy\n"; + } print OUTPO $buffer; if ($lookup{$new_buffer}) { print OUTPO $lookup{$new_buffer}[0]; } else { - print __LINE__ . "\n"; - die "Failed to find match with buffer: $new_buffer"; + if (m/^msgstr\[0\] ""/) { + $new_buffer =~ s/^msgid "/msgstr[0] "/m; + $new_buffer =~ s/^msgid_plural "/msgstr[1] "/m; + $_ = <MAINPO>; # skip untranslated plural + } else { + $new_buffer =~ s/^msgid "/msgstr "/; + } + print OUTPO $new_buffer; } $buffer = ""; diff --git a/bin/make_emptyhomes_po b/bin/make_po index 10e840599..76dc4566b 100755 --- a/bin/make_emptyhomes_po +++ b/bin/make_po @@ -1,8 +1,8 @@ #!/usr/bin/perl -w use strict; -# Generates EmptyHomes version of .po file, which is a translation -# into a language the same as English, only "problem" becomes "empty property". +# Generates a version of .po file, which is a translation +# into a language the same as English, with replacement as specified in PoChange use POSIX; use FindBin; @@ -13,14 +13,16 @@ chdir("$FindBin::Bin/../locale"); mkdir("en_GB.UTF-8"); mkdir("en_GB.UTF-8/LC_MESSAGES"); +my $pofile = shift; + open(MAINPO, "FixMyStreet.po") or die ""; -open(EHAPO, ">FixMyStreet-EmptyHomes.po") or die ""; -open(NEWPO, ">en_GB.UTF-8/LC_MESSAGES/FixMyStreet-EmptyHomes.po") or die ""; +open(EHAPO, ">$pofile.po") or die ""; +open(NEWPO, ">en_GB.UTF-8/LC_MESSAGES/$pofile.po") or die ""; -print NEWPO "# AUTOMATICALLY GENERATED by make_emptyhomes_po, do not edit\n"; +print NEWPO "# AUTOMATICALLY GENERATED by make_po, do not edit\n"; print NEWPO "#\n"; -print EHAPO "# AUTOMATICALLY GENERATED by make_emptyhomes_po, do not edit\n"; +print EHAPO "# AUTOMATICALLY GENERATED by make_po, do not edit\n"; print EHAPO "#\n"; my $buffer = ""; @@ -30,7 +32,7 @@ while(<MAINPO>) { s/#, fuzzy/#/; } if (m/"Last-Translator: FULL NAME/) { - $_ = '"Last-Translator: mysociety/bin/make_emptyhomes_po\\n"'."\n"; + $_ = '"Last-Translator: mysociety/bin/make_po\\n"'."\n"; } if (m/"PO-Revision-Date: YEAR-MO-DA/) { my $time = POSIX::strftime("%Y-%m-%d %H:%M%z", localtime(time())); @@ -56,7 +58,7 @@ while(<MAINPO>) { } elsif ($start && (m/^msgstr ""/ || m/^msgstr\[0\] ""/)) { # start of translated text - translate English into Empty Homes language - $buffer = PoChange::fixmystreet_to_reportemptyhomes($buffer); + $buffer = PoChange::translate($pofile, $buffer); print EHAPO $buffer; diff --git a/bin/open311-populate-service-list b/bin/open311-populate-service-list index 33be61af7..dc8fa3f7b 100755 --- a/bin/open311-populate-service-list +++ b/bin/open311-populate-service-list @@ -1,14 +1,26 @@ -#!/usr/bin/perl +#!/usr/bin/env perl use strict; use warnings; use FixMyStreet::App; use Open311::PopulateServiceList; +use Getopt::Long::Descriptive; +my ($opt, $usage) = describe_options( + '%c %o', + ['verbose|v', "print out all services as they are found"], + ['warn|w', "output warnings about any issues"], + ['help', "print usage message and exit" ], +); +print($usage->text), exit if $opt->help; my $council_list = FixMyStreet::App->model('DB::Open311conf')->search( { + area_id => { '!=', 2237 }, # XXX Until Oxfordshire does do so send_method => 'Open311' } ); -my $p = Open311::PopulateServiceList->new( council_list => $council_list ); +my $verbose = 0; +$verbose = 1 if $opt->warn; +$verbose = 2 if $opt->verbose; +my $p = Open311::PopulateServiceList->new( council_list => $council_list, verbose => $verbose ); $p->process_councils; diff --git a/bin/oxfordshire/open311_service_request.cgi b/bin/oxfordshire/open311_service_request.cgi new file mode 100755 index 000000000..821295ac7 --- /dev/null +++ b/bin/oxfordshire/open311_service_request.cgi @@ -0,0 +1,535 @@ +#!/usr/bin/perl + +# script for absobring incoming Open311 service request POSTs and +# passing them into Bentley EXOR backend via create_enquiry stored +# procedure. +# +# mySociety: http://code.fixmystreet.com/ +#----------------------------------------------------------------- + +use strict; +use CGI; +use Time::Piece; +use Encode qw(from_to); +use DBI; +use DBD::Oracle qw(:ora_types); +### for local testing (no Oracle): +# use constant { ORA_VARCHAR2=>1, ORA_DATE=>1, ORA_NUMBER=>1}; + +################################################################### +# Config file: values in the config file override any values set +# in the code below for the following things: +# +# host: SXX-SAN-FOO_BAR +# sid: FOOBAR +# port: 1531 +# username: foo +# password: FooBar +# testing: 0 +# encode-to-win1252: 1 +# +# Absence of the config file fails silently in case you really are +# using values directly set in this script. +#------------------------------------------------------------------ +my $CONFIG_FILENAME = "/usr/local/etc/fixmystreet.config"; + +use constant { + GENERAL_SERVICE_ERROR => 400, + CODE_OR_ID_NOT_FOUND => 404, + CODE_OR_ID_NOT_PROVIDED => 400, + BAD_METHOD => 405, + FATAL_ERROR => 500 +}; + +my $DB_SERVER_NAME = 'FOO'; +my $DB_HOST = $DB_SERVER_NAME; # did this just in case we need to add more to the name (e.g, domain) +my $DB_PORT = '1531'; +my $ORACLE_SID = 'FOOBAR'; +my $USERNAME = 'FIXMYSTREET'; +my $PASSWORD = 'XXX'; +my $STORED_PROC_NAME = 'PEM.create_enquiry'; + +# NB can override these settings in the config file! + +# Strip control chars: +# 'ruthless' removes everything (i.e. all POSIX control chars) +# 'desc' removes everything, but keeps tabs and newlines in the 'description' field, where they matter +# 'normal' keeps tabs and newlines +my $STRIP_CONTROL_CHARS = 'ruthless'; + +my $ENCODE_TO_WIN1252 = 1; # force encoding to Win-1252 for PEM data + +my $TESTING_WRITE_TO_FILE = 0; # write to file instead of DB +my $OUT_FILENAME = "fms-test.txt"; # dump data here if TESTING_WRITE_TO_FILE is true +my $TEST_SERVICE_DISCOVERY = 0; # switch to 1 to run service discovery, which confirms the DB connection at least +my $RUN_FAKE_INSERT_TEST = 0; # command-line execution attempts insert with fake data (mimics a POST request) + +# Config file overrides existing values for these, if present: +if ($CONFIG_FILENAME && open(CONF, $CONFIG_FILENAME)) { + while (<CONF>) { + next if /^#/; + if (/^\s*password:\s*(\S+)\s*$/i) { + $PASSWORD = $1; + } elsif (/^\s*sid:\s*(\S+)\s*$/i) { + $ORACLE_SID = $1; + } elsif (/^\s*username:\s*(\S+)\s*$/i) { + $USERNAME = $1; + } elsif (/^\s*port:\s*(\S+)\s*$/i) { + $DB_PORT = $1; + } elsif (/^\s*host:\s*(\S+)\s*$/i) { + $DB_HOST = $1; + } elsif (/^\s*testing:\s*(\S+)\s*$/i) { + $TESTING_WRITE_TO_FILE = $1; + } elsif (/^\s*test-service-discovery:\s*(\S+)\s*$/i) { + $TEST_SERVICE_DISCOVERY = $1; + } elsif (/^\s*strip-control-chars:\s*(\S+)\s*$/i) { + $STRIP_CONTROL_CHARS = lc $1; + } elsif (/^\s*encode-to-win1252:\s*(\S+)\s*$/i) { + $ENCODE_TO_WIN1252 = $1; + } elsif (/^\s*run-fake-insert-test:\s*(\S+)\s*$/i) { + $RUN_FAKE_INSERT_TEST = $1; + } + } +} + +# assuming Oracle environment is happily set already, don't need these: +# my $ORACLE_HOME = "/app/oracle/product/11.2.0/db_1"; +# $ENV{ORACLE_HOME} = $ORACLE_HOME; +# $ENV{ORACLE_SID} = $ORACLE_SID; +# $ENV{PATH} ="$ORACLE_HOME/bin"; +# $ENV{LD_LIBRARY_PATH}="$ORACLE_HOME/lib"; + +my %PEM_BOUND_VAR_TYPES = get_pem_field_types(); + +my $ERR_MSG = 'error'; # unique key in data hash + +# incoming (Open311, from FMS) field names +# note: attribute[*] are being sent by FMS explicitly as attributes for Oxfordshire +my %F = ( + 'ACCOUNT_ID' => 'account_id', + 'ADDRESS_ID' => 'address_id', + 'ADDRESS_STRING' => 'address_string', + 'API_KEY' => 'api_key', + 'CLOSEST_ADDRESS' => 'attribute[closest_address]', + 'DESCRIPTION' => 'description', + 'DEVICE_ID' => 'device_id', + 'EASTING' => 'attribute[easting]', + 'EMAIL' => 'email', + 'FIRST_NAME' => 'first_name', + 'FMS_ID' => 'attribute[external_id]', + 'LAST_NAME' => 'last_name', + 'LAT' => 'lat', + 'LONG' => 'long', + 'MEDIA_URL' => 'media_url', + 'NORTHING' => 'attribute[northing]', + 'PHONE' => 'phone', + 'REQUESTED_DATETIME' => 'requested_datetime', + 'SERVICE_CODE' => 'service_code', + 'STATUS' => 'status', + +); + +my $req = new CGI; + +# normally, POST requests are inserting service requests +# and GET requests are for returning service requests, although OCC aren't planning on +# using that (it's part of the Open311 spec). +# So actually the service discovery is more useful, so send in a 'services' param +# to see that. +# +# But for testing the db connection, set $TEST_SERVICE_DISCOVERY so that +# *all* requests simply do a service discovery by setting (possibly via the config file) + +if ($TEST_SERVICE_DISCOVERY) { + get_service_discovery($req); # to test +}elsif ($ENV{'REQUEST_METHOD'} eq "POST") { + post_service_request($req); +} elsif ($req -> param('services')) { + get_service_discovery($req); +} elsif ($RUN_FAKE_INSERT_TEST) { + # allow a GET to make an insert, for testing (from the commandnd line!) + print "Running fake insert test... returned: " . get_FAKE_INSERT(); + print "\nSee $OUT_FILENAME for data" if $TESTING_WRITE_TO_FILE; + print "\n"; +} else { + get_service_requests($req); +} + +#---------------------------------------------------- +# post_service_request +# accepts an incoming service request +# If everything goes well, it puts it in the database and +# returns the PEM ID to the caller +#---------------------------------------------------- +sub post_service_request { + my $req = shift; + my %data; + my $pem_id = 0; + + foreach (values %F) { + $data{$_} = $req -> param($_); + $data{$_} =~ s/^\s+|\s+$//g; # trim + } + + error_and_exit(CODE_OR_ID_NOT_PROVIDED, "missing service code (Open311 requires one)") + unless $data{$F{SERVICE_CODE}}; + error_and_exit(GENERAL_SERVICE_ERROR, "the service code you provided ($data{$F{SERVICE_CODE}}) was not recognised by this server") + unless service_exists($data{$F{SERVICE_CODE}}); + error_and_exit(GENERAL_SERVICE_ERROR, "no address or long-lat provided") + unless ( (is_longlat($data{$F{LONG}}) && is_longlat($data{$F{LAT}})) || $data{$F{ADDRESS_STRING}} ); + + $pem_id = insert_into_pem(\%data); + + if (! $pem_id) { + error_and_exit(FATAL_ERROR, $data{$ERR_MSG} || "failed to get PEM ID"); + } else { + print <<XML; +Content-type: text/xml + +<?xml version="1.0" encoding="utf-8"?> +<service_requests> + <request> + <service_request_id>$pem_id</service_request_id> + </request> +</service_requests> +XML + } +} + +#------------------------------------------------------------------ +# error_and_exit +# args: HTTP status code, error message +# Sends out the HTTP status code and message +# and temrinates execution +#------------------------------------------------------------------ +sub error_and_exit { + my ($status, $msg) = @_; + print "Status: $status $msg\n\n$msg\n"; + exit; +} + +#------------------------------------------------------------------ +# is_longlat +# returns true if this looks like a long/lat value +#------------------------------------------------------------------ +sub is_longlat { + return $_[0] =~ /^-?\d+\.\d+$/o? 1 : 0; +} + +#------------------------------------------------------------------ +# get_db_connection +# no args: uses globals, possibly read from config +# returns handle for the connection (otherwise terminates) +#------------------------------------------------------------------ +sub get_db_connection { + return DBI->connect( "dbi:Oracle:host=$DB_HOST;sid=$ORACLE_SID;port=$DB_PORT", $USERNAME, $PASSWORD ) + or error_and_exit(FATAL_ERROR, "failed to connect to database: " . $DBI::errstr, ""); +} + +#------------------------------------------------------------------ +# service_exists +# lookup the service code, to check that it exists +# SELECT det_code, det_name FROM higatlas.doc_enquiry_types WHERE +# det_dtp_code = 'REQS' AND det_dcl_code ='SERV' AND det_con_id=1 +# Actually, FMS is expected to be sending good codes because they +# come from here anyway, and are expected to be stable. But could +# cache and check... probably overkill since DB insert will probably +# throw the error anyway. +#------------------------------------------------------------------ +sub service_exists { + my $service_code = shift; + return 1; +} + +#------------------------------------------------------------------ +# dump_to_file +# args: ref to hash of data +# for testing, log the incoming data into a local file +# NB throws a fatal error +#------------------------------------------------------------------ +sub dump_to_file { + my $h = shift; # href to data hash + if (open (OUTFILE, ">$OUT_FILENAME")) { + print OUTFILE "Data dump: " . gmtime() . "\n" . '-' x 64 . "\n\n"; + foreach (sort keys %$h) { + print OUTFILE "$_ => " . $$h{$_} . "\n"; + } + print OUTFILE "\n\n" . '-' x 64 . "\n[end]\n"; + close OUTFILE; + $$h{$ERR_MSG} = "NB did not write to DB (see $OUT_FILENAME instead: switch off \$TESTING_WRITE_TO_FILE to stop this)"; + } else { + $$h{$ERR_MSG} = "failed to write to outfile ($!)"; + } + return 0; # test always throws an error so no risk of production confusion! +} + +#------------------------------------------------------------------ +# insert_into_pem +# args: hashref to data hash +# returns PEM id of the new record (or passes an error message +# into the data hash if no id is available) +#------------------------------------------------------------------ +sub insert_into_pem { + my $h = shift; # href to data hash + + my $pem_id; + my $error_value; + my $error_product; + + # set specifc vars up where further processing on them might be needed: + my $undef = undef; + my $status = $$h{$F{STATUS}}; + my $service_code = $$h{$F{SERVICE_CODE}}; + my $description = $$h{$F{DESCRIPTION}}; + my $media_url = $$h{$F{MEDIA_URL}}; + if ($media_url) { + # don't put URL for full images into the database (because they're too big to see on a Blackberry) + $media_url =~ s/\.full(\.jpe?g)$/$1/; + $description .= ($STRIP_CONTROL_CHARS ne 'ruthless'? "\n\n":" ") . "Photo: $media_url"; + } + my $location = $$h{$F{CLOSEST_ADDRESS}}; + if ($location) { + # strip out everything apart from "Nearest" preamble + $location=~s/(Nearest road)[^:]+:/$1:/; + $location=~s/(Nearest postcode)[^:]+:(.*?)(\(\w+ away\))?\s*(\n|$)/$1: $2/; + } + + my %bindings; + # comments here are suggested values + # field lengths are from OCC's Java portlet + # fixed values + $bindings{":ce_cat"} = 'REQS'; # or REQS ? + $bindings{":ce_class"} = 'SERV'; # 'FRML' ? + $bindings{":ce_contact_type"} = 'ENQUIRER'; # 'ENQUIRER' + $bindings{":ce_status_code"} = 'RE'; # RE=received (?) + $bindings{":ce_compl_user_type"}= 'USER'; # 'USER' + + # ce_incident_datetime is *not* an optional param, but FMS isn't sending it at the moment + $bindings{":ce_incident_datetime"}=$$h{$F{REQUESTED_DATETIME}} || Time::Piece->new->strftime('%Y-%m-%d %H:%M'); + + # especially FMS-specific: + $bindings{":ce_source"} = "FMS"; # important, and specific to this script! + $bindings{":ce_doc_reference"} = $$h{$F{FMS_ID}}; # FMS id + $bindings{":ce_enquiry_type"} = $service_code; + + # incoming data + $bindings{":ce_x"} = $$h{$F{EASTING}}; + $bindings{":ce_y"} = $$h{$F{NORTHING}}; + $bindings{":ce_forename"} = strip($$h{$F{FIRST_NAME}}, 30); # 'CLIFF' + $bindings{":ce_surname"} = strip($$h{$F{LAST_NAME}}, 30); # 'STEWART' + $bindings{":ce_work_phone"} = strip($$h{$F{PHONE}}, 25); # '0117 600 4200' + $bindings{":ce_email"} = strip($$h{$F{EMAIL}}, 50); # 'info@exor.co.uk' + $bindings{":ce_description"} = strip($description, 2000, $F{DESCRIPTION}); # 'Large Pothole' + + # nearest address guesstimate + $bindings{":ce_location"} = strip($location, 254); + + if ($TESTING_WRITE_TO_FILE) { + return dump_to_file(\%bindings); + } + + # everything ready: now put it into the database + my $dbh = get_db_connection(); + + my $sth = $dbh->prepare(q# + BEGIN + PEM.create_enquiry( + ce_cat => :ce_cat, + ce_class => :ce_class, + ce_forename => :ce_forename, + ce_surname => :ce_surname, + ce_contact_type => :ce_contact_type, + ce_location => :ce_location, + ce_work_phone => :ce_work_phone, + ce_email => :ce_email, + ce_description => :ce_description, + ce_enquiry_type => :ce_enquiry_type, + ce_source => :ce_source, + ce_incident_datetime => to_Date(:ce_incident_datetime,'YYYY-MM-DD HH24:MI'), + ce_x => :ce_x, + ce_y => :ce_y, + ce_doc_reference => :ce_doc_reference, + ce_status_code => :ce_status_code, + ce_compl_user_type => :ce_compl_user_type, + error_value => :error_value, + error_product => :error_product, + ce_doc_id => :ce_doc_id); + END; +#); + + foreach my $name (sort keys %bindings) { + next if grep {$name eq $_} (':error_value', ':error_product', ':ce_doc_id'); # return values (see below) + $sth->bind_param( + $name, + $bindings{$name}, + $PEM_BOUND_VAR_TYPES{$name} || ORA_VARCHAR2 + ); + } + # return values are bound explicitly here: + $sth->bind_param_inout(":error_value", \$error_value, 12); #> l_ERROR_VALUE # number + $sth->bind_param_inout(":error_product", \$error_product, 10); #> l_ERROR_PRODUCT (will always be 'DOC') + $sth->bind_param_inout(":ce_doc_id", \$pem_id, 12); #> l_ce_doc_id # number + + # not used, but from the example docs, for reference + # $sth->bind_param(":ce_contact_title", $undef); # 'MR' + # $sth->bind_param(":ce_postcode", $undef); # 'BS11EJ' NB no spaces, upper case + # $sth->bind_param(":ce_building_no", $undef); # '1' + # $sth->bind_param(":ce_building_name", $undef); # 'CLIFTON HEIGHTS' + # $sth->bind_param(":ce_street", $undef); # 'HIGH STREET' + # $sth->bind_param(":ce_town", $undef); # 'BRSITOL' + # $sth->bind_param(":ce_enquiry_type", $undef); # 'CD' , ce_source => 'T' + # $sth->bind_param(":ce_cpr_id", $undef); # '5' (priority) + # $sth->bind_param(":ce_rse_he_id", $undef); #> nm3net.get_ne_id('1200D90970/09001','L') + # $sth->bind_param(":ce_compl_target", $undef); # '08-JAN-2004' + # $sth->bind_param(":ce_compl_corresp_date",$undef); # '02-JAN-2004' + # $sth->bind_param(":ce_compl_corresp_deliv_date", $undef); # '02-JAN-2004' + # $sth->bind_param(":ce_resp_of", $undef); # 'GBOWLER' + # $sth->bind_param(":ce_hct_vip", $undef); # 'CO' + # $sth->bind_param(":ce_hct_home_phone", $undef); # '0117 900 6201' + # $sth->bind_param(":ce_hct_mobile_phone", $undef); # '07111 1111111' + # $sth->bind_param(":ce_compl_remarks", $undef); # remarks (notes) max 254 char + + $sth->execute(); + $dbh->disconnect; + + # if error, maybe need to look it up: + # error_value is the index HER_NO in table HIG_ERRORS, which has messages + # actually err_product not helpful (wil always be "DOC") + $$h{$ERR_MSG} = "$error_value $error_product" if ($error_value || $error_product); + + return $pem_id; +} + +#------------------------------------------------------------------ +# strip +# args: data, max-length, field-name +# Trims, strips control chars, truncates to max-length +# Field-name only matters for description field +#------------------------------------------------------------------ +sub strip { + my ($s, $max_len, $field_name) = @_; + if ($STRIP_CONTROL_CHARS) { + if ($STRIP_CONTROL_CHARS eq 'ruthless') { + $s =~ s/[[:cntrl:]]/ /g; # strip all control chars, simples + } elsif ($STRIP_CONTROL_CHARS eq 'desc') { + if ($field_name eq $F{DESCRIPTION}) { + $s =~ s/[^\t\n[:^cntrl:]]/ /g; # leave tabs and newlines + } else { + $s =~ s/[[:cntrl:]]/ /g; # strip all control chars, simples + } + } else { + $s =~ s/[^\t\n[:^cntrl:]]/ /g; # leave tabs and newlines + } + } + from_to($s, 'utf8', 'Windows-1252') if $ENCODE_TO_WIN1252; + return $max_len? substr($s, 0, 2000) : $s; +} + +#------------------------------------------------------------------ +# get_service_requests +# Strictly speaking, Open311 would expect the GET request for service +# requests to respond with all service requests (within a specified +# period). But as we're not using that, do a service discovery +# instead. +#------------------------------------------------------------------ +sub get_service_requests { + # error_and_exit(BAD_METHOD, "sorry, currently only handling incoming Open311 service requests: use POST method"); + get_service_discovery(); # for now +} + +#------------------------------------------------------------------ +# get_FAKE_INSERT +# for testing off command line, force the data +#------------------------------------------------------------------ +sub get_FAKE_INSERT { + my %fake_data = ( + $F{'DESCRIPTION'} => 'Testing, description: A acute (requires Latin-1): [á] ' + . ' pound sign (requires WinLatin-1): [£] omega tonos (requires UTF-8): [ώ]', + $F{'EASTING'} => '45119', + $F{'EMAIL'} => 'email@example.com', + $F{'FIRST_NAME'} => 'Dave', + $F{'FMS_ID'} => '1012', + $F{'LAST_NAME'} => 'Test', + $F{'LAT'} => '51.756741605999', + $F{'LONG'} => '-1.2596387532192', + $F{'NORTHING'} => '206709', + $F{'SERVICE_CODE'} => 'OT', + $F{'MEDIA_URL'} => 'http://www.example.com/pothole.jpg', + $F{'CLOSEST_ADDRESS'} => <<TEXT +Nearest road to the pin placed on the map (automatically generated by Bing Maps): St Giles, Oxford, OX1 3 + +Nearest postcode to the pin placed on the map (automatically generated): OX1 2LA (46m away) +TEXT + ); + return insert_into_pem(\%fake_data) +} + +#------------------------------------------------------------------ +# get_service_discovery +# Although not really implementing this, use it as a test to read the +# db and confirm connectivity. +#------------------------------------------------------------------ +sub get_service_discovery { + my $dbh = get_db_connection(); + my $ary_ref = $dbh->selectall_arrayref(qq(select det_code, det_name from higatlas.doc_enquiry_types where det_dtp_code = 'REQS' AND det_dcl_code='SERV' and det_con_id=1)); + # rough and ready XML dump now (should use XML Simple to build/escape properly!) + my $xml = ""; + foreach my $row(@{$ary_ref}) { + if (defined $row) { + my ($code, $name) = @$row; + $xml.= <<XML; + <service> + <service_code>$code</service_code> + <metadata>false</metadata> + <type>realtime</type> + <keywords/> + <group/> + <service_name>$name</service_name> + <description/> + </service> +XML + } + } + print <<XML; +Content-type: text/xml + +<?xml version="1.0" encoding="utf-8"?> +<services> +$xml +</services> +XML +} + +#------------------------------------------------------------------ +# get_pem_field_types +# return hash of types by field name: any not explicitly set here +# can be defaulted to VARCHAR2 +#------------------------------------------------------------------ +sub get_pem_field_types { + return ( + ':ce_incident_datetime' => ORA_DATE, + ':ce_x' => ORA_NUMBER, + ':ce_y' => ORA_NUMBER, + ':ce_date_expires' => ORA_DATE, + ':ce_issue_number' => ORA_NUMBER, + ':ce_status_date' => ORA_DATE, + ':ce_compl_ack_date' => ORA_DATE, + ':ce_compl_peo_date' => ORA_DATE, + ':ce_compl_target' => ORA_DATE, + ':ce_compl_complete' => ORA_DATE, + ':ce_compl_from' => ORA_DATE, + ':ce_compl_to' => ORA_DATE, + ':ce_compl_corresp_date' => ORA_DATE, + ':ce_compl_corresp_deliv_date' => ORA_DATE, + ':ce_compl_no_of_petitioners' => ORA_NUMBER, + ':ce_compl_est_cost' => ORA_NUMBER, + ':ce_compl_adv_cost' => ORA_NUMBER, + ':ce_compl_act_cost' => ORA_NUMBER, + ':ce_compl_follow_up1' => ORA_DATE, + ':ce_compl_follow_up2' => ORA_DATE, + ':ce_compl_follow_uo3' => ORA_DATE, + ':ce_date_time_arrived' => ORA_DATE, + ':error_value' => ORA_NUMBER, + ':ce_doc_id' => ORA_NUMBER, + ) +} diff --git a/bin/problem-creation-graph b/bin/problem-creation-graph index 6692ae724..b96d45540 100755 --- a/bin/problem-creation-graph +++ b/bin/problem-creation-graph @@ -26,12 +26,12 @@ else DATE="2007-02-01" fi -SOURCEA=/tmp/bci-creation-rate-graph-data-$RANDOM$RANDOM -SOURCEB=/tmp/bci-creation-rate-graph-data-$RANDOM$RANDOM -SOURCEC=/tmp/bci-creation-rate-graph-data-$RANDOM$RANDOM -SOURCED=/tmp/bci-creation-rate-graph-data-$RANDOM$RANDOM -SOURCEE=/tmp/bci-creation-rate-graph-data-$RANDOM$RANDOM -GPSCRIPT=/tmp/bci-creation-rate-graph-script-$RANDOM$RANDOM +SOURCEA=/tmp/fms-creation-rate-graph-data-$RANDOM$RANDOM +SOURCEB=/tmp/fms-creation-rate-graph-data-$RANDOM$RANDOM +SOURCEC=/tmp/fms-creation-rate-graph-data-$RANDOM$RANDOM +SOURCED=/tmp/fms-creation-rate-graph-data-$RANDOM$RANDOM +SOURCEE=/tmp/fms-creation-rate-graph-data-$RANDOM$RANDOM +GPSCRIPT=/tmp/fms-creation-rate-graph-script-$RANDOM$RANDOM # where status in ('draft') @@ -104,6 +104,6 @@ END #echo "gpscript $GPSCRIPT" export GDFONTPATH=/usr/share/fonts/truetype/ttf-bitstream-vera -gnuplot < $GPSCRIPT > fixmystreet/web/bci-live-creation$EXTENSION 2>/dev/null +gnuplot < $GPSCRIPT > fixmystreet/web/fms-live-creation$EXTENSION 2>/dev/null diff --git a/bin/problems-filed-graph b/bin/problems-filed-graph index 8addacd62..e5946b078 100755 --- a/bin/problems-filed-graph +++ b/bin/problems-filed-graph @@ -20,8 +20,8 @@ source fixmystreet/commonlib/shlib/deployfns read_conf fixmystreet/conf/general.yml -SOURCEO=/tmp/bci-report-rate-graph-data-nonwmc-$RANDOM$RANDOM -GPSCRIPT=/tmp/bci-report-rate-graph-script-$RANDOM$RANDOM +SOURCEO=/tmp/fms-report-rate-graph-data-nonwmc-$RANDOM$RANDOM +GPSCRIPT=/tmp/fms-report-rate-graph-script-$RANDOM$RANDOM echo "select date(created), count(*) @@ -57,5 +57,5 @@ END #echo "gpscript $GPSCRIPT" export GDFONTPATH=/usr/share/fonts/truetype/ttf-bitstream-vera -gnuplot < $GPSCRIPT > fixmystreet/web/bci-live-line$EXTENSION 2>/dev/null +gnuplot < $GPSCRIPT > fixmystreet/web/fms-live-line$EXTENSION 2>/dev/null diff --git a/bin/rotate-photos b/bin/rotate-photos index faf2748e6..31a60ff6c 100755 --- a/bin/rotate-photos +++ b/bin/rotate-photos @@ -16,6 +16,8 @@ use FindBin; use lib "$FindBin::Bin/../perllib"; use lib "$FindBin::Bin/../commonlib/perllib"; +use Digest::SHA1 qw(sha1_hex); + use Utils; use mySociety::Config; use mySociety::DBHandle qw(dbh select_all); @@ -36,17 +38,27 @@ my $r = select_all("select id, photo from problem where service='iPhone'"); foreach (@$r) { my $id = $_->{id}; my $photo = $_->{photo}; - my ($fh, $filename) = mySociety::TempFiles::named_tempfile('.jpeg'); - print $fh $photo; - close $fh; - my $out = `jhead -autorot $filename`; - if ($out) { - open(FP, $filename) or die $!; - $photo = join('', <FP>); - close FP; - Utils::workaround_pg_bytea("update problem set photo=? where id=?", 1, $photo, $id); - dbh()->commit(); + + if (length($photo) == 40) { + # If photo field contains a hash + my $filename = mySociety::Config::get('UPLOAD_DIR') . "$photo.jpeg"; + `jhead -autorot $filename`; + } else { + my ($fh, $filename) = mySociety::TempFiles::named_tempfile('.jpeg'); + print $fh $photo; + close $fh; + my $out = `jhead -autorot $filename`; + if ($out) { + open(FP, $filename) or die $!; + $photo = join('', <FP>); + close FP; + my $fileid = sha1_hex($photo); + rename $filename, mySociety::Config::get('UPLOAD_DIR') . "$fileid.jpeg"; + dbh()->do('UPDATE problem SET photo=? WHERE id=?', {}, $fileid, $id); + dbh()->commit(); + } else { + unlink $filename; + } } - unlink $filename; } diff --git a/bin/send-comments b/bin/send-comments index b60c46e5d..24b436ac8 100755 --- a/bin/send-comments +++ b/bin/send-comments @@ -41,6 +41,7 @@ my $councils = FixMyStreet::App->model('DB::Open311Conf')->search( { } ); while ( my $council = $councils->next ) { + my $use_extended = 0; my $comments = FixMyStreet::App->model('DB::Comment')->search( { 'me.whensent' => undef, 'me.external_id' => undef, @@ -56,21 +57,46 @@ while ( my $council = $councils->next ) { } ); + if ( $council->area_id == 2482 ) { + $use_extended = 1; + } + my %open311_conf = ( endpoint => $council->endpoint, jurisdiction => $council->jurisdiction, api_key => $council->api_key, + use_extended_updates => $use_extended, ); + if ( $council->send_extended_statuses ) { $open311_conf{extended_statuses} = 1; } my $o = Open311->new( %open311_conf ); + if ( $council->area_id =~ /2482/ ) { + my $endpoints = $o->endpoints; + $endpoints->{update} = 'update.xml'; + $endpoints->{service_request_updates} = 'update.xml'; + $o->endpoints( $endpoints ); + } + while ( my $comment = $comments->next ) { my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($comment->cobrand)->new(); + # actually this should be OK for any devolved endpoint if original Open311->can_be_devolved, presumably + if ( $cobrand->moniker eq "fixmybarangay") { + my $sender = $cobrand->get_council_sender( $council->area_id, undef, $comment->problem->category ); + my $config = $sender->{config}; + $o = Open311->new( + endpoint => $config->endpoint, + jurisdiction => $config->jurisdiction, + api_key => $config->api_key, + use_extended_updates => 1, # FMB uses extended updates + ); + } + if ( $comment->send_fail_count ) { next if bromley_retry_timeout( $comment ); } diff --git a/bin/site-specific-install.sh b/bin/site-specific-install.sh new file mode 100644 index 000000000..6ebac7970 --- /dev/null +++ b/bin/site-specific-install.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +PARENT_SCRIPT_URL=https://github.com/mysociety/commonlib/blob/master/bin/install-site.sh + +misuse() { + echo The variable $1 was not defined, and it should be. + echo This script should not be run directly - instead, please run: + echo $PARENT_SCRIPT_URL + exit 1 +} + +# Strictly speaking we don't need to check all of these, but it might +# catch some errors made when changing install-site.sh + +[ -z "$DIRECTORY" ] && misuse DIRECTORY +[ -z "$UNIX_USER" ] && misuse UNIX_USER +[ -z "$REPOSITORY" ] && misuse REPOSITORY +[ -z "$REPOSITORY_URL" ] && misuse REPOSITORY_URL +[ -z "$BRANCH" ] && misuse BRANCH +[ -z "$SITE" ] && misuse SITE +[ -z "$DEFAULT_SERVER" ] && misuse DEFAULT_SERVER +[ -z "$HOST" ] && misuse HOST +[ -z "$DISTRIBUTION" ] && misuse DISTRIBUTION +[ -z "$VERSION" ] && misuse VERSION + +install_nginx + +install_website_packages + +su -l -c "touch '$DIRECTORY/admin-htpasswd'" "$UNIX_USER" + +add_website_to_nginx + +add_postgresql_user + +su -l -c "$REPOSITORY/bin/install-as-user '$UNIX_USER' '$HOST' '$DIRECTORY'" "$UNIX_USER" + +install_sysvinit_script + +if [ $DEFAULT_SERVER = true ] && [ x != x$EC2_HOSTNAME ] +then + # If we're setting up as the default on an EC2 instance, + # make sure the ec2-rewrite-conf script is called from + # /etc/rc.local + overwrite_rc_local +fi + +# Tell the user what to do next: + +echo Installation complete - you should now be able to view the site at: +echo http://$HOST/ +echo Or you can run the tests by switching to the "'$UNIX_USER'" user and +echo running: $REPOSITORY/bin/cron-wrapper prove -r t |