File Coverage

blib/lib/MojoMojo/Controller/PageAdmin.pm
Criterion Covered Total %
statement 81 152 53.2
branch 27 74 36.4
condition 9 39 23.0
subroutine 12 15 80.0
pod 5 5 100.0
total 134 285 47.0


line stmt bran cond sub pod time code
1             package MojoMojo::Controller::PageAdmin;
2 35     35   12870 use warnings;
  35         84  
  35         1274  
3 35     35   176 use strict;
  35         79  
  35         637  
4 35     35   156 use parent 'Catalyst::Controller::HTML::FormFu';
  35         76  
  35         166  
5              
6             eval {require Syntax::Highlight::Engine::Kate};
7             my $kate_installed = !$@;
8              
9             =head1 NAME
10              
11             MojoMojo::Controller::PageAdmin - MojoMojo Page Administration
12              
13             =head1 SYNOPSIS
14              
15             See L<MojoMojo>
16              
17             =head1 DESCRIPTION
18              
19             Methods for updating pages: edit, rollback, permissions change, rename.
20              
21             =head1 METHODS
22              
23             =head2 auto
24              
25             Check that user is logged in and has rights to this page.
26              
27             =cut
28              
29             =head2 unauthorized
30              
31             Private action to return a 403 with an explanatory template.
32              
33             =cut
34              
35             sub unauthorized : Private {
36 2     2   1752 my ( $self, $c, $operation ) = @_;
37 2         10 $c->stash->{template} = 'message.tt';
38 2   0     117 $c->stash->{message} ||= $c->loc('No permissions to x this page', $operation || $c->loc('update'));
      33        
39 2 50       144 $c->response->status(403) unless $c->response->status; # 403 Forbidden
40 2         215 return 0;
41 35     35   5977 }
  35         86  
  35         208  
42              
43             sub auto : Private {
44 20     20 1 9653 my ( $self, $c ) = @_;
45             $c->forward('/user/login')
46             if $c->req->params->{pass} # XXX use case?
47 20 50 33     88 && !$c->stash->{user};
48              
49             # everyone can edit with anon mode enabled.
50 20 50       1819 return 1 if MojoMojo->pref('anonymous_user');
51 0         0 my $user = $c->stash->{user};
52 0 0 0     0 return 1 if $user && $user->can_edit( $c->stash->{path} );
53 0 0 0     0 return 1 if $user && !$c->pref('restricted_user');
54 0         0 $c->detach('unauthorized', [$c->loc('edit')]);
55 35     35   439365 }
  35         94  
  35         180  
