root/branches/release-41/lib/MT/CMS/Category.pm @ 2657

Revision 2657, 14.4 kB (checked in by fumiakiy, 17 months ago)

Adjust ancestry upon moving a category so the family relationship won't contradict with each other. BugId:80296

  • Property svn:keywords set to Id Revision
Line 
1package MT::CMS::Category;
2
3use strict;
4use MT::Util qw( encode_url encode_js );
5
6sub edit {
7    my $cb = shift;
8    my ($app, $id, $obj, $param) = @_;
9
10    my $blog = $app->blog;
11
12    if ($id) {
13        $param->{nav_categories} = 1;
14
15        #$param{ "tab_" . ( $app->param('tab') || 'details' ) } = 1;
16
17        # $app->add_breadcrumb($app->translate('Categories'),
18        #                      $app->uri( 'mode' => 'list_cat',
19        #                          args => { blog_id => $obj->blog_id }));
20        # $app->add_breadcrumb($obj->label);
21        my $parent   = $obj->parent_category;
22        my $site_url = $blog->site_url;
23        $site_url .= '/' unless $site_url =~ m!/$!;
24        $param->{path_prefix} =
25          $site_url . ( $parent ? $parent->publish_path : '' );
26        $param->{path_prefix} .= '/' unless $param->{path_prefix} =~ m!/$!;
27        require MT::Trackback;
28        my $tb = MT::Trackback->load( { category_id => $obj->id } );
29
30        if ($tb) {
31            my $list_pref = $app->list_pref('ping');
32            %$param = ( %$param, %$list_pref );
33            my $path = $app->config('CGIPath');
34            $path .= '/' unless $path =~ m!/$!;
35            if ($path =~ m!^/!) {
36                my ($blog_domain) = $blog->archive_url =~ m|(.+://[^/]+)|;
37                $path = $blog_domain . $path;
38            }
39
40            my $script = $app->config('TrackbackScript');
41            $param->{tb}     = 1;
42            $param->{tb_url} = $path . $script . '/' . $tb->id;
43            if ( $param->{tb_passphrase} = $tb->passphrase ) {
44                $param->{tb_url} .= '/' . encode_url( $param->{tb_passphrase} );
45            }
46            $app->load_list_actions( 'ping', $param->{ping_table}[0],
47                'pings' );
48        }
49    }
50    1;
51}
52
53sub list {
54    my $app   = shift;
55    my $q     = $app->param;
56    my $type  = $q->param('_type') || 'category';
57    my $class = $app->model($type);
58
59    my $perms = $app->permissions;
60    my $entry_class;
61    my $entry_type;
62    if ( $type eq 'category' ) {
63        $entry_type = 'entry';
64        return $app->return_to_dashboard( redirect => 1 )
65          unless $perms && $perms->can_edit_categories;
66    }
67    elsif ( $type eq 'folder' ) {
68        $entry_type = 'page';
69        return $app->return_to_dashboard( redirect => 1 )
70          unless $perms && $perms->can_manage_pages;
71    }
72    $entry_class = $app->model($entry_type);
73    my $blog_id = scalar $q->param('blog_id');
74    require MT::Blog;
75    my $blog = MT::Blog->load($blog_id)
76      or return $app->errtrans("Invalid request.");
77    my %param;
78    my %authors;
79    my $data = $app->_build_category_list(
80        blog_id    => $blog_id,
81        counts     => 1,
82        new_cat_id => scalar $q->param('new_cat_id'),
83        type       => $type
84    );
85    if ( $blog->site_url =~ /\/$/ ) {
86        $param{blog_site_url} = $blog->site_url;
87    }
88    else {
89        $param{blog_site_url} = $blog->site_url . '/';
90    }
91    $param{object_loop} = $param{category_loop} = $data;
92    $param{saved} = $q->param('saved');
93    $param{saved_deleted} = $q->param('saved_deleted');
94    $app->load_list_actions( $type, \%param );
95
96    #$param{nav_categories} = 1;
97    $param{sub_object_label} =
98        $type eq 'folder'
99      ? $app->translate('Subfolder')
100      : $app->translate('Subcategory');
101    $param{object_label}        = $class->class_label;
102    $param{object_label_plural} = $class->class_label_plural;
103    $param{object_type}         = $type;
104    $param{entry_label_plural}  = $entry_class->class_label_plural;
105    $param{entry_label}         = $entry_class->class_label;
106    $param{search_label}        = $param{entry_label_plural};
107    $param{search_type}         = $entry_type;
108    $param{screen_id} =
109        $type eq 'folder'
110      ? 'list-folder'
111      : 'list-category';
112    $param{listing_screen}      = 1;
113    $app->add_breadcrumb( $param{object_label_plural} );
114
115    $param{screen_class} = "list-${type}";
116    $param{screen_class} .= " list-category"
117      if $type eq 'folder';    # to piggyback on list-category styles
118    my $tmpl_file = 'list_' . $type . '.tmpl';
119    $app->load_tmpl( $tmpl_file, \%param );
120}
121
122sub save {
123    my $app   = shift;
124    my $q     = $app->param;
125    my $perms = $app->permissions;
126    my $type  = $q->param('_type');
127    my $class = $app->model($type)
128      or return $app->errtrans("Invalid request.");
129
130    if ( $type eq 'category' ) {
131        return $app->errtrans("Permission denied.")
132          unless $perms && $perms->can_edit_categories;
133    }
134    elsif ( $type eq 'folder' ) {
135        return $app->errtrans("Permission denied.")
136          unless $perms && $perms->can_manage_pages;
137    }
138
139    $app->validate_magic() or return;
140
141    my $blog_id = $q->param('blog_id');
142    my $cat;
143    if ( my $moved_cat_id = $q->param('move_cat_id') ) {
144        $cat = $class->load( $q->param('move_cat_id') )
145            or return;
146        move_category($app) or return;
147    }
148    else {
149        for my $p ( $q->param ) {
150            my ($parent) = $p =~ /^category-new-parent-(\d+)$/;
151            next unless ( defined $parent );
152
153            my $label = $q->param($p);
154            $label =~ s/(^\s+|\s+$)//g;
155            next unless ( $label ne '' );
156
157            $cat = $class->new;
158            my $original = $cat->clone;
159            $cat->blog_id($blog_id);
160            $cat->label($label);
161            $cat->author_id( $app->user->id );
162            $cat->parent($parent);
163
164            $app->run_callbacks( 'cms_pre_save.' . $type,
165                $app, $cat, $original )
166              || return $app->errtrans( "Saving [_1] failed: [_2]", $type,
167                $app->errstr );
168
169            $cat->save
170              or return $app->error(
171                $app->translate(
172                    "Saving [_1] failed: [_2]",
173                    $type, $cat->errstr
174                )
175              );
176
177            # Now post-process it.
178            $app->run_callbacks( 'cms_post_save.' . $type,
179                $app, $cat, $original );
180        }
181    }
182
183    return $app->errtrans( "The [_1] must be given a name!", $type )
184      if !$cat;
185
186    $app->redirect(
187        $app->uri(
188            'mode' => 'list_cat',
189            args   => {
190                _type      => $type,
191                blog_id    => $blog_id,
192                saved      => 1,
193                new_cat_id => $cat->id
194            }
195        )
196    );
197}
198
199sub category_add {
200    my $app  = shift;
201    my $q    = $app->param;
202    my $type = $q->param('_type') || 'category';
203    my $pkg  = $app->model($type);
204    my $data = $app->_build_category_list(
205        blog_id => scalar $q->param('blog_id'),
206        type    => $type
207    );
208    my %param;
209    $param{'category_loop'} = $data;
210    $app->add_breadcrumb( $app->translate( 'Add a [_1]', $pkg->class_label ) );
211    $param{object_type}  = $type;
212    $param{object_label} = $pkg->class_label;
213    $app->load_tmpl( 'popup/category_add.tmpl', \%param );
214}
215
216sub category_do_add {
217    my $app    = shift;
218    my $q      = $app->param;
219    my $type   = $q->param('_type') || 'category';
220    my $author = $app->user;
221    my $pkg    = $app->model($type);
222    $app->validate_magic() or return;
223    my $name = $q->param('label')
224      or return $app->error( $app->translate("No label") );
225    $name =~ s/(^\s+|\s+$)//g;
226    return $app->errtrans("Category name cannot be blank.")
227      if $name eq '';
228    my $parent   = $q->param('parent') || '0';
229    my $cat      = $pkg->new;
230    my $original = $cat->clone;
231    $cat->blog_id( scalar $q->param('blog_id') );
232    $cat->author_id( $app->user->id );
233    $cat->label($name);
234    $cat->parent($parent);
235
236    if ( !$author->is_superuser ) {
237        $app->run_callbacks( 'cms_save_permission_filter.' . $type,
238            $app, undef )
239          || return $app->error(
240            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
241    }
242
243    my $filter_result = $app->run_callbacks( 'cms_save_filter.' . $type, $app )
244      || return;
245
246    $app->run_callbacks( 'cms_pre_save.' . $type, $app, $cat, $original )
247      || return;
248
249    $cat->save or return $app->error( $cat->errstr );
250
251    # Now post-process it.
252    $app->run_callbacks( 'cms_post_save.' . $type, $app, $cat, $original )
253      or return;
254
255    my $id = $cat->id;
256    $name = encode_js($name);
257    my %param = ( javascript => <<SCRIPT);
258    o.doAddCategoryItem('$name', '$id');
259SCRIPT
260    $app->load_tmpl( 'reload_opener.tmpl', \%param );
261}
262
263sub js_add_category {
264    my $app = shift;
265    unless ( $app->validate_magic ) {
266        return $app->json_error( $app->translate("Invalid request.") );
267    }
268    my $user    = $app->user;
269    my $blog_id = $app->param('blog_id');
270    my $perms   = $app->permissions;
271    my $type    = $app->param('_type') || 'category';
272    my $class   = $app->model($type);
273    if ( !$class ) {
274        return $app->json_error( $app->translate("Invalid request.") );
275    }
276
277    my $label = $app->param('label');
278    my $enc   = $app->config->PublishCharset;
279
280    # XMLHttpRequest always send text in UTF-8... right?
281    if ( 'utf-8' ne lc($enc) ) {
282        $label = MT::I18N::encode_text( $label, 'utf-8', $enc );
283    }
284    my $basename = $app->param('basename');
285    if ( !defined($label) || ( $label =~ m/^\s*$/ ) ) {
286        return $app->json_error( $app->translate("Invalid request.") );
287    }
288
289    my $blog = $app->blog;
290    if ( !$blog ) {
291        return $app->json_error( $app->translate("Invalid request.") );
292    }
293
294    my $parent;
295    if ( my $parent_id = $app->param('parent') ) {
296        if ( $parent_id != -1 ) {    # special case for 'root' folder
297            $parent = $class->load( { id => $parent_id, blog_id => $blog_id } );
298            if ( !$parent ) {
299                return $app->json_error( $app->translate("Invalid request.") );
300            }
301        }
302    }
303
304    my $obj      = $class->new;
305    my $original = $obj->clone;
306
307    if (
308        !$app->run_callbacks(
309            'cms_save_permission.' . $type,
310            $app, $obj, $original
311        )
312      )
313    {
314        return $app->json_error( $app->translate("Permission denied.") );
315    }
316
317    $obj->label($label);
318    $obj->basename($basename)   if $basename;
319    $obj->parent( $parent->id ) if $parent;
320    $obj->blog_id($blog_id);
321    $obj->author_id( $user->id );
322    $obj->created_by( $user->id );
323
324    if (
325        !$app->run_callbacks( 'cms_pre_save.' . $type, $app, $obj, $original ) )
326    {
327        return $app->json_error( $app->errstr );
328    }
329
330    $obj->save;
331
332    $app->run_callbacks( 'cms_post_save.' . $type, $app, $obj, $original );
333
334    return $app->json_result(
335        {
336            id       => $obj->id,
337            basename => $obj->basename
338        }
339    );
340}
341
342sub can_view {
343    my ( $eh, $app, $id ) = @_;
344    my $perms = $app->permissions;
345    return $perms->can_edit_categories();
346}
347
348sub can_save {
349    my ( $eh, $app, $id ) = @_;
350    my $perms = $app->permissions;
351    return $perms->can_edit_categories();
352}
353
354sub can_delete {
355    my ( $eh, $app, $obj ) = @_;
356    return 1 if $app->user->is_superuser();
357    my $perms = $app->permissions;
358    return $perms && $perms->can_edit_categories();
359}
360
361sub pre_save {
362    my $eh = shift;
363    my ( $app, $obj ) = @_;
364    my $pkg = $app->model('category');
365    if ( defined( my $pass = $app->param('tb_passphrase') ) ) {
366        $obj->{__tb_passphrase} = $pass;
367    }
368    my @siblings = $pkg->load(
369        {
370            parent  => $obj->parent,
371            blog_id => $obj->blog_id
372        }
373    );
374    foreach (@siblings) {
375        next if $obj->id && ( $_->id == $obj->id );
376        return $eh->error(
377            $app->translate(
378"The category name '[_1]' conflicts with another category. Top-level categories and sub-categories with the same parent must have unique names.",
379                $_->label
380            )
381        ) if $_->label eq $obj->label;
382        return $eh->error(
383            $app->translate(
384"The category basename '[_1]' conflicts with another category. Top-level categories and sub-categories with the same parent must have unique basenames.",
385                $_->label
386            )
387        ) if $_->basename eq $obj->basename;
388    }
389    1;
390}
391
392sub post_save {
393    my $eh = shift;
394    my ( $app, $obj, $original ) = @_;
395
396    if ( !$original->id ) {
397        $app->log(
398            {
399                message => $app->translate(
400                    "Category '[_1]' created by '[_2]'", $obj->label,
401                    $app->user->name
402                ),
403                level    => MT::Log::INFO(),
404                class    => 'category',
405                category => 'new',
406            }
407        );
408    }
409    1;
410}
411
412sub save_filter {
413    my $eh = shift;
414    my ($app) = @_;
415    return $app->errtrans( "The name '[_1]' is too long!",
416        $app->param('label') )
417      if ( length( $app->param('label') ) > 100 );
418    return 1;
419}
420
421sub post_delete {
422    my ( $eh, $app, $obj ) = @_;
423
424    $app->log(
425        {
426            message => $app->translate(
427                "Category '[_1]' (ID:[_2]) deleted by '[_3]'",
428                $obj->label, $obj->id, $app->user->name
429            ),
430            level    => MT::Log::INFO(),
431            class    => 'system',
432            category => 'delete'
433        }
434    );
435}
436
437sub _adjust_ancestry {
438    my ( $cat, $ancestor ) = @_;
439    return unless $cat && $ancestor;
440    if ( $ancestor->parent && ( $ancestor->parent != $cat->id ) ) {
441        _adjust_ancestry($cat, $ancestor->parent_category);
442    }
443    else {
444        $ancestor->parent($cat->parent);
445        $ancestor->save;
446    }
447}
448
449sub move_category {
450    my $app   = shift;
451    my $type  = $app->param('_type');
452    my $class = $app->model($type)
453      or return $app->errtrans("Invalid request.");
454    $app->validate_magic() or return;
455
456    my $cat        = $class->load( $app->param('move_cat_id') )
457        or return;
458
459    my $new_parent_id = $app->param('move-radio');
460
461    return 1 if ( $new_parent_id == $cat->parent );
462
463    if ( $new_parent_id ) {
464        my $new_parent = $class->load( $new_parent_id )
465            or return;
466       if ( $cat->is_ancestor( $new_parent ) ) {
467            _adjust_ancestry( $cat, $new_parent );
468        }
469    }
470    $cat->parent($new_parent_id);
471    my @siblings = $class->load(
472        {
473            parent  => $cat->parent,
474            blog_id => $cat->blog_id
475        }
476    );
477    foreach (@siblings) {
478
479        # FIXME: Language should support both category / folder
480        return $app->errtrans(
481"The category name '[_1]' conflicts with another category. Top-level categories and sub-categories with the same parent must have unique names.",
482            $_->label
483        ) if $_->label eq $cat->label;
484    }
485
486    $cat->save
487      or return $app->error(
488        $app->translate( "Saving category failed: [_1]", $cat->errstr ) );
489}
490
4911;
Note: See TracBrowser for help on using the browser.