root/TagSupplementals/trunk/TagSupplementals.pl

Revision 474, 13.5 kB (checked in by ogawa, 4 months ago)

Comment out _object_tags().

  • Property svn:keywords set to Id
Line 
1# TagSupplementals - Supplemental features for MT 3.3 tags.
2#
3# $Id$
4# This software is provided as-is. You may use it for commercial or
5# personal use. If you distribute it, please keep this notice intact.
6#
7# Copyright (c) 2006-2008 Hirotaka Ogawa
8
9package MT::Plugin::TagSupplementals;
10use strict;
11use warnings;
12
13use MT 4;
14use base qw(MT::Plugin);
15
16our $VERSION = '0.10';
17our $HAVE_MT_XSEARCH = 0;
18{
19    eval { require MT::XSearch; $HAVE_MT_XSEARCH = 1 };
20    if ($HAVE_MT_XSEARCH) {
21        MT::XSearch->add_search_plugin('TagSupplementals', {
22            label       => 'Tag Search',
23            description => 'Tag Search plugin for MT-XSearch',
24            on_execute  => \&xsearch_on_execute,
25            on_stash    => \&xsearch_on_stash,
26        });
27    }
28}
29
30my $plugin = __PACKAGE__->new({
31    name           => 'TagSupplementals',
32    description    => 'A plugin for providing supplemental "tag" features for MT4',
33    doc_link       => 'http://code.as-is.net/public/wiki/TagSupplementals_Plugin',
34    author_name    => 'Hirotaka Ogawa',
35    author_link    => 'http://profile.typekey.com/ogawa/',
36    version        => $VERSION,
37    registry       => {
38        tags => {
39            block    => {
40                RelatedEntries => \&related_entries,
41                RelatedTags    => \&related_tags,
42                ArchiveTags    => \&archive_tags,
43                SearchTags     => \&search_tags,
44                $HAVE_MT_XSEARCH ? (XSearchTags => \&xsearch_tags) : (),
45            },
46            function => {
47                EntryTagsCount => \&entry_tags_count,
48                TagLastUpdated => \&tag_last_updated,
49                $HAVE_MT_XSEARCH ? (TagXSearchLink => \&tag_xsearch_link) : (),
50            },
51            modifier => {
52                encode_urlplus => \&encode_urlplus,
53            },
54        },
55    }
56});
57MT->add_plugin($plugin);
58
59use MT::Template::Context;
60use MT::Entry;
61use MT::Tag;
62use MT::ObjectTag;
63use MT::Promise qw(force);
64
65sub entry_tags_count {
66    my $ctx = shift;
67    my $entry = $ctx->stash('entry')
68        or return $ctx->_no_entry_error('MT' . $ctx->stash('tag'));
69    my @tags = $entry->get_tags;
70    scalar @tags;
71}
72
73sub tag_last_updated {
74    my ($ctx, $args) = @_;
75    my $tag = $ctx->stash('Tag') or return '';
76    my (%blog_terms, %blog_args);
77    $ctx->set_blog_load_context($args, \%blog_terms, \%blog_args)
78        or return $ctx->error($ctx->errstr);
79
80    my ($e) = MT::Entry->load(undef, {
81        sort      => 'created_on',
82        direction => 'descend',
83        limit     => 1,
84        join      => [ 'MT::ObjectTag', 'object_id', {
85            %blog_terms,
86            tag_id            => $tag->id,
87            object_datasource => MT::Entry->datasource,
88        }, {
89            %blog_args,
90            unique            => 1,
91        } ] })
92        or return '';
93
94    $args->{ts} = $e->created_on;
95    MT::Template::Context::_hdlr_date($ctx, $args);
96}
97
98#sub _object_tags {
99#    my ($blog_id, $tag_id) = @_;
100#    my $r = MT::Request->instance;
101#    my $otag_cache = $r->stash('object_tags_cache:' . $blog_id) || {};
102#    if (!$otag_cache->{$tag_id}) {
103#        my @otags = MT::ObjectTag->load({
104#            blog_id           => $blog_id,
105#            tag_id            => $tag_id,
106#            object_datasource => MT::Entry->datasource,
107#        });
108#        $otag_cache->{$tag_id} = \@otags;
109#        $r->stash('object_tags_cache:' . $blog_id, $otag_cache);
110#    }
111#    $otag_cache->{$tag_id};
112#}
113
114sub related_entries {
115    my ($ctx, $args, $cond) = @_;
116    my $entry = $ctx->stash('entry')
117        or return $ctx->_no_entry_error('MT' . $ctx->stash('tag'));
118
119    my $weight = $args->{weight} || 'constant';
120    my $lastn  = $args->{lastn}  || 0;
121    my $offset = $args->{offset} || 0;
122    $lastn += $offset;
123       
124    my $entry_id = $entry->id;
125    my (%blog_terms, %blog_args);
126    $ctx->set_blog_load_context($args, \%blog_terms, \%blog_args)
127        or return $ctx->error($ctx->errstr);
128
129    my @tags = MT::Tag->load(undef, {
130        sort => 'name',
131        join => [ 'MT::ObjectTag', 'tag_id', {
132            %blog_terms,
133            object_id         => $entry_id,
134            object_datasource => MT::Entry->datasource,
135        }, {
136            %blog_args,
137            unique            => 1,
138        } ] })
139        or return '';
140    my %tag_ids;
141    foreach (@tags) {
142        $tag_ids{$_->id} = 1;
143        my @more = MT::Tag->load({ n8d_id => $_->n8d_id ? $_->n8d_id : $_->id });
144        $tag_ids{$_->id} = 1 foreach @more;
145    }
146    my @tag_ids = keys %tag_ids;
147
148    my %rank;
149    if ($weight eq 'constant') {
150        if (MT::Object->driver->can('count_group_by')) {
151            my $iter = MT::ObjectTag->count_group_by({
152                %blog_terms,
153                tag_id            => \@tag_ids,
154                object_datasource => MT::Entry->datasource,
155            }, {
156                %blog_args,
157                group             => ['object_id'],
158            });
159            while (my ($count, $object_id) = $iter->()) {
160                $rank{$object_id} = $count;
161            }
162        } else {
163            my $iter = MT::ObjectTag->load_iter({
164                %blog_terms,
165                tag_id            => \@tag_ids,
166                object_datasource => MT::Entry->datasource,
167            }, {
168                %blog_args,
169            });
170            while (my $otag = $iter->()) {
171                $rank{$otag->object_id}++;
172            }
173        }
174    } elsif ($weight eq 'idf') {
175        for my $tag_id (@tag_ids) {
176            my @otags = MT::ObjectTag->load({
177                %blog_terms,
178                tag_id => $tag_id,
179                object_datasource => MT::Entry->datasource,
180            }, {
181                %blog_args,
182            });
183            next if scalar @otags == 1;
184            my $rank = 1 / (scalar @otags - 1);
185            for my $otag (@otags) {
186                $rank{$otag->object_id} += $rank;
187            }
188        }
189    }
190    delete $rank{$entry_id};
191
192    # sort by entry_id, and then sort by rank
193    my @eids = sort { $b <=> $a } keys %rank;
194    @eids = sort { $rank{$b} <=> $rank{$a} } @eids;
195
196    my @entries;
197    my $i = 0;
198    foreach (@eids) {
199        my $e = MT::Entry->load($_);
200        if ($e->status == MT::Entry::RELEASE()) {
201            next if $i < $offset;
202            push @entries, $e;
203            $i++;
204            last if $lastn && $i >= $lastn;
205        }
206    }
207
208    my $res = '';
209    my $tokens = $ctx->stash('tokens');
210    my $builder = $ctx->stash('builder');
211    $i = 0;
212    for my $e (@entries) {
213        local $ctx->{__stash}{entry} = $e;
214        local $ctx->{current_timestamp} = $e->created_on;
215        local $ctx->{modification_timestamp} = $e->modified_on;
216        my $out = $builder->build($ctx, $tokens, {
217            %$cond,
218            EntriesHeader => !$i,
219            EntriesFooter => !defined $entries[$i+1],
220        });
221        return $ctx->error($ctx->errstr) unless defined $out;
222        $res .= $out;
223        $i++;
224    }
225    $res;
226}
227
228sub related_tags {
229    my ($ctx, $args, $cond) = @_;
230    my $tag = $ctx->stash('Tag') or return '';
231    my (%blog_terms, %blog_args);
232    $ctx->set_blog_load_context($args, \%blog_terms, \%blog_args)
233        or return $ctx->error($ctx->errstr);
234
235    my @otags = MT::ObjectTag->load({
236        %blog_terms,
237        tag_id            => $tag->id,
238        object_datasource => MT::Entry->datasource,
239    }, {
240        %blog_args,
241    });
242    my @eids = map { $_->object_id } @otags;
243
244    my $iter = MT::Tag->load_iter(undef, {
245        sort => 'name',
246        join => ['MT::ObjectTag', 'tag_id', {
247            %blog_terms,
248            object_id         => \@eids,
249            object_datasource => MT::Entry->datasource,
250        }, {
251            %blog_args,
252            unique            => 1,
253        } ] });
254
255    my @res;
256    my $builder = $ctx->stash('builder');
257    my $tokens = $ctx->stash('tokens');
258    while (my $t = $iter->()) {
259        next if $t->is_private || ($t->id == $tag->id);
260        local $ctx->{__stash}{Tag} = $t;
261        local $ctx->{__stash}{tag_count} = undef;
262        local $ctx->{__stash}{tag_entry_count} = undef;
263        defined(my $out = $builder->build($ctx, $tokens))
264            or return $ctx->error($ctx->errstr);
265        push @res, $out;
266    }
267    my $glue = $args->{glue} || '';
268    join $glue, @res;
269}
270
271sub archive_tags {
272    my ($ctx, $args, $cond) = @_;
273    my $entries = force($ctx->stash('entries')) or return '';
274    my (%blog_terms, %blog_args);
275    $ctx->set_blog_load_context($args, \%blog_terms, \%blog_args)
276        or return $ctx->error($ctx->errstr);
277
278    my @eids = map { $_->id } grep { $_->status == MT::Entry::RELEASE() } @$entries;
279
280    my $iter = MT::Tag->load_iter(undef, {
281        sort => 'name',
282        join => ['MT::ObjectTag', 'tag_id', {
283            %blog_terms,
284            object_id         => \@eids,
285            object_datasource => MT::Entry->datasource,
286        }, {
287            %blog_args,
288            unique            => 1,
289        } ] });
290
291    my @res;
292    my $builder = $ctx->stash('builder');
293    my $tokens = $ctx->stash('tokens');
294    while (my $t = $iter->()) {
295        next if $t->is_private;
296        local $ctx->{__stash}{Tag} = $t;
297        local $ctx->{__stash}{tag_count} = undef;
298        local $ctx->{__stash}{tag_entry_count} = undef;
299        defined(my $out = $builder->build($ctx, $tokens))
300            or return $ctx->error($ctx->errstr);
301        push @res, $out;
302    }
303    my $glue = $args->{glue} || '';
304    join $glue, @res;
305}
306
307sub encode_urlplus {
308    my $s = $_[0];
309    return $s unless $_[1];
310    $s =~ s!([^ a-zA-Z0-9_.~-])!uc sprintf "%%%02x", ord($1)!eg;
311    $s =~ tr/ /+/;
312    $s;
313}
314
315sub search_tags {
316    my ($ctx, $args, $cond) = @_;
317
318    return '' unless $ctx->stash('search_string') =~ /\S/;
319    my $tags = $ctx->stash('search_string');
320    my @tag_names = MT::Tag->split(',', $tags);
321#    my %tags = map { $_ => 1, MT::Tag->normalize($_) => 1 } @tag_names;
322#    my @tags = MT::Tag->load({ name => [ keys %tags ] });
323    my @tags = MT::Tag->load({ name => @tag_names });
324    return '' unless scalar @tags;
325
326    my @res;
327    my $builder = $ctx->stash('builder');
328    my $tokens = $ctx->stash('tokens');
329    foreach (@tags) {
330        local $ctx->{__stash}{'Tag'} = $_;
331        local $ctx->{__stash}{tag_count} = undef;
332        defined(my $out = $builder->build($ctx, $tokens, $cond))
333            or return $ctx->error($ctx->errstr);
334        push @res, $out;
335    }
336    my $glue = $args->{glue} || '';
337    join $glue, @res;
338}
339
340sub tag_xsearch_link {
341    my ($ctx, $args, $cond) = @_;
342    my $tag = $ctx->stash('Tag') or return '';
343    my $delimiter = $args->{delimiter} || '';
344    my $path = MT::Template::Context->_hdlr_cgi_path($ctx);
345
346    $path . 'mt-xsearch.cgi' . '?blog_id=' . $ctx->stash('blog_id') .
347        '&amp;search_key=TagSupplementals' .
348        ($delimiter ? '&amp;delimiter=' . MT::Util::encode_url($delimiter) : '') .
349        '&amp;search=' . MT::Util::encode_url($tag->name);
350}
351
352sub xsearch_tags {
353    my ($ctx, $args, $cond) = @_;
354
355    return '' unless defined $ctx->stash('xsearch_tags');
356    my $tags = $ctx->stash('xsearch_tags');
357    return '' unless scalar @$tags;
358
359    my @res;
360    my $builder = $ctx->stash('builder');
361    my $tokens = $ctx->stash('tokens');
362    foreach (@$tags) {
363        local $ctx->{__stash}{'Tag'} = $_;
364        local $ctx->{__stash}{tag_count} = undef;
365        defined(my $out = $builder->build($ctx, $tokens, $cond))
366            or return $ctx->error($ctx->errstr);
367        push @res, $out;
368    }
369    my $glue = $args->{glue} || '';
370    join $glue, @res;
371}
372
373sub xsearch_on_stash {
374    my ($ctx, $val, $self) = @_;
375    $ctx->stash('entry', $val);
376    $ctx->{current_timestamp} = $val->created_on;
377    $ctx->{modification_timestamp} = $val->modified_on;
378    $ctx->stash('xsearch_tags', $self->{xsearch_tags});
379}
380
381sub xsearch_on_execute {
382    my ($args, $self) = @_;
383
384    my $blog_id = $args->{blog_id} or MT->error('Blog ID is required.');
385    my $delimiter = $args->{delimiter} || ',';
386    my $sort_by = $args->{sort_by} || 'created_on';
387    my $sort_order = $args->{sort_order} || 'descend';
388    my $lastn = $args->{lastn} || 0;
389
390    my $tags = $args->{search} or MT->error('Search string is required.');
391    my @tag_names = MT::Tag->split($delimiter, $tags)
392        or return [];
393    my $tag_count = scalar @tag_names;
394
395    my @tags = MT::Tag->load_by_datasource(MT::Entry->datasource, {
396        is_private => 0,
397        $blog_id ? (blog_id => $blog_id) : (),
398        name       => \@tag_names,
399    });
400    $self->{xsearch_tags} = \@tags;
401    my @tag_ids = map { $_->id } @tags;
402
403    my @eids;
404    if (MT::Object->driver->can('count_group_by')) {
405        my $iter = MT::ObjectTag->count_group_by({
406            blog_id           => $blog_id,
407            tag_id            => \@tag_ids,
408            object_datasource => MT::Entry->datasource,
409        }, {
410            group => ['object_id'],
411        });
412        while (my ($count, $object_id) = $iter->()) {
413            push @eids, $object_id if $count == $tag_count;
414        }
415    } else {
416        my $iter = MT::ObjectTag->load_iter({
417            blog_id           => $blog_id,
418            tag_id            => \@tag_ids,
419            object_datasource => MT::Entry->datasource,
420        });
421        my %count;
422        while (my $otag = $iter->()) {
423            $count{$otag->object_id}++;
424        }
425        foreach (keys %count) {
426            push @eids, $_ if $count{$_} == $tag_count;
427        }
428    }
429    return [] unless scalar @eids;
430
431    my @entries;
432    map { push @entries, MT::Entry->load($_) } @eids;
433    @entries = $sort_order eq 'descend' ?
434        sort { $b->created_on <=> $a->created_on } @entries :
435        sort { $a->created_on <=> $b->created_on } @entries;
436    splice(@entries, $lastn) if $lastn && (scalar @entries > $lastn);
437
438    \@entries;
439}
440
4411;
Note: See TracBrowser for help on using the browser.