56              
57             =head2 edit
58              
59             This action will display the edit form, then save the previous
60             revision, and create a new one based on the posted content.
61             After saving, it will forward to the highlight action.
62              
63             =cut
64              
65             sub edit : Global FormConfig {
66 20     20 1 883581 my ( $self, $c, $path ) = @_;
67              
68             # Set up the basics. Log in if there's a user.
69 20         115 my $form = $c->stash->{form};
70 20         1329 my $stash = $c->stash;
71 20         1272 $stash->{template} = 'page/edit.tt';
72              
73 20 50       166 my $user_id = $c->user_exists ? $c->user->obj->id : 1; # Anon edit
74              
75 20         10266 my ( $path_pages, $proto_pages ) = @$stash{qw/ path_pages proto_pages /};
76              
77             # we should always have at least "/" in path pages. if we don't,
78             # we must not have had these structures in the stash
79 20 50       109 unless ($path_pages) {
80 0         0 ( $path_pages, $proto_pages ) =
81             $c->model('DBIC::Page')->path_pages($path);
82             }
83              
84             # the page we're editing is at the end of either path_pages or
85             # proto_pages, depending on whether or not the page already exists
86 20 100       102 my $page = (
87             @$proto_pages > 0
88             ? $proto_pages->[-1]
89             : $path_pages->[-1]
90             );
91 20         80 @$stash{qw/ path_pages proto_pages /} = ( $path_pages, $proto_pages );
92              
93             my $perms =
94 20 50       98 $c->check_permissions( $stash->{'path'}, $c->user_exists ? $c->user->obj : undef );
95 20 100       129 my $permtocheck = ( @$proto_pages > 0 ? 'create' : 'edit' );
96 20 100       180 my $loc_permtocheck = $permtocheck eq 'create'
97             ? $c->loc('create')
98             : $c->loc('edit');
99              
100             # TODO this should be caught in the auto action. To reproduce, disable "Edit allowed by default"
101             # in Site settings, then go to /.edit
102 20 50       8849 if ( !$perms->{$permtocheck} ) {
103 0 0       0 my $name = ref($page) eq 'HASH' ? $page->{name} : $page->name;
104             $stash->{message} =
105 0         0 $c->loc( 'Permission denied to x x', [ $loc_permtocheck, $name ] );
106 0         0 $c->detach('unauthorized');
107             }
108             # TODO in the use case above, the message below should be displayed. However, that never happens
109 20 50 33     122 if ( $user_id == 1 && !$c->pref('anonymous_user') ) {
110 0         0 $c->stash->{message} = $c->loc('Anonymous edit disabled');
111 0         0 $c->detach('unauthorized');
112             }
113              
114             # for anonymous users, use CAPTCHA, if enabled
115 20 50 33     107 if ( $user_id == 1 && $c->pref('use_captcha') ) {
116 0   0     0 my $captcha_lang = $c->session->{lang} || $c->pref('default_lang') ;
117 0         0 $c->stash->{captcha} = $form->element({
118             type => 'reCAPTCHA',
119             name => 'captcha',
120             recaptcha_options => {
121             lang => $captcha_lang,
122             theme => 'white'
123             }
124             });
125 0         0 $form->process;
126             }
127              
128             # prepare the list of available syntax highlighters
129 20 50       84 if ($kate_installed) {
130 20         262 my $syntax = new Syntax::Highlight::Engine::Kate;
131             # 'Alerts' is a hidden Kate module, so delete it from list
132 20         10052 $c->stash->{syntax_formatters} = [ grep ( !/^Alerts$/ , $syntax->languageList() ) ];
133             }
134              
135 20 100       20391 if ( $form->submitted_and_valid ) {
136              
137 6         6605 my $valid = $form->params;
138 6         2503 $valid->{creator} = $user_id;
139              
140 6 100       27 if (@$proto_pages) { # page doesn't exist yet
141              
142 4         22 $path_pages = $c->model('DBIC::Page')->create_path_pages(
143             path_pages => $path_pages,
144             proto_pages => $proto_pages,
145             creator => $user_id,
146             );
147 4         18 $page = $path_pages->[-1];
148            
149              
150             # update the pages that wanted the new one
151              
152             }
153              
154 6         426 $stash->{content} = $page->content;
155 6         12955 $c->model("DBIC::Page")->set_paths(@$path_pages);
156              
157             # setup redirect back to edits or view page mode.
158 6         74 my $redirect = $c->uri_for( $c->stash->{path} );
159 6 50       47 if ( $form->params->{submit} eq $c->localize('Save') ) {
160 6         5516 $redirect .= '.edit';
161             }
162            
163             # No need to update if we have no difference between browser and db.
164 6 100 100     60 if ( $c->stash->{content} && ($c->stash->{content}->body eq $form->params->{body}) ) {
165 1         547 $c->res->redirect($redirect);
166 1         246 return;
167             }
168            
169             # If we get here it means we have some difference between wiki page in browser and db.
170             # TODO: Is the discard_changes necessary? Why are we discarding local changes?
171             # Are there even any local changes to $page?
172 5         1099 $page->discard_changes;
173              
174             # Check for changes made by another user to the same base revision.
175 5 50 66     31635 if( $c->stash->{content} &&
176             $c->req->params->{version} != $c->stash->{content}->version ) {
177 0         0 $c->stash->{message}=$c->loc('Someone else changed the page while you edited. Your changes has been merged. Please review and save again');
178             my $orig_content = $c->model("DBIC::Content")->find(
179             {
180             page => $page->id,
181             version => $c->req->params->{version},
182             }
183 0         0 );
184             $c->stash->{merged_body} ||= $orig_content->merge_content(
185             $c->stash->{content},
186             $form->params->{body},
187 0   0     0 $c->loc('THEIR CHANGES'),
188             $c->loc('YOUR CHANGES'),
189             $c->loc('END OF CONFLICT'));
190 0         0 return;
191             }
192              
193             # Format content body and store the result in content.precompiled
194             # This speeds up MojoMojo page rendering on /.view actions
195 5         711 my $precompiled_body = $valid->{'body'};
196 5         51 MojoMojo->call_plugins( 'format_content', \$precompiled_body, $c, $page );
197              
198             # Make precompiled empty when we have any of: redirect, comment or include
199 5 50       74 $valid->{'precompiled'} = $c->stash->{precompile_off} ? '' : $precompiled_body;
200              
201 5         512 $page->update_content(%$valid);
202              
203             # update the search index with the new content
204 5         18055 $c->model("DBIC::Page")->set_paths($page);
205 5 50       23 $c->model('Search')->index_page($page)
206             unless $c->pref('disable_search');
207 5         32066 $page->content->store_links();
208              
209 5         13298 $c->res->redirect($redirect);
210             }
211             else {
212              
213             # if we have missing or invalid fields, display the edit form.
214             # this will always happen on the initial request
215 14         541 $stash->{page} = $page;
216              
217             # Insert an attachment, or inline an image.
218 14         77 my %attachment_template = (
219             insert_attachment => 'page/insert.tt',
220             inline_image_attachment => 'page/inline_image.tt',
221             );
222 14         56 foreach my $attachment_action ( keys %attachment_template ) {
223 28 50       1009 if (my $attachment_id = $c->req->query_params->{$attachment_action} ) {
224 0         0 my $saved_stash = $stash;
225              
226 0         0 my $attachment = $c->model("DBIC::Attachment")
227             ->find( { id => $attachment_id } );
228              
229 0         0 $c->stash( { att => $attachment } );
230              
231 0         0 my $insert_text = $c->view('TT')->render( $c, $attachment_template{$attachment_action} );
232 0         0 $insert_text =~ s/^\s+|\s+$//;
233              
234 0         0 $c->stash($saved_stash);
235              
236 0         0 $page->content->body( $page->content->body . "\n\n" . $insert_text . "\n\n" );
237             }
238             }
239             }
240 35     35   58345 } # end sub edit
  35         89  
  35         183  
