File Coverage

File:lib/Yukki/Web/Controller/Attachment.pm
Coverage:42.8%

linestmtbrancondsubpodtimecode
1package Yukki::Web::Controller::Attachment;
2
3
2
2
616
7
use v5.24;
4
2
2
2
7
14
21
use utf8;
5
2
2
2
30
2
17
use Moo;
6
7with 'Yukki::Web::Controller';
8
9
2
2
2
2238
11824
9
use JSON;
10
2
2
2
360
4
7
use Yukki::Error qw( http_throw );
11
12
2
2
2
308
3
7
use namespace::clean;
13
14# ABSTRACT: Controller for uploading, downloading, and viewing attachments
15
16 - 26
=head1 DESCRIPTION

Handles uploading, downloading, and viewing attachments.

=head1 METHODS

=head2 fire

Maps download requests to L</download_file>, upload requests to L</upload_file>, and view requestst to L</view_file>.

=cut
27
28sub fire {
29
2
1
5
    my ($self, $ctx) = @_;
30
31
2
26
    my $action = $ctx->request->path_parameters->{action};
32
2
1
42
3
    if    ($action eq 'download') { $self->download_file($ctx) }
33
0
0
    elsif ($action eq 'upload')   { $self->upload_file($ctx) }
34
0
0
    elsif ($action eq 'view')     { $self->view_file($ctx) }
35
0
0
    elsif ($action eq 'rename')   { $self->rename_file($ctx) }
36
0
0
    elsif ($action eq 'remove')   { $self->remove_file($ctx) }
37    else {
38
1
5
        http_throw('That attachment action does not exist.', {
39            status => 'NotFound',
40        });
41    }
42}
43
44 - 51
=head2 lookup_file

  my $file = $self->lookup_file($repository, $path);

This is a helper for locating and returning a L<Yukki::Model::File> for the
requested repository and path.

=cut
52
53sub lookup_file {
54
1
1
2
    my ($self, $repo_name, $file) = @_;
55
56
1
20
    my $repository = $self->model('Repository', { name => $repo_name });
57
58
1
6
    my $final_part = pop @$file;
59
1
2
    my $filetype;
60
1
3
    if ($final_part =~ s/\.(?<filetype>[a-z0-9]+)$//) {
61
1
1
1
0
461
278
468
0
        $filetype = $+{filetype};
62    }
63
64
1
3
    my $path = join '/', @$file, $final_part;
65
1
5
    return $repository->file({ path => $path, filetype => $filetype });
66}
67
68 - 73
=head2 download_file

Returns the file in the response with a MIME type of "application/octet". This
should force the browser to treat it like a download.

=cut
74
75sub download_file {
76
1
1
2
    my ($self, $ctx) = @_;
77
78
1
13
    my $repo_name = $ctx->request->path_parameters->{repository};
79
1
64
    my $path      = $ctx->request->path_parameters->{file};
80
81
1
27
    my $file      = $self->lookup_file($repo_name, $path);
82
83
1
22
    $ctx->response->content_type('application/octet');
84
1
179
    $ctx->response->body([ scalar $file->fetch ]);
85}
86
87 - 92
=head2 view_file

Returns the file in the response with a MIME type reported by
L<Yukki::Model::File/media_type>.

=cut
93
94sub view_file {
95
0
1
    my ($self, $ctx) = @_;
96
97
0
    my $repo_name = $ctx->request->path_parameters->{repository};
98
0
    my $path      = $ctx->request->path_parameters->{file};
99
100
0
    my $file      = $self->lookup_file($repo_name, $path);
101
102
0
    $ctx->response->content_type($file->media_type);
103
0
    $ctx->response->body([ scalar $file->fetch ]);
104}
105
106 - 110
=head2 rename_file

Handles attachment renaming via the page rename controller.

