root/branches/athena/lib/MT/Component.pm @ 1092

Revision 1092, 17.0 kB (checked in by hachi, 2 years ago)

Merging release-15 to athena branch. svn merge -r59987:60375 http://svn.sixapart.com/repos/eng/movabletype/branches/release-15 .

  • Property svn:keywords set to Id Revision
Line 
1# Copyright 2001-2007 Six Apart. This code cannot be redistributed without
2# permission from www.sixapart.com.  For more information, consult your
3# Movable Type license.
4#
5# $Id$
6
7package MT::Component;
8
9use strict;
10use base qw( Class::Accessor::Fast MT::ErrorHandler );
11use MT::Util qw(encode_js);
12
13__PACKAGE__->mk_accessors(qw( id path envelope version schema_version ));
14
15BEGIN {
16
17    # my @registry_methods = qw( callbacks upgrade_functions object_types
18    #     junk_filters text_filters permissions importers rebuild_options
19    #     tasks );
20    my @registry_methods = qw( callbacks );
21    foreach my $meth (@registry_methods) {
22        my $sub = sub { &_getset( shift, $meth, @_ ) };
23        no strict 'refs';
24        *$meth = $sub;
25    }
26}
27
28# static method
29sub select {
30    my ( $pkg, $class ) = @_;
31    if ( $class && $class !~ m/::/ ) {
32        $class = $pkg . '::' . $class;
33    }
34    elsif ( !$class ) {
35        $class = $pkg;
36    }
37    my @plugins;
38    foreach my $p (@MT::Components) {
39        push @plugins, $p if UNIVERSAL::isa( $p, $class );
40    }
41    return @plugins;
42}
43
44sub new {
45    my $class = shift;
46    my ($self) = ref $_[0] ? @_ : {@_};
47    bless $self, $class;
48    $self->init();
49    $self;
50}
51
52sub init {
53    my $c = shift;
54    $c->init_registry() or return;
55
56    # plugin callbacks are initialized after they finish loading.
57    $c->init_callbacks() unless $c->isa('MT::Plugin');
58    $c;
59}
60
61sub init_callbacks {
62    my $c = shift;
63
64    # Tricky; we only want to add this callback IF the plugin
65    # has an init_app callback of it's own; at the same time
66    # we have to declare a superclass init_app method so if
67    # the plugin uses $plugin->SUPER::init_app(), it works.
68    # So we test here to see if the signature of the init_app
69    # method is something other than our stub init_app
70    # method in MT::Component (same goes for init_request below)
71    if ( $c->can('init_app') != \&MT::Component::init_app ) {
72        MT->add_callback(
73            'init_app',
74            5, $c,
75            sub {
76                my $cb = shift;
77                local $MT::plugin_registry = $c->{registry};
78                $c->init_app(@_);
79            }
80        );
81    }
82    elsif (_getset( $c, 'init_app' )
83        || _getset( $c, 'applications' ) )
84    {
85        MT->add_callback( 'init_app', 5, $c, \&on_init_app );
86    }
87
88    if ( $c->can('init_request') != \&MT::Component::init_request ) {
89        MT->add_callback(
90            'init_request',
91            5, $c,
92            sub {
93                my $cb = shift;
94                local $MT::plugin_registry = $c->{registry};
95                $c->init_request(@_);
96            }
97        );
98    }
99    elsif ( _getset( $c, 'init_request' ) || _getset( $c, 'applications' ) ) {
100        MT->add_callback( 'init_request', 5, $c, \&on_init_request );
101    }
102
103    if ( $c->{init_tasks} ) {
104        MT->add_callback( 'tasks', 5, $c, $c->{init_tasks} );
105    }
106    elsif ( $c->can('init_tasks') ) {
107        MT->add_callback( 'tasks', 5, $c,
108            sub { my $cb = shift; $c->init_tasks(@_) } );
109    }
110
111    if ( my $callbacks = $c->callbacks ) {
112        if ( ref $callbacks eq 'ARRAY' ) {
113            foreach my $cb (@$callbacks) {
114                MT->add_callback( $_->{name}, $cb->{priority} || 5,
115                    $c, $cb->{handler} || $cb->{code} );
116            }
117        }
118        elsif ( ref $callbacks eq 'HASH' ) {
119            foreach my $cbname ( keys %$callbacks ) {
120                if ( ref $callbacks->{$cbname} eq 'CODE' ) {
121                    MT->add_callback( $cbname, 5, $c, $callbacks->{$cbname} );
122                }
123                elsif ( ref $callbacks->{$cbname} eq 'HASH' ) {
124                    MT->add_callback(
125                        $callbacks->{$cbname}{callback} || $cbname,
126                        $callbacks->{$cbname}{priority} || 5,
127                        $c,
128                        $callbacks->{$cbname}{handler}
129                          || $callbacks->{$cbname}{code}
130                    );
131                }
132                elsif ( ref $callbacks->{$cbname} eq 'ARRAY' ) {
133                    my $list = $callbacks->{$cbname};
134                    MT->add_callback( $cbname, $_->{priority} || 5,
135                        $c, $_->{handler} || $_->{code} )
136                      foreach @$list;
137                }
138            }
139        }
140    }
141}
142
143sub load_registry {
144    my $c      = shift;
145    my ($file) = @_;
146    my $path   = $c->path or return;
147    $path = File::Spec->catfile( $c->path, $file );
148    return unless -f $path;
149    require YAML::Tiny;
150    my $y = eval { YAML::Tiny->read($path) }
151        or die "Error reading $path: " . $YAML::Tiny::errstr;
152    if ( ref($y) ) {
153
154        # skip over non-hash elements
155        shift @$y while @$y && ( ref( $y->[0] ) ne 'HASH' );
156        return $y->[0] if @$y;
157    }
158    return {};
159}
160
161sub init_registry {
162    my $c = shift;
163    my $r = $c->load_registry("config.yaml");
164    if ( !$r ) {
165        return 1;
166    }
167
168   # TBD: 'extends' support...
169   # if (my $ext = $r->{extends}) {
170   #     # require any other components declared here
171   #     $ext = [ $ext ] unless ref($ext) eq 'ARRAY';
172   #     foreach my $comp (@$ext) {
173   #         MT->require_component($comp)
174   #             or return $c->error("Error loading required component: $comp");
175   #     }
176   # }
177    $c->registry($r);
178
179    # map key registry elements into metadata
180    foreach my $prop (qw(version schema_version)) {
181        $c->$prop( $r->{$prop} ) if exists $r->{$prop};
182    }
183    $c->name( $r->{label} ) if exists $r->{label};
184    return 1;
185}
186
187# STUB
188sub init_app { }
189sub init_request { }
190
191sub on_init_app {
192    my $cb      = shift;
193    my $c       = $cb->plugin;
194    my ($app)   = @_;
195    my $app_pkg = ref $app;
196    my $init;
197    if ( $init = _getset( $c, 'init_app' ) ) {
198        if ( ref $init eq 'HASH' ) {
199            $init = $init->{$app_pkg};
200        }
201    }
202    else {
203        $init = $c->registry( "applications", $app->id, "init" );
204    }
205    if ( $init && !ref($init) ) {
206        $init = MT->handler_to_coderef($init);
207    }
208    if ( $init && ( ref $init eq 'CODE' ) ) {
209        local $MT::plugin_registry = $c->{registry};
210        $init->( $c, @_ );
211    }
212    elsif ( $c->can('init_app') ) {
213        local $MT::plugin_registry = $c->{registry};
214        $c->init_app(@_);
215    }
216}
217
218sub on_init_request {
219    my $cb      = shift;
220    my $c       = $cb->plugin;
221    my ($app)   = @_;
222    my $app_pkg = ref $app;
223    my $init;
224    if ( $init = _getset( $c, 'init_request' ) ) {
225        if ( ref $init eq 'HASH' ) {
226            $init = $init->{$app_pkg} if exists $init->{$app_pkg};
227        }
228    }
229    else {
230        $init = $c->registry( "applications", $app->id, "init_request" );
231    }
232    if ( $init && !ref($init) ) {
233        $init = MT->handler_to_coderef($init);
234    }
235    if ( ref $init eq 'CODE' ) {
236        local $MT::plugin_registry = $c->{registry};
237        $init->( $app, @_ );
238    }
239}
240
241sub _getset {
242    my $c = shift;
243    my ($prop) = @_;
244    if ( exists $c->{registry}{$prop} ) {
245        if ( @_ > 1 ) {
246            return $c->{registry}{$prop} = $_[1];
247        }
248        else {
249            my $out = $c->{registry}{$prop};
250
251            # Handle reference to another YAML file
252            # (ie, app-cms.yaml/tags.yaml/etc.)
253            if ( defined($out) && !ref($out) && ( $out =~ m/^[-\w]+\.yaml$/ ) )
254            {
255                my $r = $c->load_registry($out);
256                if ($r) {
257                    return $c->{registry}{$prop} = $r;
258                }
259                return undef;
260            }
261            return $out;
262        }
263    }
264    return @_ > 1 ? $c->{$prop} = $_[1] : $c->{$prop};
265}
266
267sub _getset_translate {
268    my $c = shift;
269    my ($p) = @_;
270    if ( !$p ) {
271        $p = ( caller(1) )[3];
272        $p =~ s/.*:://;
273    }
274    my $return;
275    if ( exists $c->{registry}{$p} ) {
276        $return = @_ > 1 ? $c->{registry}{$p} = $_[1] : $c->{registry}{$p};
277    }
278    else {
279        $return = @_ > 1 ? $c->{$p} = $_[1] : $c->{$p};
280    }
281    return $c->l10n_filter( defined $return ? $return : '' );
282}
283
284sub name { &_getset_translate }
285
286sub label {
287    my $c = shift;
288    return $c->_getset('label') || $c->name();
289}
290
291sub description { &_getset_translate }
292
293sub needs_upgrade {
294    my $c  = shift;
295    my $sv = $c->schema_version;
296    return 0 unless defined $sv;
297    my $key     = 'PluginSchemaVersion';
298    my $id      = $c->id;
299    my $ver     = MT->config($key);
300    my $cfg_ver = $ver->{$id} if $ver;
301    if ( ( !defined $cfg_ver ) || ( $cfg_ver < $sv ) ) {
302        return 1;
303    }
304    0;
305}
306
307sub template_paths {
308    my $c = shift;
309
310    my $mt   = MT->instance;
311    my $path = $mt->config('TemplatePath');
312
313    my @paths;
314    my $dir = File::Spec->catdir( $c->path, 'tmpl' );
315    push @paths, $dir if -d $dir;
316    $dir = $c->path;
317    push @paths, $dir if -d $dir;
318    if ( my $alt_path = $mt->config('AltTemplatePath') ) {
319        if ( -d $alt_path ) {    # AltTemplatePath is absolute
320            push @paths, File::Spec->catdir( $alt_path, $mt->{template_dir} )
321              if $mt->{template_dir};
322            push @paths, $alt_path;
323        }
324    }
325    push @paths, File::Spec->catdir( $path, $mt->{template_dir} )
326      if $mt->{template_dir};
327    push @paths, $path;
328    return @paths;
329}
330
331sub load_tmpl {
332    my $c = shift;
333    my ($file, $param) = @_;
334
335    my $mt = MT->instance;
336    my $type = { 'SCALAR' => 'scalarref', 'ARRAY' => 'arrayref' }->{ ref $file }
337      || 'filename';
338
339    require MT::Template;
340    my $tmpl = MT::Template->new(
341        type   => $type,
342        source => $file,
343        path   => [ $c->template_paths ],
344        ($mt->isa('MT::App') ? ( filter => sub {
345            my ($str, $fname) = @_;
346            if ($fname) {
347                $fname = File::Basename::basename($fname);
348                $fname =~ s/\.tmpl$//;
349                $mt->run_callbacks("template_source.$fname", $mt, @_);
350            } else {
351                $mt->run_callbacks("template_source", $mt, @_);
352            }
353            return $str;
354        }) : ()),
355    );
356    return $c->error(
357        $mt->translate( "Loading template '[_1]' failed: [_2]", $file, MT::Template->errstr ) )
358      unless defined $tmpl;
359    $tmpl->{__file} = $file if $type eq 'filename';
360
361    ## We do this in load_tmpl because show_error and login don't call
362    ## build_page; so we need to set these variables here.
363    if ( $mt->isa('MT::App') ) {
364        $mt->set_default_tmpl_params($tmpl);
365        $tmpl->param($param) if $param;
366    }
367    else {
368        my $author = $mt->user;
369        my %param = (
370            ($author ? (
371                author_id => $author->id,
372                author_name => $author->name,
373                can_logout => MT::Auth->can_logout,
374            ) : ()),
375            static_uri        => $mt->static_path,
376            script_path       => $mt->path,
377            mt_version        => MT->version_id,
378            language_tag      => $mt->current_language,
379            language_encoding => $mt->charset,
380            %{ $param || {} },
381        );
382        $tmpl->param( \%param );
383        if ($type eq 'filename') {
384            if (!$tmpl->param('template_filename')) {
385                my $fname = $file;
386                $fname =~ s!\\!/!g;
387                $fname =~ s/\.tmpl$//;
388                $tmpl->param('template_filename', $fname);
389            }
390        }
391    }
392
393
394    return $tmpl;
395}
396
397sub l10n_class { _getset( shift, 'l10n_class', @_ ) || 'MT::L10N' }
398
399sub translate {
400    my $c       = shift;
401    my $handles = MT->request('l10n_handle') || {};
402    my $h       = $handles->{ $c->id };
403    unless ($h) {
404        my $lang = MT->current_language || MT->config->DefaultLanguage;
405        eval "require " . $c->l10n_class . ";";
406        if ($@) {
407            $h = MT->language_handle;
408        }
409        else {
410            $h = $c->l10n_class->get_handle($lang);
411        }
412        $handles->{ $c->id } = $h;
413        MT->request( 'l10n_handle', $handles );
414    }
415    my ( $format, @args ) = @_;
416    foreach (@args) {
417        $_ = $_->() if ref($_) eq 'CODE';
418    }
419    my $enc = MT->instance->config('PublishCharset');
420    my $str;
421    if ($h) {
422        if ( $enc =~ m/utf-?8/i ) {
423            $str = $h->maketext( $format, @args );
424        }
425        else {
426            $str = MT::I18N::encode_text(
427                $h->maketext(
428                    $format,
429                    map { MT::I18N::encode_text( $_, $enc, 'utf-8' ) } @args
430                ),
431                'utf-8', $enc
432            );
433        }
434    }
435    if ( !defined $str ) {
436        $str = MT->translate(@_);
437    }
438    $str;
439}
440
441sub translate_templatized {
442    my $c = shift;
443    my ($text) = @_;
444    while (1) {
445        $text =~
446s!(<(?:_|MT)_TRANS(?:\s+((?:\w+)\s*=\s*(["'])(?:<[^>]+?>|[^\3]+?)*?\3))+?\s*/?>)!
447        my($msg, %args) = ($1);
448        while ($msg =~ /\b(\w+)\s*=\s*(["'])((?:<[^>]+?>|[^\2])*?)?\2/g) {  #"
449            $args{$1} = $3;
450        }
451        $args{params} = '' unless defined $args{params};
452        my @p = map MT::Util::decode_html($_),
453                split /\s*%%\s*/, $args{params}, -1;
454        @p = ('') unless @p;
455        my $translation = $c->translate($args{phrase}, @p);
456        if (exists $args{escape}) {
457            if (lc($args{escape}) eq 'html') {
458                $translation = encode_html($translation);
459            } elsif (lc($args{escape}) eq 'url') {
460                $translation = MT::Util::encode_url($translation);
461            } else {
462                # fallback for js/javascript/singlequotes
463                $translation = encode_js($translation);
464            }
465        }
466        $translation;
467        !igem or last;
468    }
469    return $text;
470}
471
472sub l10n_filter { $_[0]->translate_templatized( $_[1] ) }
473
474# can be invoked statically or with an instance.
475# if invoked statically, it queries all available plugins
476#   MT::Plugin->registry("applications")
477# if invoked with an instance, it only selects for that plugin's registry
478#   $foo_plugin->registry("applications")
479
480sub registry {
481    my $c = shift;
482    if ( ref $c ) {
483        if ( !@_ ) { return $c->{registry} ||= {} }
484        if ( ref $_[0] ) {
485            return $c->{registry} = shift;
486        }
487        my @path = @_;
488        my $r    = $c->{registry};
489        return undef unless $r;
490        my ( $last_r, $last_p );
491        foreach my $p (@path) {
492            if ( ref $p ) {
493
494                # Handle the case where an assignment
495                # is being made to a registry item. Ie
496                # $comp->registry("foo","bar","baz", { stuff => ... })
497                $last_r->{$last_p} = $p;
498                $r = $last_r;
499                last;
500            }
501            if ( exists $r->{$p} ) {
502                my $v = $r->{$p};
503
504                # check for a yaml file reference...
505                if ( !ref($v) && ( $v =~ m/^[-\w]+\.yaml$/ ) ) {
506                    my $f = File::Spec->catfile( $c->path, $v );
507                    if ( -f $f ) {
508                        require YAML::Tiny;
509                        my $y = eval { YAML::Tiny->read($f) }
510                            or die "Error reading $f: " . $YAML::Tiny::errstr;
511
512                        # skip over non-hash elements
513                        shift @$y while @$y && ( ref( $y->[0] ) ne 'HASH' );
514                        if (@$y) {
515                            $r->{$p} = $y->[0];
516                        }
517                    }
518                }
519                elsif ( ref($v) eq 'CODE' ) {
520                    $r->{$p} = $v->($c);
521                }
522                $last_r = $r;
523                $last_p = $p;
524                $r      = $r->{$p};
525            }
526            else {
527                return undef;
528            }
529        }
530
531        # deepscan for any label elements since they will need translation
532        if ( ref $r eq 'HASH' ) {
533            __deep_localize_labels( $c, $r );
534            $_->{plugin} = $c for grep { ref $_ eq 'HASH' } values %$r;
535        }
536
537        # $r should now be the element of the path requested
538        return $r;
539    }
540    else {
541        my @objs = $c->select();
542        my @list;
543        foreach my $o (@objs) {
544            my $r = $o->registry(@_);
545            push @list, $r if defined $r;
546        }
547        return @list ? \@list : undef;
548    }
549}
550
551sub __deep_localize_labels {
552    my ( $c, $hash ) = @_;
553    foreach my $k ( keys %$hash ) {
554        if ( ref( $hash->{$k} ) eq 'HASH' ) {
555            __deep_localize_labels( $c, $hash->{$k} );
556        }
557        else {
558            next unless $k =~ m/(?:\b|_)label\b/;
559            if ( !ref( my $label = $hash->{$k} ) ) {
560                $hash->{$k} = sub { $c->translate($label) };
561            }
562        }
563    }
564}
565
5661;
567__END__
568
569=head1 NAME
570
571MT::Component - Movable Type class that describes a component.
572
573=head1 SYNOPSIS
574
575=head1 DESCRIPTION
576
577=head1 ARGUMENTS
578
579=over 4
580
581=item * id (recommended)
582
583=item * label
584
585=item * version
586
587The version number for the release of the plugin. Will be displayed
588next to the plugin's name wherever listed. This information is not
589required, but recommended.
590
591=item * schema_version
592
593If your plugin declares a list of object classes, the schema_version
594is used to determine whether your classes require installation or
595upgrade. MT will store your plugin's schema_version in the C<MT::Config>
596table for future reference.
597
598=back
599
600=head1 METHODS
601
602=head1 LOCALIZATION
603
604=head1 AUTHOR & COPYRIGHT
605
606Please see L<MT/AUTHOR & COPYRIGHT>.
607
608=cut
Note: See TracBrowser for help on using the browser.