241              
242              
243             =head2 permissions
244              
245             This action allows page permissions to be displayed and edited.
246              
247             =cut
248              
249             sub permissions : Global {
250 0     0 1 0 my ( $self, $c, $path ) = @_;
251              
252 0         0 my $stash = $c->stash;
253 0         0 $stash->{template} = 'page/permissions.tt';
254              
255 0         0 my @path_elements = $c->_expand_path_elements( $stash->{path} );
256 0         0 my $current_path = pop @path_elements;
257              
258 0         0 my $data = $c->get_permissions_data( $current_path, \@path_elements );
259             my $current_data =
260 0 0       0 exists $data->{$current_path} ? $data->{$current_path} : {};
261              
262 0         0 my @roles = $c->model('DBIC::Role')->active_roles->all;
263 0         0 my %roles = map { $_->id => $_->name } @roles;
  0         0  
264              
265 0         0 my %current;
266              
267             # build current page permissions hash for each role
268 0         0 for my $role (@roles) {
269             $current{ $role->name } =
270             exists $current_data->{ $role->id }
271 0 0       0 ? $current_data->{ $role->id }
272             : undef;
273             }
274              
275             # same as above: sort elements to avoid nasty TT code
276             $stash->{current_perms} = [
277             map {
278 0         0 {
279             role_name => $_,
280             inherited => $current{$_} ? 1 : 0,
281             perms => $current{$_} && $current{$_}->{page},
282             subpages => exists $current{$_}->{subpages}
283 0 0 0     0 ? 1
    0          
284             : 0
285             }
286             }
287             sort keys %current
288             ];
289              
290 0         0 my %inherited;
291 0         0 my $parent_path = $path_elements[-1];
292              
293             # build inherited permissions hash
294 0         0 for my $path ( keys %$data ) {
295              
296             # might have additional data (if cached)
297 0 0 0     0 next unless ($parent_path && $parent_path =~ /^$path/);
298 0 0       0 next if $path eq $current_path;
299 0         0 my $path_perms = $data->{$path};
300 0         0 for my $role ( keys %$path_perms ) {
301 0 0       0 next unless exists $roles{$role};
302 0         0 my $role_perms = $path_perms->{$role};
303             $inherited{$path}{ $roles{$role} } = $role_perms->{subpages}
304 0 0       0 if exists $role_perms->{subpages};
305             }
306             }
307              
308             # sort elements to avoid nasty TT code
309             $stash->{inherited_perms} = [
310 0         0 map { { path => $_, perms => $inherited{$_} } }
311 0         0 sort { length $a <=> length $b } keys %inherited
  0         0  
312             ];
313 35     35   42914 }
  35         87  
  35         184  
