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

# File Coverage

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

line | stmt | bran | cond | sub | pod | time | code
--- | --- | --- | --- | --- | --- | --- | ---
1 |  |  |  |  |  |  | package Yukki::Web::Controller::Attachment;
2 |
3 | 2 2 |  |  | 2 |  | 616 7 | use v5.24;
4 | 2 2 2 |  |  | 2 |  | 7 14 21 | use utf8;
5 | 2 2 2 |  |  | 2 |  | 30 2 17 | use Moo;
6 |
7 |  |  |  |  |  |  | with 'Yukki::Web::Controller';
8 |
9 | 2 2 2 |  |  | 2 |  | 2238 11824 9 | use JSON;
10 | 2 2 2 |  |  | 2 |  | 360 4 7 | use Yukki::Error qw( http_throw );
11 |
12 | 2 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 |
28 |  |  |  |  |  |  | sub fire {
29 | 2 |  |  | 2 | 1 | 5 | my ($self, $ctx) = @_;
30 |
31 | 2 |  |  |  |  | 26 | my $action = $ctx->request->path_parameters->{action};
32 | 2 1 | 100 50 50 50 50 |  |  |  | 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 |
53 |  |  |  |  |  |  | sub lookup_file {
54 | 1 |  |  | 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 | 50 |  |  |  | 3 | if ($final_part =~ s/\.(?<filetype>[a-z0-9]+)$//) {
61 | 1 1 1 0 |  |  | 1 |  | 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 |
75 |  |  |  |  |  |  | sub download_file {
76 | 1 |  |  | 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 |
94 |  |  |  |  |  |  | sub view_file {
95 | 0 |  |  | 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 |
112 |  |  |  |  |  |  | sub rename_file {
113 | 0 |  |  | 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 | 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 | 0 |  |  |  |  | if ($new_name =~ m{^$part(?:/$part)*$}) {
125 | 0 | 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 |  | 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 |
162 |  |  |  |  |  |  | sub remove_file {
163 | 0 |  |  | 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 |  | 0 |  |  |  | my $return_to = $file->parent // $file->repository->default_file;
171 |
172 | 0 |  |  |  |  |  | my $confirmed = $ctx->request->body_parameters->{confirmed};
173 | 0 | 0 | 0 |  |  |  | if ($ctx->request->method eq 'POST' and $confirmed) {
174 |
175 | 0 | 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 |
205 |  |  |  |  |  |  | sub upload_file {
206 | 0 |  |  | 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 | 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 |
233 |  |  |  |  |  |  | 1;