=cut
111
112sub rename_file {
113
0
1
    my ($self, $ctx) = @_;
114
115
0
    my $repo_name = $ctx->request->path_parameters->{repository};
116
0
    my $path      = $ctx->request->path_parameters->{file};
117
118
0
    my $file      = $self->lookup_file($repo_name, $path);
119
120
0
    if ($ctx->request->method eq 'POST') {
121
0
        my $new_name = $ctx->request->parameters->{yukkiname_new};
122
123
0
        my $part = qr{[_a-z0-9-.]+(?:\.[_a-z0-9-]+)*}i;
124
0
        if ($new_name =~ m{^$part(?:/$part)*$}) {
125
0
            if (my $user = $ctx->session->{user}) {
126
0
                $file->author_name($user->{name});
127
0
                $file->author_email($user->{email});
128            }
129
130
0
            my $new_file = $file->rename({
131                full_path => $new_name,
132                comment   => 'Renamed ' . $file->full_path . ' to ' . $new_name,
133            });
134
135
0
            my $parent = $new_file->parent // $file->repository->default_file;
136
137
0
            $ctx->response->redirect(join '/',
138                '/page/edit', $repo_name, $parent->full_path);
139
0
            return;
140        }
141        else {
142
0
            $ctx->add_errors('the new name must contain only letters, numbers, underscores, dashes, periods, and slashes');
143        }
144    }
145
146    $ctx->response->body(
147
0
        $self->view('Attachment')->rename($ctx, {
148            title       => $file->title,
149            repository  => $repo_name,
150            page        => $file->full_path,
151            file        => $file,
152        })
153    );
154}
155
156 - 160
=head2 remove_file

Displays the remove confirmation.

=cut
161
162sub remove_file {
163
0
1
    my ($self, $ctx) = @_;
164
165
0
    my $repo_name = $ctx->request->path_parameters->{repository};
166
0
    my $path      = $ctx->request->path_parameters->{file};
167
168
0
    my $file      = $self->lookup_file($repo_name, $path);
169
170
0
    my $return_to = $file->parent // $file->repository->default_file;
171
172
0
    my $confirmed = $ctx->request->body_parameters->{confirmed};
173
0
    if ($ctx->request->method eq 'POST' and $confirmed) {
174
175
0
        if (my $user = $ctx->session->{user}) {
176
0
            $file->author_name($user->{name});
177
0
            $file->author_email($user->{email});
178        }
179
180        $file->remove({
181
0
            comment => 'Removing ' . $file->full_path . ' from repository.',
182        });
183
184
0
        $ctx->response->redirect(join '/', '/page/view', $repo_name, $return_to->full_path);
185
0
        return;
186    }
187
188    $ctx->response->body(
189
0
        $self->view('Attachment')->remove($ctx, {
190            title       => $file->title,
191            repository  => $repo_name,
192            page        => $file->full_path,
193            file        => $file,
194            return_link => join('/', '/page/view', $repo_name, $return_to->full_path),
195        })
196    );
197}
198
199 - 203
=head2 upload_file

This uploads the file given into the wiki.

=cut
204
205sub upload_file {
206
0
1
    my ($self, $ctx) = @_;
207
208
0
    my $repo_name = $ctx->request->path_parameters->{repository};
209
0
    my $path      = $ctx->request->path_parameters->{file};
210
211
0
    my $file      = $self->lookup_file($repo_name, $path);
212
213
0
    if (my $user = $ctx->session->{user}) {
214
0
        $file->author_name($user->{name});
215
0
        $file->author_email($user->{email});
216    }
217
218
0
    my $upload = $ctx->request->uploads->{file};
219
0
    $file->store({
220        filename => $upload->tempname,
221        comment  => 'Uploading file ' . $upload->filename,
222    });
223
224
0
    $ctx->response->content_type('application/json');
225
0
    $ctx->response->body(
226        encode_json({
227            viewable        => 1,
228            repository_path => join('/', $repo_name, $file->full_path),
229        })
230    );
231}
232
2331;