314              
315             =head2 rollback
316              
317             This action will revert a page to a older revision.
318              
319             =cut
320              
321             sub rollback : Global {
322 0     0 1 0 my ( $self, $c, $page ) = @_;
323 0 0       0 if ($c->req->method ne 'POST') {
324             # general error - we want a POST
325 0         0 $c->res->status(400);
326 0         0 $c->detach('unauthorized', [$c->loc('rollback')]);
327             }
328              
329 0 0       0 if ( $c->req->param('rev') ) {
330             # TODO this needs to do a proper versioned rollback, via
331             # $page->add_version( content_version => $c->req->param('rev')
332             # The problem is that the page_version table doesn't have a content_version field
333             # We could cannibalize the parent_version field, which is dummily always '1'
334 0         0 $c->stash->{page}->content_version( $c->req->param('rev') );
335 0         0 $c->stash->{page}->update;
336 0         0 undef $c->req->params->{rev};
337 0         0 $c->forward('/page/view');
338             }
339 35     35   35390 }
  35         94  
  35         155  
340              
341             =head2 title ( .info/title )
342              
343             AJAX method for renaming a page. Creates a new
344             L<PageVersion|MojoMojo::Schema::Result::PageVersion> with the rename,
345             so that the renaming itself could in the future be shown in the page
346             history.
347              
348             =cut
349              
350             sub title : Path('/info/title') {
351 0     0 1   my ( $self, $c ) = @_;
352 0           my $page = $c->stash->{page};
353 0 0         my $user_id = $c->user_exists ? $c->user->obj->id : 1; # Anon edit
354            
355 0 0         if ($c->req->method ne 'POST') {
356             # general error - we want a POST
357 0           $c->res->status(400);
358 0           $c->detach('unauthorized', [$c->loc('rename via non-POST method')]);
359             }
360            
361 0 0         if ( $c->req->param('update_value') ) {
362 0           my $page_version_new = $page->add_version(
363             creator => $user_id,
364             name_orig => $c->req->param('update_value'),
365             );
366 0           $c->res->body( $page_version_new->name_orig );
367             } else {
368             # User attempted to rename the page to ''. Deny that.
369 0           $c->res->body( $page->name_orig );
370             }
371            
372 35     35   35670 }
  35         85  
  35         160  
373             =head1 AUTHOR
374              
375             Marcus Ramberg <mramberg@cpan.org>
376              
377             =head1 LICENSE
378              
379             This library is free software. You can redistribute it and/or modify
380             it under the same terms as Perl itself.
381              
382             =cut
383              
384             1;