<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package FixMyStreet::App::Controller::Contact;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller'; }

use MIME::Base64;
use mySociety::EmailUtil;
use FixMyStreet::Email;
use FixMyStreet::Template::SafeString;

=head1 NAME

FixMyStreet::App::Controller::Contact - Catalyst Controller

=head1 DESCRIPTION

Contact us page

=head1 METHODS

=head2 auto

Functions to run on both GET and POST contact requests.

=cut

sub auto : Private {
    my ($self, $c) = @_;
    $c-&gt;forward('/auth/get_csrf_token');
}

sub begin : Private {
    my ($self, $c) = @_;
    $c-&gt;forward('/begin');
    $c-&gt;forward('setup_request');
}

=head2 index

Display contact us page

=cut

sub index : Path : Args(0) {
    my ( $self, $c ) = @_;
    $c-&gt;forward('determine_contact_type');
}

=head2 submit

Handle contact us form submission

=cut

sub submit : Path('submit') : Args(0) {
    my ( $self, $c ) = @_;

    $c-&gt;forward('determine_contact_type');
    $c-&gt;res-&gt;redirect( '/contact' ) and return unless $c-&gt;req-&gt;method eq 'POST';

    $c-&gt;go('index') unless $c-&gt;forward('validate');
    $c-&gt;forward('prepare_params_for_email');
    $c-&gt;forward('send_email');
    $c-&gt;forward('redirect_on_success');
}

=head2 determine_contact_type

Work out if we have got here via a report/update or this is a
generic contact request and set up things accordingly

=cut

sub determine_contact_type : Private {
    my ( $self, $c ) = @_;

    my $id = $c-&gt;get_param('id');
    my $update_id = $c-&gt;get_param('update_id');
    my $token = $c-&gt;get_param('m');
    $id        = undef unless $id        &amp;&amp; $id        =~ /^[1-9]\d*$/;
    $update_id = undef unless $update_id &amp;&amp; $update_id =~ /^[1-9]\d*$/;

    if ($token) {
        my $token_obj = $c-&gt;forward('/tokens/load_auth_token', [ $token, 'moderation' ]);
        my $problem = $c-&gt;cobrand-&gt;problems-&gt;find( { id =&gt; $token_obj-&gt;data-&gt;{id} } );
        if ($problem) {
            $c-&gt;stash-&gt;{problem} = $problem;
            $c-&gt;stash-&gt;{moderation_complaint} = $token;
        } else {
            $c-&gt;forward( '/report/load_problem_or_display_error', [ $id ] );
        }

    } elsif ($id) {
        $c-&gt;forward( '/report/load_problem_or_display_error', [ $id ] );
        if ($update_id) {
            my $update = $c-&gt;cobrand-&gt;updates-&gt;search(
                {
                    "me.id" =&gt; $update_id,
                    problem_id =&gt; $id,
                    "me.state" =&gt; 'confirmed',
                }
            )-&gt;first;

            unless ($update) {
                $c-&gt;detach( '/page_error_404_not_found', [ _('Unknown update ID') ] );
            }

            $c-&gt;stash-&gt;{update} = $update;
        }

        if ( $c-&gt;get_param("reject") &amp;&amp; $c-&gt;user-&gt;has_permission_to(report_reject =&gt; $c-&gt;stash-&gt;{problem}-&gt;bodies_str_ids) ) {
            $c-&gt;stash-&gt;{rejecting_report} = 1;
        }
    } elsif ( $c-&gt;cobrand-&gt;abuse_reports_only ) {
        # General enquiries replaces contact form if enabled
        if ( $c-&gt;cobrand-&gt;can('setup_general_enquiries_stash') ) {
            $c-&gt;res-&gt;redirect( '/contact/enquiry' );
            $c-&gt;detach;
            return 1;
        } else {
            $c-&gt;detach( '/page_error_404_not_found' );
        }
    }

    return 1;
}

=head2 validate

Validate the form submission parameters. Sets error messages and redirect
to index page if errors.

=cut

