root/branches/release-34/lib/MT/CMS/Tag.pm @ 1873

Revision 1873, 15.7 kB (checked in by bchoate, 20 months ago)

Applied patches from Ogawa-san to add an optimized 'exist' method for testing for existing rows. BugId:69661

  • Property svn:keywords set to Id Revision
Line 
1package MT::CMS::Tag;
2
3use strict;
4
5sub list {
6    my $app = shift;
7    my %param;
8    my $filter_key = $app->param('filter_key') || 'entry';
9    my $type       = $app->param('_type')      || $filter_key;
10    my $plural;
11    $param{TagObjectType} = $type;
12    $param{Package}       = $app->model($type);
13
14    if ( !$app->blog && !$app->user->is_superuser ) {
15        return $app->errtrans("Permission denied.");
16    }
17
18    my $perms = $app->permissions;
19    if ( $app->blog && ( ( !$perms ) || ( $perms && $perms->is_empty ) ) ) {
20        $app->delete_param('blog_id');
21        return $app->return_to_dashboard( permission => 1 );
22    }
23
24    if ( $param{Package}->can('class_label_plural') ) {
25        $plural = $param{Package}->class_label_plural();
26    }
27    else {
28        if ( $type =~ m/y$/ ) {
29            $plural = $type;
30            $plural =~ s/y$/ies/;
31        }
32        else {
33            $plural = $type . 's';
34        }
35        $plural =~ s/(.*)/\u$1/;
36    }
37
38    $param{TagObjectLabelPlural} = $plural;
39    $app->model($type) or return;
40    unless ( UNIVERSAL::can( $param{Package}, 'tag_count' ) ) {
41        return $app->errtrans("Invalid type");
42    }
43    list_tag_for($app, %param);
44}
45
46sub rename_tag {
47    my $app     = shift;
48    my $perms   = $app->permissions;
49    my $blog_id = $app->blog->id if $app->blog;
50    ( $blog_id && $perms && $perms->can_edit_tags )
51      || ( $app->user->is_superuser() )
52      or return $app->errtrans("Permission denied.");
53    my $id        = $app->param('__id');
54    my $name      = $app->param('tag_name')
55      or return $app->error( $app->translate("New name of the tag must be specified.") );
56    my $obj_type  = $app->param('__type') || 'entry';
57    my $obj_class = $app->model($obj_type);
58    my $tag_class = $app->model('tag');
59    my $ot_class  = $app->model('objecttag');
60    my $tag       = $tag_class->load($id)
61      or return $app->error( $app->translate("No such tag") );
62    my $tag2 =
63      $tag_class->load( { name => $name }, { binary => { name => 1 } } );
64
65    if ($tag2) {
66        return $app->call_return if $tag->id == $tag2->id;
67    }
68
69    my $terms = { tag_id => $tag->id };
70    $terms->{blog_id} = $blog_id if $blog_id;
71
72    my $iter = $obj_class->load_iter(
73        {
74            (
75                $obj_type =~ m/asset/i
76                ? ( class => '*' )
77                : ( class => $obj_type )
78            )
79        },
80        { join => MT::ObjectTag->join_on( 'object_id', $terms ) }
81    );
82    my @tagged_objects;
83    while ( my $o = $iter->() ) {
84        $o->remove_tags( $tag->name );
85        $o->add_tags($name);
86        push @tagged_objects, $o;
87    }
88    $_->save foreach @tagged_objects;
89
90    if ($tag2) {
91        $app->add_return_arg( merged => 1 );
92    }
93    else {
94        $app->add_return_arg( renamed => 1 );
95    }
96    $app->call_return;
97}
98
99sub js_tag_check {
100    my $app       = shift;
101    my $name      = $app->param('tag_name');
102    my $blog_id   = $app->param('blog_id');
103    my $type      = $app->param('_type') || 'entry';
104    my $tag_class = $app->model('tag')
105      or return $app->json_error( $app->translate("Invalid request.") );
106    my $tag =
107      $tag_class->load( { name => $name }, { binary => { name => 1 } } );
108    my $class = $app->model($type)
109      or $app->json_error( $app->translate("Invalid request.") );
110    if ( $tag && $blog_id ) {
111        my $ot_class = $app->model('objecttag');
112        my $exist    = $ot_class->exist(
113            {
114                object_datasource => $class->datasource,
115                blog_id           => $blog_id,
116                tag_id            => $tag->id
117            }
118        );
119        undef $tag unless $exist;
120    }
121    return $app->json_result( { exists => $tag ? 'true' : 'false' } );
122}
123
124sub js_tag_list {
125    my $app     = shift;
126    my $blog_id = $app->param('blog_id');
127    my $type    = $app->param('_type') || 'entry';
128
129    my $class = $app->model($type)
130      or return $app->json_error( $app->translate("Invalid request.") );
131    my $result;
132    if (
133        my $tag_list = MT::Tag->cache(
134            blog_id => $blog_id,
135            class   => $class,
136            private => 1,
137        )
138      )
139    {
140        $result = { tags => $tag_list };
141    }
142    else {
143        $result = { tags => [] };
144    }
145    $app->json_result($result);
146}
147
148sub js_recent_entries_for_tag {
149    my $app          = shift;
150    my $user         = $app->user or return;
151    my $tag_class    = $app->model('tag') or return;
152    my $objtag_class = $app->model('objecttag') or return;
153    my $limit        = $app->param('limit') || 10;
154    my $obj_ds       = $app->param('_type') || 'entry';
155    my $blog_id      = $app->param('blog_id');
156    my $obj_class    = $app->model($obj_ds) or return;
157    my $tag_name     = $app->param('tag') or return;
158    if ( 'utf-8' ne lc( $app->config->PublishCharset) ) {
159        $tag_name = MT::I18N::encode_text( $tag_name, 'utf-8', $app->config->PublishCharset );
160    }
161    my $tag_obj =
162      $tag_class->load( { name => $tag_name }, { binary => { name => 1 } } );
163
164    if ( !$tag_obj ) {
165        return $app->json_error( $app->translate("Invalid request.") );
166    }
167    my $tag_id = $tag_obj->id;
168
169    my @entries = $obj_class->load(
170        {
171            ( $blog_id ? ( blog_id => $blog_id ) : () ),
172            status => MT::Entry::RELEASE(),
173        },
174        {
175            sort      => 'authored_on',
176            direction => 'descend',
177            limit     => $limit,
178            join      => $objtag_class->join_on(
179                'object_id',
180                {
181                    ( $blog_id ? ( blog_id => $blog_id ) : () ),
182                    tag_id            => $tag_id,
183                    object_datasource => $obj_ds,
184                }
185            ),
186        }
187    );
188    my $count =
189      $obj_class->tagged_count( $tag_id,
190        { ( $blog_id ? ( blog_id => $blog_id ) : () ) } );
191    require MT::Template;
192    require MT::Blog;
193    my $tmpl = $app->load_tmpl('widget/blog_stats_recent_entries.tmpl');
194    my $ctx  = $tmpl->context;
195    $ctx->stash( 'blog', MT::Blog->load($blog_id) ) if $blog_id;
196    $ctx->stash( 'entries', \@entries );
197    $tmpl->param( 'entry_count', scalar @entries );
198    $tmpl->param( 'script_url', $app->uri );
199    $tmpl->param( 'tag',        $tag_name );
200    $tmpl->param( 'blog_id',    $blog_id ) if $blog_id;
201    my $editable = $app->user->is_superuser;
202    if ( $blog_id && !$editable ) {
203        $editable = $user->permissions($blog_id)->can_edit_all_posts;
204    }
205    $tmpl->param('editable',    $editable);
206    my $html = $app->build_page( $tmpl );
207    return $app->json_result( { html => $html } );
208}
209
210sub add_tags_to_entries {
211    my $app = shift;
212
213    my @id = $app->param('id');
214
215    require MT::Tag;
216    my $tags      = $app->param('itemset_action_input');
217    my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
218    my @tags      = MT::Tag->split( $tag_delim, $tags );
219    return $app->call_return unless @tags;
220
221    require MT::Entry;
222
223    my $user  = $app->user;
224    my $perms = $app->permissions;
225    foreach my $id (@id) {
226        next unless $id;
227        my $entry = MT::Entry->load($id) or next;
228        next
229          unless $entry
230          || $user->is_superuser
231          || $perms->can_edit_entry( $entry, $user );
232
233        $entry->add_tags(@tags);
234        $entry->save
235          or return $app->trans_error( "Error saving entry: [_1]",
236            $entry->errstr );
237    }
238
239    $app->add_return_arg( 'saved' => 1 );
240    $app->call_return;
241}
242
243sub remove_tags_from_entries {
244    my $app = shift;
245
246    my @id = $app->param('id');
247
248    require MT::Tag;
249    my $tags      = $app->param('itemset_action_input');
250    my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
251    my @tags      = MT::Tag->split( $tag_delim, $tags );
252    return $app->call_return unless @tags;
253
254    require MT::Entry;
255
256    my $user  = $app->user;
257    my $perms = $app->permissions;
258    foreach my $id (@id) {
259        next unless $id;
260        my $entry = MT::Entry->load($id) or next;
261        next
262          unless $entry
263          || $user->is_superuser
264          || $perms->can_edit_entry( $entry, $user );
265        $entry->remove_tags(@tags);
266        $entry->save
267          or return $app->trans_error( "Error saving entry: [_1]",
268            $entry->errstr );
269    }
270
271    $app->add_return_arg( 'saved' => 1 );
272    $app->call_return;
273}
274
275sub add_tags_to_assets {
276    my $app = shift;
277
278    my @id = $app->param('id');
279
280    require MT::Tag;
281    my $tags      = $app->param('itemset_action_input');
282    my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
283    my @tags      = MT::Tag->split( $tag_delim, $tags );
284    return $app->call_return unless @tags;
285
286    require MT::Asset;
287
288    my $user  = $app->user;
289    my $perms = $app->permissions;
290    foreach my $id (@id) {
291        next unless $id;
292        my $asset = MT::Asset->load($id) or next;
293        next
294          unless $asset
295          || $user->is_superuser
296          || $perms->can_edit_assets;
297
298        $asset->add_tags(@tags);
299        $asset->save
300          or
301          return $app->trans_error( "Error saving file: [_1]", $asset->errstr );
302    }
303
304    $app->add_return_arg( 'saved' => 1 );
305    $app->call_return;
306}
307
308sub remove_tags_from_assets {
309    my $app = shift;
310
311    my @id = $app->param('id');
312
313    require MT::Tag;
314    my $tags      = $app->param('itemset_action_input');
315    my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
316    my @tags      = MT::Tag->split( $tag_delim, $tags );
317    return $app->call_return unless @tags;
318
319    require MT::Asset;
320
321    my $user  = $app->user;
322    my $perms = $app->permissions;
323    foreach my $id (@id) {
324        next unless $id;
325        my $asset = MT::Asset->load($id) or next;
326        next
327          unless $asset
328          || $user->is_superuser
329          || $perms->can_edit_assets;
330        $asset->remove_tags(@tags);
331        $asset->save
332          or
333          return $app->trans_error( "Error saving file: [_1]", $asset->errstr );
334    }
335
336    $app->add_return_arg( 'saved' => 1 );
337    $app->call_return;
338}
339
340sub can_delete {
341    my ( $eh, $app, $obj ) = @_;
342    return 1 if $app->user->is_superuser();
343    my $perms = $app->permissions;
344    return 1
345      if $app->blog
346      && ( $perms->can_administer_blog() || $perms->can_edit_tags );
347    return 0;
348}
349
350sub post_delete {
351    my ( $eh, $app, $obj ) = @_;
352
353    $app->log(
354        {
355            message => $app->translate(
356                "Tag '[_1]' (ID:[_2]) deleted by '[_3]'",
357                $obj->name, $obj->id, $app->user->name
358            ),
359            level    => MT::Log::INFO(),
360            class    => 'system',
361            category => 'delete'
362        }
363    );
364}
365
366sub list_tag_for {
367    my $app = shift;
368    my (%params) = @_;
369
370    my $pkg = $params{Package};
371
372    my $q         = $app->param;
373    my $blog_id   = $app->param('blog_id');
374    my $list_pref = $app->list_pref('tag');
375    my %param     = %$list_pref;
376
377    my $limit = $list_pref->{rows};
378    my $offset = $app->param('offset') || 0;
379
380    my ( %terms, %arg );
381
382    my $tag_class = $app->model('tag');
383    my $ot_class  = $app->model('objecttag');
384    my $total = $pkg->tag_count( $blog_id ? { blog_id => $blog_id } : undef )
385      || 0;
386
387    $arg{'sort'} = 'name';
388    $arg{limit} = $limit + 1;
389    if ( $total && $offset > $total - 1 ) {
390        $arg{offset} = $offset = $total - $limit;
391    }
392    elsif ( $offset && ( ( $offset < 0 ) || ( $total - $offset < $limit ) ) ) {
393        $arg{offset} = $offset = $total - $limit;
394    }
395    else {
396        $arg{offset} = $offset if $offset;
397    }
398    $arg{join} = $ot_class->join_on(
399        'tag_id',
400        {
401            object_datasource => $pkg->datasource,
402            ( $blog_id ? ( blog_id => $blog_id ) : () )
403        },
404        {
405            unique => 1,
406            'join' => $pkg->join_on(
407                undef,
408                {
409                    id => \'= objecttag_object_id',
410                    ( $pkg =~ m/asset/i ? () : ( class => $pkg->class_type ) )
411                }
412            )
413        }
414    );
415
416    my $data = build_tag_table( $app,
417        load_args => [ \%terms, \%arg ],
418        'package' => $pkg,
419        param     => \%param
420    );
421    delete $param{tag_table} unless @$data;
422
423    ## We tried to load $limit + 1 entries above; if we actually got
424    ## $limit + 1 back, we know we have another page of entries.
425    my $have_next_entry = @$data > $limit;
426    pop @$data while @$data > $limit;
427    if ($offset) {
428        $param{prev_offset}     = 1;
429        $param{prev_offset_val} = $offset - $limit;
430        $param{prev_offset_val} = 0 if $param{prev_offset_val} < 0;
431    }
432    if ($have_next_entry) {
433        $param{next_offset}     = 1;
434        $param{next_offset_val} = $offset + $limit;
435    }
436
437    # load tag filters
438    my $filters = $app->registry( "list_filters", "tag" ) || {};
439    my $filter_key = $app->param('filter_key') || 'entry';
440    my $filter_label = '';
441    if ( my $filter = $filters->{$filter_key} ) {
442        $filter_label = $filter->{label};
443    }
444
445    $param{limit}                   = $limit;
446    $param{offset}                  = $offset;
447    $param{tag_object_type}         = $params{TagObjectType};
448    $param{tag_object_label}        = $params{TagObjectLabel} || $pkg->class_label;
449    $param{tag_object_label_plural} = $params{TagObjectLabelPlural} || $pkg->class_label_plural;
450    $param{object_label}            = $tag_class->class_label;
451    $param{object_label_plural}     = $tag_class->class_label_plural;
452    $param{object_type}             = 'tag';
453
454    my $search_types = $app->search_apis($app->blog ? 'blog' : 'system');
455    if (grep { $_->{key} eq $param{tag_object_type} } @$search_types) {
456        $param{search_type} = $param{tag_object_type};
457        $param{search_label} = $param{tag_object_label_plural};
458    } else {
459        $param{search_type} = 'entry';
460        $param{search_label} = $app->translate("Entries");
461    }
462
463    $param{list_start}              = $offset + 1;
464    $param{list_end}                = $offset + scalar @$data;
465    $param{list_total}              = $total;
466    $param{next_max}                = $param{list_total} - $limit;
467    $param{next_max}     = 0 if ( $param{next_max} || 0 ) < $offset + 1;
468    $param{list_noncron} = 1;
469    $param{list_filters} = $app->list_filters('tag');
470    $param{filter_key}   = $filter_key;
471    $param{filter_label} = $filter_label;
472    $param{link_to}      = 'list_' . lc $params{TagObjectType};
473
474    $param{saved}         = $q->param('saved');
475    $param{saved_deleted} = $q->param('saved_deleted');
476    $param{nav_tags}      = 1;
477    $app->add_breadcrumb( $app->translate('Tags') );
478    $param{screen_class} = "list-tag";
479    $param{screen_id} = "list-tag";
480    $param{listing_screen} = 1;
481
482    $app->load_tmpl( 'list_tag.tmpl', \%param );
483}
484
485sub build_tag_table {
486    my $app = shift;
487    my (%args) = @_;
488
489    my $iter;
490    if ( $args{load_args} ) {
491        my $class = $app->model('tag');
492        $iter = $class->load_iter( @{ $args{load_args} } );
493    }
494    elsif ( $args{iter} ) {
495        $iter = $args{iter};
496    }
497    elsif ( $args{items} ) {
498        $iter = sub { shift @{ $args{items} } };
499    }
500    return [] unless $iter;
501
502    my $param   = $args{param} || {};
503    my $blog_id = $app->param('blog_id');
504    my $pkg     = $args{'package'};
505
506    my @data;
507    while ( my $tag = $iter->() ) {
508        my $count = $pkg->tagged_count(
509            $tag->id,
510            {
511                ( $blog_id ? ( blog_id => $blog_id ) : () ),
512                ( $pkg =~ m/asset/i ? ( class => '*' ) : () )
513            }
514        );
515        $count ||= 0;
516        my $row = {
517            tag_id    => $tag->id,
518            tag_name  => $tag->name,
519            tag_count => $count,
520            object    => $tag,
521        };
522        push @data, $row;
523    }
524    return [] unless @data;
525
526    $param->{tag_table}[0]{object_loop} = \@data;
527    $app->load_list_actions( 'tag', $param->{tag_table}[0] );
528    $param->{object_loop} = $param->{tag_table}[0]{object_loop};
529}
530
5311;
Note: See TracBrowser for help on using the browser.