sub validate : Private {
    my ( $self, $c ) = @_;

    $c-&gt;forward('/auth/check_csrf_token');
    my $s = $c-&gt;stash-&gt;{s} = unpack("N", decode_base64($c-&gt;get_param('s')));
    return if !FixMyStreet-&gt;test_mode &amp;&amp; time() &lt; $s; # uncoverable statement

    my ( %field_errors, @errors );
    my %required = (
        name    =&gt; _('Please enter your name'),
        em      =&gt; _('Please enter your email'),
        subject =&gt; _('Please enter a subject'),
        message =&gt; _('Please write a message')
    );

    foreach my $field ( keys %required ) {
        $field_errors{$field} = $required{$field}
          unless $c-&gt;get_param($field) =~ /\S/;
    }

    unless ( $field_errors{em} ) {
        $field_errors{em} = _('Please enter a valid email address')
          if !mySociety::EmailUtil::is_valid_email( $c-&gt;get_param('em') );
    }

    %field_errors = (
        %field_errors,
        $c-&gt;cobrand-&gt;extra_contact_validation($c)
    );

    push @errors, _('Illegal ID')
      if $c-&gt;get_param('id') &amp;&amp; !$c-&gt;stash-&gt;{problem}
          or $c-&gt;get_param('update_id') &amp;&amp; !$c-&gt;stash-&gt;{update};

    push @errors, _('There was a problem showing this page. Please try again later.')
      if $c-&gt;get_param('message') &amp;&amp; $c-&gt;get_param('message') =~ /\[url=|&lt;a/;

    unshift @errors,
      _('There were problems with your report. Please see below.')
      if scalar keys %field_errors;

    if ( @errors or scalar keys %field_errors ) {
        $c-&gt;stash-&gt;{errors}       = \@errors;
        $c-&gt;stash-&gt;{field_errors} = \%field_errors;
        return 0;
    }

    return 1;
}

=head2 prepare_params_for_email

Does neccessary reformating of exiting params and add any additional
information required for emailing ( problem ids, admin page links etc )

=cut

sub prepare_params_for_email : Private {
    my ( $self, $c ) = @_;

    $c-&gt;stash-&gt;{message} =~ s/\r\n/\n/g;
    $c-&gt;stash-&gt;{subject} =~ s/\r|\n/ /g;

    my $base_url = $c-&gt;cobrand-&gt;base_url();
    my $admin_url = $c-&gt;cobrand-&gt;admin_base_url;

    my $user = $c-&gt;cobrand-&gt;users-&gt;find( { email =&gt; lc $c-&gt;stash-&gt;{em} } );
    if ( $user ) {
        $c-&gt;stash-&gt;{user_admin_url} = $admin_url . '/users/' . $user-&gt;id;
        $c-&gt;stash-&gt;{user_reports_admin_url} = $admin_url . '/reports?search=' . $user-&gt;email;

        my $user_latest_problem = $user-&gt;latest_visible_problem();
        if ( $user_latest_problem) {
            $c-&gt;stash-&gt;{user_latest_report_admin_url} = $admin_url . '/report_edit/' . $user_latest_problem-&gt;id;
        }
    }

    if ( $c-&gt;stash-&gt;{update} ) {

        $c-&gt;stash-&gt;{problem_url} = $base_url . $c-&gt;stash-&gt;{update}-&gt;url;
        $c-&gt;stash-&gt;{admin_url} = $admin_url . '/update_edit/' . $c-&gt;stash-&gt;{update}-&gt;id;
        $c-&gt;stash-&gt;{complaint} = sprintf(
            "Complaint about update %d on report %d",
            $c-&gt;stash-&gt;{update}-&gt;id,
            $c-&gt;stash-&gt;{update}-&gt;problem_id,
        );
    }
    elsif ( $c-&gt;stash-&gt;{problem} ) {

        $c-&gt;stash-&gt;{problem_url} = $base_url . '/report/' . $c-&gt;stash-&gt;{problem}-&gt;id;
        $c-&gt;stash-&gt;{admin_url} = $admin_url . '/report_edit/' . $c-&gt;stash-&gt;{problem}-&gt;id;
        $c-&gt;stash-&gt;{complaint} = sprintf(
            "Complaint about report %d",
            $c-&gt;stash-&gt;{problem}-&gt;id,
        );

        # flag this so it's automatically listed in the admin interface
        $c-&gt;stash-&gt;{problem}-&gt;flagged(1);
        $c-&gt;stash-&gt;{problem}-&gt;update;
    }

    my @extra = grep { /^extra\./ } keys %{$c-&gt;req-&gt;params};
    foreach (@extra) {
        my $param = $c-&gt;get_param($_);
        my ($field_name) = /extra\.(.*)/;
        $c-&gt;stash-&gt;{message} = "\u$field_name: $param\n\n" . $c-&gt;stash-&gt;{message};
    }

    return 1;
}

=head2 setup_request

Pulls things from request into stash and adds other information
generally required to stash

=cut

sub setup_request : Private {
    my ( $self, $c ) = @_;

    my $email = $c-&gt;cobrand-&gt;contact_email;
    $email =~ s/\@/&amp;#64;/;
    $c-&gt;stash-&gt;{contact_email} = FixMyStreet::Template::SafeString-&gt;new($email);

    for my $param (qw/em subject message/) {
        $c-&gt;stash-&gt;{$param} = $c-&gt;get_param($param);
    }

    # name is already used in the stash for the app class name
    $c-&gt;stash-&gt;{form_name} = $c-&gt;get_param('name');

    my $s = encode_base64(pack("N", time() + 10), '');
    $s =~ s/=+$//;
    $c-&gt;stash-&gt;{s} = $s;

    return 1;
}

=head2 send_email

Sends the email

=cut

sub send_email : Private {
    my ( $self, $c ) = @_;

    my $recipient      = $c-&gt;cobrand-&gt;contact_email;
    my $recipient_name = $c-&gt;cobrand-&gt;contact_name();
    my $use_reply_to = $c-&gt;cobrand-&gt;feature('always_use_reply_to');

    if (my $localpart = $c-&gt;get_param('recipient')) {
        $recipient = join('@', $localpart, FixMyStreet-&gt;config('EMAIL_DOMAIN'));
    }

    $c-&gt;stash-&gt;{host} = $c-&gt;req-&gt;header('HOST');
    $c-&gt;stash-&gt;{ip}   = $c-&gt;req-&gt;address;
    $c-&gt;stash-&gt;{ip} .=
      $c-&gt;req-&gt;header('X-Forwarded-For')
      ? ' ( forwarded from ' . $c-&gt;req-&gt;header('X-Forwarded-For') . ' )'
      : '';

    my $from = [ $c-&gt;stash-&gt;{em}, $c-&gt;stash-&gt;{form_name} ];
    my $params = {
        to =&gt; [ [ $recipient, _($recipient_name) ] ],
        user_agent =&gt; $c-&gt;req-&gt;user_agent,
    };
    if ($use_reply_to || FixMyStreet::Email::test_dmarc($c-&gt;stash-&gt;{em})) {
        $params-&gt;{'Reply-To'} = [ $from ];
        $params-&gt;{from} = [ FixMyStreet-&gt;config('DO_NOT_REPLY_EMAIL'), $c-&gt;stash-&gt;{form_name} ];
    } else {
        $params-&gt;{from} = $from;
    }

    $c-&gt;stash-&gt;{success} = $c-&gt;send_email('contact.txt', $params);

    return 1;
}

=head2 redirect_on_success

Redirect to a custom URL if one was provided

=cut

sub redirect_on_success : Private {
    my ( $self, $c ) = @_;

    if (my $success_url = $c-&gt;get_param('success_url')) {
        $c-&gt;res-&gt;redirect($success_url);
        $c-&gt;detach;
    }

    return 1;
}

=head1 AUTHOR

Struan Donald

=head1 LICENSE

This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.

=cut

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</pre></body></html>