root/branches/release-41/lib/MT.pm.pre @ 2744

Revision 2744, 119.8 kB (checked in by bchoate, 17 months ago)

Updated POD for MT module.

  • Property svn:keywords set to Author Date Id Revision
Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT;
8
9use strict;
10use base qw( MT::ErrorHandler );
11use File::Spec;
12use File::Basename;
13use MT::Util qw( weaken );
14use MT::I18N qw( encode_text );
15
16our ( $VERSION,      $SCHEMA_VERSION );
17our ( $PRODUCT_NAME, $PRODUCT_CODE, $PRODUCT_VERSION, $VERSION_ID );
18our ( $MT_DIR,       $APP_DIR, $CFG_DIR, $CFG_FILE, $SCRIPT_SUFFIX );
19our (
20    $plugin_sig, $plugin_envelope, $plugin_registry,
21    %Plugins,    @Components,      %Components,
22    $DebugMode,  $mt_inst,         %mt_inst
23);
24my %Text_filters;
25
26# For state determination in MT::Object
27our $plugins_installed;
28
29BEGIN {
30    $plugins_installed = 0;
31
32    ( $VERSION, $SCHEMA_VERSION ) = ( '__API_VERSION__', '__SCHEMA_VERSION__' );
33    ( $PRODUCT_NAME, $PRODUCT_CODE, $PRODUCT_VERSION, $VERSION_ID ) = (
34        '__PRODUCT_NAME__',    '__PRODUCT_CODE__',
35        '__PRODUCT_VERSION__', '__PRODUCT_VERSION_ID__'
36    );
37
38    $DebugMode = 0;
39
40    # Alias lowercase to uppercase package; note: this is an equivalence
41    # as opposed to having @mt::ISA set to 'MT'. so @mt::Plugins would
42    # resolve as well as @MT::Plugins.
43    *{mt::} = *{MT::};
44
45    # Alias these; Components is the preferred array for MT 4
46    *Plugins = \@Components;
47}
48
49# On-demand loading of compatibility module, if a plugin asks for it, using
50#     use MT 3;
51# or even specific to minor version (but this just loads MT::Compat::v3)
52#     use MT 3.3;
53sub VERSION {
54    my $v = $_[1];
55    if ( defined $v && ( $v =~ m/^(\d+)/ ) ) {
56        my $compat = "MT::Compat::v" . $1;
57        if ( ( $1 > 2 ) && ( $1 < int($VERSION) ) ) {
58            no strict 'refs';
59            unless ( defined *{ $compat . '::' } ) {
60                eval "# line " . __LINE__ . " " . __FILE__ . "\nrequire $compat;";
61            }
62        }
63    }
64    return UNIVERSAL::VERSION(@_);
65}
66
67sub version_number  { $VERSION }
68sub version_id      { $VERSION_ID }
69sub product_code    { $PRODUCT_CODE }
70sub product_name    { $PRODUCT_NAME }
71sub product_version { $PRODUCT_VERSION }
72sub schema_version  { $SCHEMA_VERSION }
73
74# Default id method turns MT::App::CMS => cms; Foo::Bar => foo/bar
75sub id {
76    my $pkg = shift;
77    my $id = ref($pkg) || $pkg;
78    # ignore the MT::App prefix as part of the identifier
79    $id =~ s/^MT::App:://;
80    $id =~ s!::!/!g;
81    return lc $id;
82}
83
84sub version_slug {
85    return MT->translate_templatized(<<"SLUG");
86<MT_TRANS phrase="Powered by [_1]" params="$PRODUCT_NAME">
87<MT_TRANS phrase="Version [_1]" params="$VERSION_ID">
88<MT_TRANS phrase="http://www.sixapart.com/movabletype/">
89SLUG
90}
91
92sub import {
93    my $pkg = shift;
94    return unless @_;
95
96    my (%param) = @_;
97    my $app_pkg;
98    if ( $app_pkg = $param{app} || $param{App} || $ENV{MT_APP} ) {
99        if ( $app_pkg !~ m/::/ ) {
100            my $apps = $pkg->registry('applications');
101            $app_pkg = $apps->fetch($app_pkg);
102            if ( ref $app_pkg ) {
103
104                # pick first one??
105                $app_pkg = $app_pkg->[0];
106
107                # pick last one??
108                # $app_pkg = pop @$app_pkg;
109            }
110        }
111    }
112    elsif ( $param{run} || $param{Run} ) {
113
114        # my $script = File::Spec->rel2abs($0);
115        my ( $filename, $path, $suffix ) = fileparse( $0, qr{\..+$} );
116        $SCRIPT_SUFFIX = $suffix;
117        my $script = lc $filename;
118        $script =~ s/^mt-//;
119        my $apps = $pkg->registry('applications');
120        $app_pkg = $apps->fetch( lc $script );
121        unless ($app_pkg) {
122            die "cannot determine application for script $0, stopped at";
123        }
124    }
125    $pkg->run_app( $app_pkg, \%param )
126      if $app_pkg;
127}
128
129sub run_app {
130    my $pkg = shift;
131    my ( $class, $param ) = @_;
132
133    # When running under FastCGI, the initial invocation of the
134    # script has a bare environment. We can use this to test
135    # for FastCGI.
136    my $not_fast_cgi = 0;
137    $not_fast_cgi ||= exists $ENV{$_}
138      for qw(HTTP_HOST GATEWAY_INTERFACE SCRIPT_FILENAME SCRIPT_URL);
139    my $fast_cgi = ( !$not_fast_cgi ) || $param->{fastcgi};
140    $fast_cgi =
141      defined( $param->{fastcgi} || $param->{FastCGI} )
142      ? ( $param->{fastcgi} || $param->{FastCGI} )
143      : $fast_cgi;
144    if ($fast_cgi) {
145        eval { require CGI::Fast; };
146        $fast_cgi = 0 if $@;
147    }
148
149    # ready to run now... run inside an eval block so we can gracefully
150    # die if something bad happens
151    my $app;
152    eval {
153        eval "require $class; 1;" or die $@;
154        if ($fast_cgi) {
155            while ( my $cgi = new CGI::Fast ) {
156                $app = $class->new( %$param, CGIObject => $cgi )
157                  or die $class->errstr;
158                local $SIG{__WARN__} = sub { $app->trace( $_[0] ) };
159                $pkg->set_instance($app);
160                $app->init_request( CGIObject => $cgi );
161                $app->run;
162            }
163        }
164        else {
165            $app = $class->new(%$param) or die $class->errstr;
166            local $SIG{__WARN__} = sub { $app->trace( $_[0] ) };
167            $app->run;
168        }
169    };
170    if ( my $err = $@ ) {
171        my $charset = 'utf-8';
172        eval {
173            $app ||= MT->instance;
174            my $cfg = $app->config;
175            my $c   = $app->find_config;
176            $cfg->read_config($c);
177            $charset = $cfg->PublishCharset;
178        };
179        if ( $app && UNIVERSAL::isa( $app, 'MT::App' ) ) {
180            eval {
181                my %param = ( error => $err );
182                if ( $err =~ m/Bad ObjectDriver/ ) {
183                    $param{error_database_connection} = 1;
184                }
185                elsif ( $err =~ m/Bad CGIPath/ ) {
186                    $param{error_cgi_path} = 1;
187                }
188                elsif ( $err =~ m/Missing configuration file/ ) {
189                    $param{error_config_file} = 1;
190                }
191                my $page = $app->build_page( 'error.tmpl', \%param )
192                  or die $app->errstr;
193                print "Content-Type: text/html; charset=$charset\n\n";
194                print $page;
195            };
196            if ( my $err = $@ ) {
197                print "Content-Type: text/plain; charset=$charset\n\n";
198                print $app
199                  ? $app->translate( "Got an error: [_1]", $err )
200                  : "Got an error: $err";
201            }
202        }
203        else {
204            if ( $err =~ m/Missing configuration file/ ) {
205                my $host = $ENV{SERVER_NAME} || $ENV{HTTP_HOST};
206                $host =~ s/:\d+//;
207                my $port = $ENV{SERVER_PORT};
208                my $uri = $ENV{REQUEST_URI} || $ENV{PATH_INFO};
209                $uri =~ s/mt(\Q$SCRIPT_SUFFIX\E)?.*$//;
210                my $cgipath = '';
211                $cgipath = $port == 443 ? 'https' : 'http';
212                $cgipath .= '://' . $host;
213                $cgipath .= ( $port == 443 || $port == 80 ) ? '' : ':' . $port;
214                $cgipath .= $uri;
215
216                print "Status: 302 Moved\n";
217                print "Location: " . $cgipath . "mt-wizard.cgi\n\n";
218            }
219            else {
220                print "Content-Type: text/plain; charset=$charset\n\n";
221                print $app
222                  ? $app->translate( "Got an error: [_1]", $err )
223                  : "Got an error: $err\n";
224            }
225        }
226    }
227}
228
229sub app {
230    my $class = shift;
231    $mt_inst ||= $mt_inst{$class} ||= $class->construct(@_);
232}
233*instance = *app;
234
235sub set_instance {
236    my $class = shift;
237    $mt_inst = shift;
238}
239
240sub new {
241    my $mt = &instance_of;
242    $mt_inst ||= $mt;
243    $mt;
244}
245
246sub instance_of {
247    my $class = shift;
248    $mt_inst{$class} ||= $class->construct(@_);
249}
250
251sub construct {
252    my $class = shift;
253    my $mt = bless {}, $class;
254    local $mt_inst = $mt;
255    $mt->init(@_)
256      or die $mt->errstr;
257    $mt;
258}
259
260{
261    my %object_types;
262
263    sub model {
264        my $pkg = shift;
265        my ($k) = @_;
266        $object_types{$k} = $_[1] if scalar @_ > 1;
267        return $object_types{$k} if exists $object_types{$k};
268
269        if ($k =~ m/^(.+):meta$/) {
270            my $ppkg = $pkg->model($1);
271            my $mpkg = $ppkg->meta_pkg;
272            return $mpkg ? $object_types{$k} = $mpkg : undef;
273        }
274
275        my $model = $pkg->registry( 'object_types', $k );
276        if ( ref($model) eq 'ARRAY' ) {
277
278            # First element of an array *should* be a scalar; in case it isn't,
279            # return undef.
280            $model = $model->[0];
281            return undef if ref $model;
282        }
283        elsif ( ref($model) eq 'HASH' ) {
284
285            # If all we have is a hash, this doesn't tell us the package for
286            # this object type, so it's undefined.
287            return undef;
288        }
289        return undef unless $model;
290
291        # Element in object type hash is scalar, so return it
292        no strict 'refs';
293        unless ( defined *{ $model . '::__properties' } ) {
294            use strict 'refs';
295            eval "# line " . __LINE__ . " " . __FILE__ . "\nrequire $model;";
296            if ( $@ && ( $k =~ m/^(.+)\./ ) ) {
297
298                # x.foo can't be found, so try loading x
299                if ( my $ppkg = $pkg->model($1) ) {
300
301                    # well now see if $model is defined
302                    no strict 'refs';
303                    unless ( defined *{ $model . '::__properties' } ) {
304
305                        # if not, use parent package instead
306                        $model = $ppkg;
307                    }
308                }
309            }
310        }
311        return $object_types{$k} = $model;
312    }
313
314    sub models {
315        my $pkg = shift;
316        my ($k) = @_;
317
318        my @matches;
319        my $model = $pkg->registry('object_types');
320        foreach my $m ( keys %$model ) {
321            if ( $m =~ m/^\Q$k\E\.?/ ) {
322                push @matches, $m;
323            }
324        }
325        return @matches;
326    }
327}
328
329sub registry {
330    my $pkg = shift;
331
332    require MT::Component;
333    my $regs = MT::Component->registry(@_);
334    my $r;
335    if ($regs) {
336        foreach my $cr (@$regs) {
337
338            # in the event that our registry request returns something
339            # other than an array of hashes, return it as is instead of
340            # merging it together.
341            return $regs unless ref($cr) eq 'HASH';
342
343            # next unless ref($cr) eq 'HASH';
344            delete $cr->{plugin} if exists $cr->{plugin};
345            __merge_hash( $r ||= {}, $cr );
346        }
347    }
348    return $r;
349}
350
351# merges contents of two hashes, giving preference to the right side
352# if $replace is true; otherwise it will always append to the left side.
353sub __merge_hash {
354    my ( $h1, $h2, $replace ) = @_;
355    for my $k ( keys(%$h2) ) {
356        if ( exists( $h1->{$k} ) && ( !$replace ) ) {
357            if ( ref $h1->{$k} eq 'HASH' ) {
358                __merge_hash( $h1->{$k}, $h2->{$k}, ( $replace || 0 ) + 1 );
359            }
360            elsif ( ref $h1->{$k} eq 'ARRAY' ) {
361                if ( ref $h2->{$k} eq 'ARRAY' ) {
362                    push @{ $h1->{$k} }, @{ $h2->{$k} };
363                }
364                else {
365                    push @{ $h1->{$k} }, $h2->{$k};
366                }
367            }
368            else {
369                $h1->{$k} = [ $h1->{$k}, $h2->{$k} ];
370            }
371        }
372        else {
373            $h1->{$k} = $h2->{$k};
374        }
375    }
376}
377
378# The above functions can all be used to make MT objects (and subobjects).
379# The difference between them is characterized by these assertions:
380#
381#  $mt = MT::App::Search->new();
382#  assert($mt->isa('MT::App::Search'))
383#
384#  $mt1 = MT->instance
385#  $mt2 = MT->instance
386#  assert($mt1 == $mt2);
387#
388#  $mt1 = MT::App::CMS->construct()
389#  $mt2 = MT::App::CMS->construct()
390#  assert($mt1 != $mt2);
391#
392# TBD: make a test script for these.
393
394sub unplug {
395}
396
397sub config {
398    my $mt = shift;
399    ref $mt or $mt = MT->instance;
400    unless ( $mt->{cfg} ) {
401        require MT::ConfigMgr;
402        weaken( $mt->{cfg} = MT::ConfigMgr->instance );
403    }
404    if (@_) {
405        my $setting = shift;
406        @_ ? $mt->{cfg}->set( $setting, @_ ) : $mt->{cfg}->get($setting);
407    }
408    else {
409        $mt->{cfg};
410    }
411}
412
413sub request {
414    my $pkg  = shift;
415    my $inst = ref($pkg) ? $pkg : $pkg->instance;
416    unless ( $inst->{request} ) {
417        require MT::Request;
418        $inst->{request} = MT::Request->instance;
419    }
420    if (@_) {
421        $inst->{request}->stash(@_);
422    }
423    else {
424        $inst->{request};
425    }
426}
427
428sub log {
429    my $mt = shift;
430    unless ($plugins_installed) {
431        # finish init_schema here since we have to log something
432        # to the database.
433        $mt->init_schema();
434    }
435    my $msg;
436    if ( !@_ ) {    # single parameter to log, so $mt must be message
437        $msg = $mt;
438        $mt  = MT->instance;
439    }
440    else {          # multiple parameters to log; second one is message
441        $msg = shift;
442    }
443    my $log_class = $mt->model('log');
444    my $log = $log_class->new();
445    if ( ref $msg eq 'HASH' ) {
446        $log->set_values($msg);
447    }
448    elsif ( ( ref $msg ) && ( UNIVERSAL::isa( $msg, 'MT::Log' ) ) ) {
449        $log = $msg;
450    }
451    else {
452        $log->message($msg);
453    }
454    $log->level( MT::Log::INFO() )
455      unless defined $log->level;
456    $log->class('system')
457      unless defined $log->class;
458    $log->save();
459    print STDERR MT->translate( "Message: [_1]", $log->message ) . "\n"
460      if $MT::DebugMode;
461}
462my $plugin_full_path;
463
464sub run_tasks {
465    my $mt = shift;
466    require MT::TaskMgr;
467    MT::TaskMgr->run_tasks(@_);
468}
469
470sub add_plugin {
471    my $class = shift;
472    my ($plugin) = @_;
473    if ( ref $plugin eq 'HASH' ) {
474        require MT::Plugin;
475        $plugin = new MT::Plugin($plugin);
476    }
477    $plugin->{name} ||= $plugin_sig;
478    $plugin->{plugin_sig} = $plugin_sig;
479
480    my $id = $plugin->id;
481    unless ($plugin_envelope) {
482        warn "MT->add_plugin improperly called outside of MT plugin load loop.";
483        return;
484    }
485    $plugin->envelope($plugin_envelope);
486    Carp::confess("You cannot register multiple plugin objects from a single script. $plugin_sig")
487      if exists( $Plugins{$plugin_sig} )
488      && ( exists $Plugins{$plugin_sig}{object} );
489
490    $Components{ lc $id } = $plugin if $id;
491    $Plugins{$plugin_sig}{object} = $plugin;
492    $plugin->{full_path}  = $plugin_full_path;
493    $plugin->path($plugin_full_path);
494    unless ( $plugin->{registry} && ( %{ $plugin->{registry} } ) ) {
495        $plugin->{registry} = $plugin_registry;
496    }
497    if ( $plugin->{registry} ) {
498        if ( my $settings = $plugin->{registry}{config_settings} ) {
499            $settings = $plugin->{registry}{config_settings} = $settings->()
500              if ref($settings) eq 'CODE';
501            $class->config->define($settings);
502        }
503    }
504    push @Components, $plugin;
505    1;
506}
507
508our %CallbackAlias;
509our $CallbacksEnabled = 1;
510my %CallbacksEnabled;
511my @Callbacks;
512
513sub add_callback {
514    my $class = shift;
515    my ( $meth, $priority, $plugin, $code ) = @_;
516    if ( $meth =~ m/^(.+::)?([^\.]+)(\..+)?$/ ) {
517
518        # Remap (whatever)::(name).(something)
519        if ( exists $CallbackAlias{$2} ) {
520            $meth = $CallbackAlias{$2};
521            $meth = $1 . $meth if $1;
522            $meth = $meth . $3 if $3;
523        }
524    }
525    $meth = $CallbackAlias{$meth} if exists $CallbackAlias{$meth};
526    my $internal = 0;
527    if ( ref $plugin ) {
528        if ( ( defined $mt_inst ) && ( $plugin == $mt_inst ) ) {
529            $plugin   = undef;
530            $internal = 1;
531        }
532        elsif ( !UNIVERSAL::isa( $plugin, "MT::Component" ) ) {
533            return $class->trans_error(
534"If present, 3rd argument to add_callback must be an object of type MT::Component or MT::Plugin"
535            );
536        }
537    }
538    if ( ( ref $code ) ne 'CODE' ) {
539        if ( ref $code ) {
540            return $class->trans_error(
541                '4th argument to add_callback must be a CODE reference.');
542        }
543        else {
544            # Defer until callback is used
545            # if ($plugin) {
546            #     $code = MT->handler_to_coderef($code);
547            # }
548        }
549    }
550
551    # 0 and 11 are exclusive.
552    if ( $priority == 0 || $priority == 11 ) {
553        if ( $Callbacks[$priority]->{$meth} ) {
554            return $class->trans_error("Two plugins are in conflict");
555        }
556    }
557    return $class->trans_error( "Invalid priority level [_1] at add_callback",
558        $priority )
559      if ( ( $priority < 0 ) || ( $priority > 11 ) );
560    require MT::Callback;
561    $CallbacksEnabled{$meth} = 1;
562    ## push @{$Plugins{$plugin_sig}{callbacks}}, "$meth Callback" if $plugin_sig;
563    my $cb = new MT::Callback(
564        plugin   => $plugin,
565        code     => $code,
566        priority => $priority,
567        internal => $internal,
568        method   => $meth
569    );
570    push @{ $Callbacks[$priority]->{$meth} }, $cb;
571    $cb;
572}
573
574sub remove_callback {
575    my $class    = shift;
576    my ($cb)     = @_;
577    my $priority = $cb->{priority};
578    my $method   = $cb->{method};
579    my $list     = $Callbacks[$priority];
580    return unless $list;
581    my $cbarr = $list->{$method};
582    return unless $cbarr;
583    @$cbarr = grep { $_ != $cb } @$cbarr;
584}
585
586# For use by MT internal code
587sub _register_core_callbacks {
588    my $class = shift;
589    my ($callback_table) = @_;
590    foreach my $name ( keys %$callback_table ) {
591        $class->add_callback( $name, 5, $mt_inst, $callback_table->{$name} )
592          || return;
593    }
594    1;
595}
596
597sub register_callbacks {
598    my $class = shift;
599    my ($callback_list) = @_;
600    foreach my $cb (@$callback_list) {
601        $class->add_callback( $cb->{name}, $cb->{priority}, $cb->{plugin},
602            $cb->{code} )
603          || return;
604    }
605    1;
606}
607
608our $CB_ERR;
609sub callback_error { $CB_ERR = $_[0]; }
610sub callback_errstr { $CB_ERR }
611
612sub run_callback {
613    my $class = shift;
614    my ( $cb, @args ) = @_;
615
616    $cb->error();    # reset the error string
617    my $result = eval {
618        # line __LINE__ __FILE__
619        $cb->invoke(@args);
620    };
621    if ( my $err = $@ ) {
622        $cb->error($err);
623        my $plugin = $cb->{plugin};
624        my $name;
625        if ( $cb->{internal} ) {
626            $name = "Internal callback";
627        }
628        elsif ( UNIVERSAL::isa( $plugin, 'MT::Plugin' ) ) {
629            $name = $plugin->name() || MT->translate("Unnamed plugin");
630        }
631        else {
632            $name = MT->translate("Unnamed plugin");
633        }
634        require MT::Log;
635        MT->log(
636            {
637                message => MT->translate( "[_1] died with: [_2]", $name, $err ),
638                class   => 'system',
639                category => 'callback',
640                level    => MT::Log::ERROR(),
641            }
642        );
643        return 0;
644    }
645    if ( $cb->errstr() ) {
646        return 0;
647    }
648    return $result;
649}
650
651# A callback should return a true/false value. The result of
652# run_callbacks is the logical AND of all the callback's return
653# values. Some hookpoints will ignore the return value: e.g. object
654# callbacks don't use it. By convention, those that use it have Filter
655# at the end of their names (CommentPostFilter, CommentThrottleFilter,
656# etc.)
657# Note: this composition is not short-circuiting. All callbacks are
658# executed even if one has already returned false.
659# ALSO NOTE: failure (dying or setting $cb->errstr) does not force a
660# "false" return.
661# THINK: are there cases where a true value should override all false values?
662# that is, where logical OR is the right way to compose multiple callbacks?
663sub run_callbacks {
664    my $class = shift;
665    my ( $meth, @args ) = @_;
666    return 1 unless $CallbacksEnabled && %CallbacksEnabled;
667    $meth = $CallbackAlias{$meth} if exists $CallbackAlias{$meth};
668    my @methods;
669
670    # execution:
671    #   Full::Name.<variant>
672    #   *::Name.<variant> OR Name.<variant>
673    #   Full::Name
674    #   *::Name OR Name
675    push @methods, $meth if $CallbacksEnabled{$meth};    # bleh::blah variant
676    if ( $meth =~ /::/ ) {    # presence of :: implies it's an obj. cb
677        my $name = $meth;
678        $name =~ s/^.*::([^:]*)$/$1/;
679        $name = $CallbackAlias{ '*::' . $name }
680          if exists $CallbackAlias{ '*::' . $name };
681        push @methods, '*::' . $name
682          if $CallbacksEnabled{ '*::' . $name };    # *::blah variant
683        push @methods, $name if $CallbacksEnabled{$name};    # blah variant
684    }
685    if ( $meth =~ /\./ ) {    # presence of ' ' implies it is a variant callback
686        my ($name) = split /\./, $meth, 2;
687        $name = $CallbackAlias{$name} if exists $CallbackAlias{$name};
688        push @methods, $name if $CallbacksEnabled{$name};    # bleh::blah
689        if ( $name =~ m/::/ ) {
690            my $name2 = $name;
691            $name2 =~ s/^.*::([^:]*)$/$1/;
692            $name2 = $CallbackAlias{ '*::' . $name2 }
693              if exists $CallbackAlias{ '*::' . $name2 };
694            push @methods, '*::' . $name2
695              if $CallbacksEnabled{ '*::' . $name2 };        # *::blah
696            push @methods, $name2 if $CallbacksEnabled{$name2};    # blah
697        }
698    }
699    return 1 unless @methods;
700
701    $CallbacksEnabled{$_} = 0 for @methods;
702    my @errors;
703    my $filter_value = 1;
704    my $first_error;
705
706    foreach my $callback_sheaf (@Callbacks) {
707        for my $meth (@methods) {
708            if ( my $set = $callback_sheaf->{$meth} ) {
709                for my $cb (@$set) {
710                    my $result = $class->run_callback( $cb, @args );
711                    $filter_value &&= $result;
712                    if ( !$result ) {
713                        if ( $cb->errstr() ) {
714                            push @errors, $cb->errstr();
715                        }
716                        if ( !defined($first_error) ) {
717                            $first_error = $cb->errstr();
718                        }
719                    }
720                }
721            }
722        }
723    }
724
725    callback_error( join( '', @errors ) );
726
727    $CallbacksEnabled{$_} = 1 for @methods;
728    if ( !$filter_value ) {
729        return $class->error($first_error);
730    }
731    else {
732        return $filter_value;
733    }
734}
735
736sub user_class {
737    shift->{user_class};
738}
739
740sub find_config {
741    my $mt = shift;
742    my ($param) = @_;
743
744    $param->{Config}    ||= $ENV{MT_CONFIG};
745    $param->{Directory} ||= $ENV{MT_HOME};
746    if ( !$param->{Directory} ) {
747        if ( $param->{Config} ) {
748            $param->{Directory} = dirname( $param->{Config} );
749        }
750        else {
751            $param->{Directory} = dirname($0) || $ENV{PWD} || '.';
752        }
753    }
754
755    # the directory is the more important parameter between it and
756    # the config parameter. if config is unreadable, then scan for
757    # a config file using the directory as a base.  we support
758    # either mt.cfg or mt-config.cgi for the config file name. the
759    # latter being a more secure choice since it is unreadable from
760    # a browser.
761    for my $cfg_file ( $param->{Config},
762        File::Spec->catfile( $param->{Directory}, 'mt-config.cgi' ),
763        'mt-config.cgi' )
764    {
765        return $cfg_file if $cfg_file && -r $cfg_file && -f $cfg_file;
766    }
767    return undef;
768}
769
770sub init_schema {
771    require MT::Object;
772    MT::Object->install_pre_init_properties();
773}
774
775sub init_permissions {
776    require MT::Permission;
777    MT::Permission->init_permissions;
778}
779
780sub init_config {
781    my $mt = shift;
782    my ($param) = @_;
783
784    my $cfg_file = $mt->find_config($param);
785    return $mt->error(
786"Missing configuration file. Maybe you forgot to move mt-config.cgi-original to mt-config.cgi?"
787    ) unless $cfg_file;
788    $cfg_file = File::Spec->rel2abs($cfg_file);
789
790    # translate the config file's location to an absolute path, so we
791    # can use that directory as a basis for calculating other relative
792    # paths found in the config file.
793    my $config_dir = $mt->{config_dir} = dirname($cfg_file);
794
795    # store the mt_dir (home) as an absolute path; fallback to the config
796    # directory if it isn't set.
797    $mt->{mt_dir} =
798      $param->{Directory}
799      ? File::Spec->rel2abs( $param->{Directory} )
800      : $mt->{config_dir};
801    $mt->{mt_dir} ||= dirname($0);
802
803    # also make note of the active application path; this is derived by
804    # checking the PWD environment variable, the dirname of $0,
805    # the directory of SCRIPT_FILENAME and lastly, falls back to mt_dir
806    $mt->{app_dir} = $ENV{PWD} || "";
807    $mt->{app_dir} = dirname($0)
808      if !$mt->{app_dir}
809      || !File::Spec->file_name_is_absolute( $mt->{app_dir} );
810    $mt->{app_dir} = dirname( $ENV{SCRIPT_FILENAME} )
811      if $ENV{SCRIPT_FILENAME}
812      && ( !$mt->{app_dir}
813        || ( !File::Spec->file_name_is_absolute( $mt->{app_dir} ) ) );
814    $mt->{app_dir} ||= $mt->{mt_dir};
815    $mt->{app_dir} = File::Spec->rel2abs( $mt->{app_dir} );
816
817    my $cfg = $mt->config;
818    $cfg->define( $mt->registry('config_settings') );
819    $cfg->read_config($cfg_file) or return $mt->error( $cfg->errstr );
820    $mt->{cfg_file} = $cfg_file;
821
822    my @mt_paths = $cfg->paths;
823    for my $meth (@mt_paths) {
824        my $path = $cfg->get( $meth, undef );
825        my $type = $cfg->type($meth);
826        if ( defined $path ) {
827            if ( $type eq 'ARRAY' ) {
828                my @paths = $cfg->get($meth);
829                local $_;
830                foreach (@paths) {
831                    next if File::Spec->file_name_is_absolute($_);
832                    $_ = File::Spec->catfile( $config_dir, $_ );
833                }
834                $cfg->$meth( \@paths );
835            }
836            else {
837                if ( !File::Spec->file_name_is_absolute($path) ) {
838                    $path = File::Spec->catfile( $config_dir, $path );
839                    $cfg->$meth($path);
840                }
841            }
842        }
843        else {
844            next if $type eq 'ARRAY';
845            my $path = $cfg->default($meth);
846            if ( defined $path ) {
847                $cfg->$meth( File::Spec->catfile( $config_dir, $path ) );
848            }
849        }
850    }
851
852    return $mt->trans_error("Bad ObjectDriver config")
853      unless $cfg->ObjectDriver;
854
855    if ( $MT::DebugMode = $cfg->DebugMode ) {
856        require Data::Dumper;
857        $Data::Dumper::Terse    = 1;
858        $Data::Dumper::Maxdepth = 4;
859        $Data::Dumper::Sortkeys = 1;
860        $Data::Dumper::Indent   = 1;
861    }
862
863    if ($cfg->PerformanceLogging && $cfg->ProcessMemoryCommand) {
864        $mt->log_times();
865    }
866
867    $mt->set_language( $cfg->DefaultLanguage );
868
869    my $cgi_path = $cfg->CGIPath;
870    if ( !$cgi_path || $cgi_path =~ m!http://www\.example\.com/! ) {
871        return $mt->trans_error("Bad CGIPath config");
872    }
873
874    $mt->{cfg} = $cfg;
875
876    1;
877}
878
879{
880my ($memory_start);
881sub log_times {
882    my $pkg = shift;
883
884    my $timer = $pkg->get_timer;
885    return unless $timer;
886
887    my $memory;
888    my $cmd = $pkg->config->ProcessMemoryCommand;
889    if ($cmd) {
890        my $re;
891        if (ref($cmd) eq 'HASH') {
892            $re = $cmd->{regex};
893            $cmd = $cmd->{command};
894        }
895        $cmd =~ s/\$\$/$$/g;
896        $memory = `$cmd`;
897        if ($re) {
898            if ($memory =~ m/$re/) {
899                $memory = $1;
900                $memory =~ s/\D//g;
901            }
902        } else {
903            $memory =~ s/\s+//gs;
904        }
905    }
906
907    # Called at the start of the process; so we're only recording
908    # the memory usage at the start of the app right now.
909    unless ($timer->{elapsed}) {
910        $memory_start = $memory;
911        return;
912    }
913
914    require File::Spec;
915    my $dir = MT->config('PerformanceLoggingPath') or return;
916
917    my @time = localtime(time);
918    my $file = sprintf("pl-%04d%02d%02d.log", $time[5] + 1900, $time[4]+1, $time[3]);
919    my $log_file = File::Spec->catfile( $dir, $file );
920
921    my $first_write = ! -f $log_file;
922
923    local *PERFLOG;
924    open PERFLOG, ">>$log_file";
925    require Fcntl;
926    flock(PERFLOG, Fcntl::LOCK_EX());
927
928    if ($first_write) {
929        require Config;
930        my ($osname, $osvers) = ($Config::Config{osname}, $Config::Config{osvers});
931        print PERFLOG "# Operating System: $osname/$osvers\n";
932        print PERFLOG "# Platform: $^O\n";
933        my $ver = ref($^V) eq 'version' ? $^V->normal : ( $^V ? join('.', unpack 'C*', $^V) : $] );
934        print PERFLOG "# Perl Version: $ver\n";
935        print PERFLOG "# Web Server: $ENV{SERVER_SOFTWARE}\n";
936        require MT::Object;
937        my $driver = MT::Object->driver;
938        if ($driver) {
939            my $dbh = $driver->r_handle;
940            if ($dbh) {
941                my $dbname = $dbh->get_info( 17 ); # SQL_DBMS_NAME
942                my $dbver = $dbh->get_info( 18 ); # SQL_DBMS_VER
943                if ($dbname && $dbver) {
944                    print PERFLOG "# Database: $dbname/$dbver\n";
945                }
946            }
947        }
948        my ($drname, $drh) = each %DBI::installed_drh;
949        print PERFLOG "# Database Library: DBI/" . $DBI::VERSION . "; DBD/" . $drh->{Version} . "\n";
950        if ($ENV{MOD_PERL}) {
951            print PERFLOG "# App Mode: mod_perl\n";
952        }
953        elsif ($ENV{FAST_CGI}) {
954            print PERFLOG "# App Mode: FastCGI\n";
955        }
956        else {
957            print PERFLOG "# App Mode: CGI\n";
958        }
959    }
960
961    if ($memory) {
962        print PERFLOG $timer->dump_line("mem_start=$memory_start", "mem_end=$memory");
963    } else {
964        print PERFLOG $timer->dump_line();
965    }
966
967    close PERFLOG;
968}
969}
970
971sub get_timer {
972    my $mt = shift;
973    $mt = MT->instance unless ref $mt;
974    my $timer = $mt->request('timer');
975    unless (defined $timer) {
976        if (MT->config('PerformanceLogging')) {
977            my $uri;
978            if ($mt->isa('MT::App')) {
979                $uri = $mt->uri( args => { $mt->param_hash } );
980            }
981            require MT::Util::ReqTimer;
982            $timer = MT::Util::ReqTimer->new( $uri );
983        } else {
984            $timer = 0;
985        }
986        $mt->request('timer', $timer);
987    }
988    return $timer;
989}
990
991sub time_this {
992    my $mt = shift;
993    my ($str, $code) = @_;
994    my $timer = $mt->get_timer();
995    my $ret;
996    if ($timer) {
997        $timer->pause_partial();
998        $ret = $code->();
999        $timer->mark($str);
1000    } else {
1001        $ret = $code->();
1002    }
1003    return $ret;
1004}
1005
1006sub init_config_from_db {
1007    my $mt = shift;
1008    my ($param) = @_;
1009    my $cfg = $mt->config;
1010    $cfg->read_config_db();
1011
1012    # Tell any instantiated drivers to reconfigure themselves as necessary
1013    MT::ObjectDriverFactory->configure;
1014
1015    1;
1016}
1017
1018sub bootstrap {
1019    my $pkg = shift;
1020    $pkg->init_paths() or return;
1021    $pkg->init_core()  or return;
1022}
1023
1024sub init_paths {
1025    my $mt = shift;
1026    my ($param) = @_;
1027
1028    # determine MT directory
1029    my ($orig_dir);
1030    require File::Spec;
1031    if ( !( $MT_DIR = $ENV{MT_HOME} ) ) {
1032        if ( $0 =~ m!(.*([/\\]))! ) {
1033            $orig_dir = $MT_DIR = $1;
1034            my $slash = $2;
1035            $MT_DIR =~ s!(?:[/\\]|^)(?:plugins[/\\].*|tools[/\\])$!$slash!;
1036            $MT_DIR = '' if ( $MT_DIR =~ m!^\.?[\\/]$! );
1037        }
1038        else {
1039
1040            # MT_DIR/lib/MT.pm -> MT_DIR/lib -> MT_DIR
1041            $MT_DIR = dirname( dirname( File::Spec->rel2abs(__FILE__) ) );
1042        }
1043        unless ($MT_DIR) {
1044            $orig_dir = $MT_DIR = $ENV{PWD} || '.';
1045            $MT_DIR =~ s!(?:[/\\]|^)(?:plugins[/\\].*|tools[/\\]?)$!!;
1046        }
1047        $ENV{MT_HOME} = $MT_DIR;
1048    }
1049    unshift @INC, File::Spec->catdir( $MT_DIR,   'extlib' );
1050    unshift @INC, File::Spec->catdir( $orig_dir, 'lib' )
1051      if $orig_dir && ( $orig_dir ne $MT_DIR );
1052
1053    $mt->set_language('__BUILD_LANGUAGE__');
1054
1055    if ( my $cfg_file = $mt->find_config($param) ) {
1056        $cfg_file = File::Spec->rel2abs($cfg_file);
1057        $CFG_FILE = $cfg_file;
1058    }
1059    else {
1060        return $mt->trans_error(
1061"Missing configuration file. Maybe you forgot to move mt-config.cgi-original to mt-config.cgi?"
1062        ) if ref($mt);
1063    }
1064
1065    # store the mt_dir (home) as an absolute path; fallback to the config
1066    # directory if it isn't set.
1067    $MT_DIR ||=
1068      $param->{directory}
1069      ? File::Spec->rel2abs( $param->{directory} )
1070      : $CFG_DIR;
1071    $MT_DIR ||= dirname($0);
1072
1073    # also make note of the active application path; this is derived by
1074    # checking the PWD environment variable, the dirname of $0,
1075    # the directory of SCRIPT_FILENAME and lastly, falls back to mt_dir
1076    $APP_DIR = $ENV{PWD} || "";
1077    $APP_DIR = dirname($0)
1078      if !$APP_DIR || !File::Spec->file_name_is_absolute($APP_DIR);
1079    $APP_DIR = dirname( $ENV{SCRIPT_FILENAME} )
1080      if $ENV{SCRIPT_FILENAME}
1081      && ( !$APP_DIR || ( !File::Spec->file_name_is_absolute($APP_DIR) ) );
1082    $APP_DIR ||= $MT_DIR;
1083    $APP_DIR = File::Spec->rel2abs($APP_DIR);
1084
1085    return 1;
1086}
1087
1088sub init_core {
1089    my $mt = shift;
1090    return if exists $Components{'core'};
1091    require MT::Core;
1092    my $c = MT::Core->new( { id => 'core', path => $MT_DIR } )
1093      or die MT::Core->errstr;
1094    $Components{'core'} = $c;
1095
1096    # Additional locale-specific defaults
1097    my $defaults = $c->{registry}{config_settings};
1098    $defaults->{DefaultLanguage}{default} = '__BUILD_LANGUAGE__';
1099    $defaults->{NewsboxURL}{default} = '__NEWSBOX_URL__';
1100    $defaults->{LearningNewsURL}{default} = '__LEARNINGNEWS_URL__';
1101    $defaults->{SupportURL}{default} = '__SUPPORT_URL__';
1102    $defaults->{NewsURL}{default} = '__NEWS_URL__';
1103    #$defaults->{HelpURL}{default} = '__HELP_URL__';
1104    $defaults->{DefaultTimezone}{default} = '__DEFAULT_TIMEZONE__';
1105    $defaults->{TimeOffset}{default} = '__DEFAULT_TIMEZONE__';
1106    $defaults->{MailEncoding}{default} = '__MAIL_ENCODING__';
1107    $defaults->{ExportEncoding}{default} = '__EXPORT_ENCODING__';
1108    $defaults->{LogExportEncoding}{default} = '__LOG_EXPORT_ENCODING__';
1109    $defaults->{CategoryNameNodash}{default} = '__CATEGORY_NAME_NODASH__';
1110    $defaults->{PublishCharset}{default} = '__PUBLISH_CHARSET__';
1111
1112    push @Components, $c;
1113    return 1;
1114}
1115
1116sub init {
1117    my $mt    = shift;
1118    my %param = @_;
1119
1120    $mt->bootstrap() unless $MT_DIR;
1121    $mt->{mt_dir}     = $MT_DIR;
1122    $mt->{config_dir} = $CFG_DIR;
1123    $mt->{app_dir}    = $APP_DIR;
1124
1125    $mt->init_callbacks();
1126
1127    ## Initialize the language to the default in case any errors occur in
1128    ## the rest of the initialization process.
1129    $mt->init_config( \%param ) or return;
1130    $mt->init_addons(@_)       or return;
1131    $mt->init_config_from_db( \%param ) or return;
1132    $mt->init_plugins(@_)       or return;
1133    $plugins_installed = 1;
1134    $mt->init_schema();
1135    $mt->init_permissions();
1136
1137    # Load MT::Log so constants are available
1138    require MT::Log;
1139
1140    $mt->run_callbacks('post_init', $mt, \%param);
1141    return $mt;
1142}
1143
1144sub init_callbacks {
1145    my $mt = shift;
1146    MT->_register_core_callbacks({
1147        'build_file_filter' => sub { MT->publisher->queue_build_file_filter(@_) },
1148        'cms_upload_file' => \&core_upload_file_to_sync,
1149        'api_upload_file' => \&core_upload_file_to_sync,
1150    });
1151}
1152
1153sub core_upload_file_to_sync {
1154    my ($cb, %args) = @_;
1155    MT->upload_file_to_sync(%args);
1156}
1157
1158sub upload_file_to_sync {
1159    my $class = shift;
1160    my (%args) = @_;
1161
1162    # no need to do this unless we're syncing stuff.
1163    return unless MT->config('SyncTarget');
1164
1165    my $url = $args{url};
1166    my $file = $args{file};
1167    return unless -f $file;
1168
1169    my $blog = $args{blog};
1170    my $blog_id = $blog->id;
1171    return unless $blog->publish_queue;
1172
1173    require MT::FileInfo;
1174    my $base_url = $url;
1175    $base_url =~ s!^https?://[^/]+!!;
1176    my $fi = MT::FileInfo->load({ blog_id => $blog_id, url => $base_url });
1177    if (!$fi) {
1178        $fi = new MT::FileInfo;
1179        $fi->blog_id($blog_id);
1180        $fi->url($base_url);
1181        $fi->file_path($file);
1182    } else {
1183        $fi->file_path($file);
1184    }
1185    $fi->save;
1186
1187    require MT::TheSchwartz;
1188    require TheSchwartz::Job;
1189    my $job = TheSchwartz::Job->new();
1190    $job->funcname('MT::Worker::Sync');
1191    $job->uniqkey( $fi->id );
1192    $job->coalesce( ( $fi->blog_id || 0 ) . ':' . $$ . ':' . ( time - ( time % 10 ) ) );
1193    MT::TheSchwartz->insert($job);
1194}
1195
1196sub init_addons {
1197    my $mt = shift;
1198    my $cfg = $mt->config;
1199    my @PluginPaths;
1200
1201    unshift @PluginPaths, File::Spec->catdir( $MT_DIR, 'addons' );
1202    return $mt->_init_plugins_core({}, 1, \@PluginPaths);
1203}
1204
1205sub init_plugins {
1206    my $mt = shift;
1207
1208    # Load compatibility module for prior version
1209    # This should always be MT::Compat::v(MAJOR_RELEASE_VERSION - 1).
1210    require MT::Compat::v3;
1211
1212    require MT::Plugin;
1213    my $cfg          = $mt->config;
1214    my $use_plugins  = $cfg->UsePlugins;
1215    my @PluginPaths  = $cfg->PluginPath;
1216    my $PluginSwitch = $cfg->PluginSwitch || {};
1217    return $mt->_init_plugins_core($PluginSwitch, $use_plugins, \@PluginPaths);
1218}
1219
1220sub _init_plugins_core {
1221    my $mt = shift;
1222    my ($PluginSwitch, $use_plugins, $PluginPaths) = @_;
1223
1224    my $timer;
1225    if ($mt->config->PerformanceLogging) {
1226        $timer = $mt->get_timer();
1227    }
1228
1229    foreach my $PluginPath (@$PluginPaths) {
1230        my $plugin_lastdir = $PluginPath;
1231        $plugin_lastdir =~ s![\\/]$!!;
1232        $plugin_lastdir =~ s!.*[\\/]!!;
1233        local *DH;
1234        if ( opendir DH, $PluginPath ) {
1235            my @p = readdir DH;
1236          PLUGIN:
1237            for my $plugin (@p) {
1238                next if ( $plugin =~ /^\.\.?$/ || $plugin =~ /~$/ );
1239
1240                my $load_plugin = sub {
1241                    my ( $plugin, $sig ) = @_;
1242                    die "Bad plugin filename '$plugin'"
1243                      if ( $plugin !~ /^([-\\\/\@\:\w\.\s~]+)$/ );
1244                    local $plugin_sig      = $sig;
1245                    local $plugin_registry = {};
1246                    $plugin = $1;
1247                    if (
1248                        !$use_plugins
1249                        || ( exists $PluginSwitch->{$plugin_sig}
1250                            && !$PluginSwitch->{$plugin_sig} )
1251                      )
1252                    {
1253                        $Plugins{$plugin_sig}{full_path} = $plugin_full_path;
1254                        $Plugins{$plugin_sig}{enabled}   = 0;
1255                        return 0;
1256                    }
1257                    return 0 if exists $Plugins{$plugin_sig};
1258                    $Plugins{$plugin_sig}{full_path} = $plugin_full_path;
1259                    $timer->pause_partial if $timer;
1260                    eval "# line " . __LINE__ . " " . __FILE__ . "\nrequire '$plugin';";
1261                    $timer->mark("Loaded plugin " . $sig) if $timer;
1262                    if ($@) {
1263                        $Plugins{$plugin_sig}{error} = $@;
1264                        # Issue MT log within another eval block in the
1265                        # event that the plugin error is happening before
1266                        # the database has been initialized...
1267                        eval {
1268                            # line __LINE__ __FILE__
1269                            require MT::Log;
1270                            $mt->log(
1271                                {
1272                                    message => $mt->translate(
1273                                        "Plugin error: [_1] [_2]", $plugin,
1274                                        $Plugins{$plugin_sig}{error}
1275                                    ),
1276                                    class => 'system',
1277                                    level => MT::Log::ERROR()
1278                                }
1279                            );
1280                        };
1281                        return 0;
1282                    }
1283                    else {
1284                        if ( my $obj = $Plugins{$plugin_sig}{object} ) {
1285                            $obj->init_callbacks();
1286                        }
1287                        else {
1288
1289                            # A plugin did not register itself, so
1290                            # create a dummy plugin object which will
1291                            # cause it to show up in the plugin listing
1292                            # by it's filename.
1293                            MT->add_plugin( {} );
1294                        }
1295                    }
1296                    $Plugins{$plugin_sig}{enabled} = 1;
1297                    return 1;
1298                };
1299                $plugin_full_path = File::Spec->catfile( $PluginPath, $plugin );
1300                if ( -f $plugin_full_path ) {
1301                    $plugin_envelope = $plugin_lastdir;
1302                    $load_plugin->( $plugin_full_path, $plugin )
1303                      if $plugin_full_path =~ /\.pl$/;
1304                }
1305                else {
1306                    my $plugin_dir = $plugin;
1307                    $plugin_envelope = "$plugin_lastdir/" . $plugin;
1308
1309                    # handle config.yaml
1310                    my $yaml =
1311                      File::Spec->catdir( $plugin_full_path, 'config.yaml' );
1312
1313                    foreach my $lib (qw(lib extlib)) {
1314                        my $plib = File::Spec->catdir( $plugin_full_path, $lib );
1315                        unshift @INC, $plib if -d $plib;
1316                    }
1317
1318                    if ( -f $yaml ) {
1319                        my $pclass =
1320                          $plugin_dir =~ m/\.pack$/
1321                          ? 'MT::Component'
1322                          : 'MT::Plugin';
1323
1324                        # Don't process disabled plugin config.yaml files.
1325                        if (
1326                            $pclass eq 'MT::Plugin'
1327                            && (
1328                                !$use_plugins
1329                                || ( exists $PluginSwitch->{$plugin_dir}
1330                                    && !$PluginSwitch->{$plugin_dir} )
1331                            )
1332                          )
1333                        {
1334                            $Plugins{$plugin_dir}{full_path} =
1335                              $plugin_full_path;
1336                            $Plugins{$plugin_dir}{enabled} = 0;
1337                            next;
1338                        }
1339                        my $id = lc $plugin_dir;
1340                        $id =~ s/\.\w+$//;
1341                        my $p = $pclass->new(
1342                            {
1343                                id       => $id,
1344                                path     => $plugin_full_path,
1345                                envelope => $plugin_envelope
1346                            }
1347                        );
1348
1349                        # rebless? based on config?
1350                        local $plugin_sig = $plugin_dir;
1351                        MT->add_plugin($p);
1352                        $p->init_callbacks()
1353                            if $pclass eq 'MT::Plugin';
1354                        next;
1355                    }
1356
1357                    opendir SUBDIR, $plugin_full_path;
1358                    my @plugins = readdir SUBDIR;
1359                    closedir SUBDIR;
1360                    for my $plugin (@plugins) {
1361                        next if $plugin !~ /\.pl$/;
1362                        my $plugin_file =
1363                          File::Spec->catfile( $plugin_full_path, $plugin );
1364                        if ( -f $plugin_file ) {
1365                            $load_plugin->(
1366                                $plugin_file, $plugin_dir . '/' . $plugin
1367                            );
1368                        }
1369                    }
1370                }
1371            }
1372            closedir DH;
1373        }
1374    }
1375
1376    # Reset the Text_filters hash in case it was preloaded by plugins by
1377    # calling all_text_filters (Markdown in particular does this).
1378    # Upon calling all_text_filters again, it will be properly loaded by
1379    # querying the registry.
1380    %Text_filters = ();
1381
1382    1;
1383}
1384
1385my %addons;
1386
1387sub find_addons {
1388    my $mt = shift;
1389    my ($type) = @_;
1390
1391    unless (%addons) {
1392        my $addon_path = File::Spec->catdir( $MT_DIR, 'addons' );
1393        local *DH;
1394        if ( opendir DH, $addon_path ) {
1395            my @p = readdir DH;
1396            foreach my $p (@p) {
1397                next if $p eq '.' || $p eq '..';
1398                my $full_path = File::Spec->catdir( $addon_path, $p );
1399                if ( -d $full_path ) {
1400                    if ( $p =~ m/^(.+)\.(\w+)$/ ) {
1401                        my $label = $1;
1402                        my $id    = lc $1;
1403                        my $type  = $2;
1404                        if ( $type eq 'pack' ) {
1405                            $label .= ' Pack';
1406                        }
1407                        elsif ( $type eq 'theme' ) {
1408                            $label .= ' Theme';
1409                        }
1410                        elsif ( $type eq 'plugin' ) {
1411                            $label .= ' Plugin';
1412                        }
1413                        push @{ $addons{$type} },
1414                          {
1415                            label    => $label,
1416                            id       => $id,
1417                            envelope => 'addons/' . $p . '/',
1418                            path     => $full_path,
1419                          };
1420                    }
1421                }
1422            }
1423        }
1424    }
1425    if ($type) {
1426        my $addons = $addons{$type} ||= [];
1427        return $addons;
1428    }
1429    return 1;
1430}
1431
1432*mt_dir = \&server_path;
1433sub server_path { $_[0]->{mt_dir} }
1434sub app_dir     { $_[0]->{app_dir} }
1435sub config_dir  { $_[0]->{config_dir} }
1436
1437sub component {
1438    my $mt = shift;
1439    my ($id) = @_;
1440    return $Components{ lc $id };
1441}
1442
1443sub publisher {
1444    my $mt = shift;
1445    $mt = $mt->instance unless ref $mt;
1446    unless ( $mt->{WeblogPublisher} ) {
1447        require MT::WeblogPublisher;
1448        $mt->{WeblogPublisher} = new MT::WeblogPublisher();
1449    }
1450    $mt->{WeblogPublisher};
1451}
1452
1453sub rebuild {
1454    my $mt = shift;
1455    $mt->publisher->rebuild(@_)
1456      or return $mt->error( $mt->publisher->errstr );
1457}
1458
1459sub rebuild_entry {
1460    my $mt = shift;
1461    $mt->publisher->rebuild_entry(@_)
1462      or return $mt->error( $mt->publisher->errstr );
1463}
1464
1465sub rebuild_indexes {
1466    my $mt = shift;
1467    $mt->publisher->rebuild_indexes(@_)
1468      or return $mt->error( $mt->publisher->errstr );
1469}
1470
1471sub rebuild_archives {
1472    my $mt = shift;
1473    $mt->publisher->rebuild_archives(@_)
1474      or return $mt->error( $mt->publisher->errstr );
1475}
1476
1477sub ping {
1478    my $mt    = shift;
1479    my %param = @_;
1480    my $blog;
1481    require MT::Entry;
1482    require MT::Util;
1483    unless ( $blog = $param{Blog} ) {
1484        my $blog_id = $param{BlogID};
1485        $blog = MT::Blog->load($blog_id)
1486          or return $mt->trans_error( "Load of blog '[_1]' failed: [_2]",
1487            $blog_id, MT::Blog->errstr );
1488    }
1489
1490    my (@res);
1491
1492    my $send_updates = 1;
1493    if ( exists $param{OldStatus} ) {
1494        ## If this is a new entry (!$old_status) OR the status was previously
1495        ## set to draft, and is now set to publish, send the update pings.
1496        my $old_status = $param{OldStatus};
1497        if ( $old_status && $old_status eq MT::Entry::RELEASE() ) {
1498            $send_updates = 0;
1499        }
1500    }
1501
1502    if ( $send_updates && !( MT->config->DisableNotificationPings ) ) {
1503        ## Send update pings.
1504        my @updates = $mt->update_ping_list($blog);
1505        for my $url (@updates) {
1506            require MT::XMLRPC;
1507            if ( MT::XMLRPC->ping_update( 'weblogUpdates.ping', $blog, $url ) )
1508            {
1509                push @res, { good => 1, url => $url, type => "update" };
1510            }
1511            else {
1512                push @res,
1513                  {
1514                    good  => 0,
1515                    url   => $url,
1516                    type  => "update",
1517                    error => MT::XMLRPC->errstr
1518                  };
1519            }
1520        }
1521        if ( $blog->mt_update_key ) {
1522            require MT::XMLRPC;
1523            if ( MT::XMLRPC->mt_ping($blog) ) {
1524                push @res,
1525                  {
1526                    good => 1,
1527                    url  => $mt->{cfg}->MTPingURL,
1528                    type => "update"
1529                  };
1530            }
1531            else {
1532                push @res,
1533                  {
1534                    good  => 0,
1535                    url   => $mt->{cfg}->MTPingURL,
1536                    type  => "update",
1537                    error => MT::XMLRPC->errstr
1538                  };
1539            }
1540        }
1541    }
1542
1543    my $cfg     = $mt->{cfg};
1544    my $send_tb = $cfg->OutboundTrackbackLimit;
1545    return \@res if $send_tb eq 'off';
1546
1547    my @tb_domains;
1548    if ( $send_tb eq 'selected' ) {
1549        @tb_domains = $cfg->OutboundTrackbackDomains;
1550    }
1551    elsif ( $send_tb eq 'local' ) {
1552        my $iter = MT::Blog->load_iter();
1553        while ( my $b = $iter->() ) {
1554            next if $b->id == $blog->id;
1555            push @tb_domains, MT::Util::extract_domains( $b->site_url );
1556        }
1557    }
1558    my $tb_domains;
1559    if (@tb_domains) {
1560        $tb_domains = '';
1561        my %seen;
1562        local $_;
1563        foreach (@tb_domains) {
1564            next unless $_;
1565            $_ = lc($_);
1566            next if $seen{$_};
1567            $tb_domains .= '|' if $tb_domains ne '';
1568            $tb_domains .= quotemeta($_);
1569            $seen{$_} = 1;
1570        }
1571        $tb_domains = '(' . $tb_domains . ')' if $tb_domains;
1572    }
1573
1574    ## Send TrackBack pings.
1575    if ( my $entry = $param{Entry} ) {
1576        my $pings = $entry->to_ping_url_list;
1577
1578        my %pinged = map { $_ => 1 } @{ $entry->pinged_url_list };
1579        my $cats = $entry->categories;
1580        for my $cat (@$cats) {
1581            push @$pings, grep !$pinged{$_}, @{ $cat->ping_url_list };
1582        }
1583
1584        my $ua = MT->new_ua;
1585
1586        ## Build query string to be sent on each ping.
1587        my @qs;
1588        push @qs, 'title=' . MT::Util::encode_url( $entry->title );
1589        push @qs, 'url=' . MT::Util::encode_url( $entry->permalink );
1590        push @qs, 'excerpt=' . MT::Util::encode_url( $entry->get_excerpt );
1591        push @qs, 'blog_name=' . MT::Util::encode_url( $blog->name );
1592        my $qs = join '&', @qs;
1593
1594        ## Character encoding--best guess.
1595        my $enc = $mt->{cfg}->PublishCharset;
1596
1597        for my $url (@$pings) {
1598            $url =~ s/^\s*//;
1599            $url =~ s/\s*$//;
1600            my $url_domain;
1601            ($url_domain) = MT::Util::extract_domains($url);
1602            next if $tb_domains && lc($url_domain) !~ m/$tb_domains$/;
1603
1604            my $req = HTTP::Request->new( POST => $url );
1605            $req->content_type(
1606                "application/x-www-form-urlencoded; charset=$enc");
1607            $req->content($qs);
1608            my $res = $ua->request($req);
1609            if ( substr( $res->code, 0, 1 ) eq '2' ) {
1610                my $c = $res->content;
1611                my ( $error, $msg ) =
1612                  $c =~ m!<error>(\d+).*<message>(.+?)</message>!s;
1613                if ($error) {
1614                    push @res,
1615                      {
1616                        good  => 0,
1617                        url   => $url,
1618                        type  => 'trackback',
1619                        error => $msg
1620                      };
1621                }
1622                else {
1623                    push @res, { good => 1, url => $url, type => 'trackback' };
1624                }
1625            }
1626            else {
1627                push @res,
1628                  {
1629                    good  => 0,
1630                    url   => $url,
1631                    type  => 'trackback',
1632                    error => "HTTP error: " . $res->status_line
1633                  };
1634            }
1635        }
1636    }
1637    \@res;
1638}
1639
1640sub ping_and_save {
1641    my $mt    = shift;
1642    my %param = @_;
1643    if ( my $entry = $param{Entry} ) {
1644        my $results = MT::ping( $mt, @_ ) or return;
1645        my %still_ping;
1646        my $pinged = $entry->pinged_url_list;
1647        for my $res (@$results) {
1648            next if $res->{type} ne 'trackback';
1649            if ( !$res->{good} ) {
1650                $still_ping{ $res->{url} } = 1;
1651            }
1652            push @$pinged,
1653              $res->{url}
1654              . ( $res->{good}
1655                ? ''
1656                : ' ' . MT::I18N::encode_text( $res->{error} ) );
1657        }
1658        $entry->pinged_urls( join "\n", @$pinged );
1659        $entry->to_ping_urls( join "\n", keys %still_ping );
1660        $entry->save or return $mt->error( $entry->errstr );
1661        return $results;
1662    }
1663    1;
1664}
1665
1666sub needs_ping {
1667    my $mt    = shift;
1668    my %param = @_;
1669    my $blog  = $param{Blog};
1670    my $entry = $param{Entry};
1671    require MT::Entry;
1672    return unless $entry->status == MT::Entry::RELEASE();
1673    my $old_status = $param{OldStatus};
1674    my %list;
1675    ## If this is a new entry (!$old_status) OR the status was previously
1676    ## set to draft, and is now set to publish, send the update pings.
1677    if ( ( !$old_status || $old_status ne MT::Entry::RELEASE() )
1678        && !( MT->config->DisableNotificationPings ) )
1679    {
1680        my @updates = $mt->update_ping_list($blog);
1681        @list{@updates} = (1) x @updates;
1682        $list{ $mt->{cfg}->MTPingURL } = 1 if $blog && $blog->mt_update_key;
1683    }
1684    if ($entry) {
1685        @list{ @{ $entry->to_ping_url_list } } = ();
1686        my %pinged = map { $_ => 1 } @{ $entry->pinged_url_list };
1687        my $cats = $entry->categories;
1688        for my $cat (@$cats) {
1689            @list{ grep !$pinged{$_}, @{ $cat->ping_url_list } } = ();
1690        }
1691    }
1692    my @list = keys %list;
1693    return unless @list;
1694    \@list;
1695}
1696
1697sub update_ping_list {
1698    my $mt = shift;
1699    my ($blog) = @_;
1700
1701    my @updates;
1702    if ( my $pings = MT->registry('ping_servers') ) {
1703        my $up = $blog->update_pings;
1704        if ($up) {
1705            foreach ( split ',', $up ) {
1706                next unless exists $pings->{$_};
1707                push @updates, $pings->{$_}->{url};
1708            }
1709        }
1710    }
1711    if ( my $others = $blog->ping_others ) {
1712        push @updates, split /\r?\n/, $others;
1713    }
1714    my %updates;
1715    for my $url (@updates) {
1716        for ($url) {
1717            s/^\s*//;
1718            s/\s*$//;
1719        }
1720        next unless $url =~ /\S/;
1721        $updates{$url}++;
1722    }
1723    keys %updates;
1724}
1725
1726{
1727    my $LH;
1728
1729    sub set_language {
1730        my $pkg = shift;
1731        require MT::L10N;
1732        $LH = MT::L10N->get_handle(@_);
1733
1734        # Clear any l10n_handles in request
1735        $pkg->request( 'l10n_handle', {} );
1736        return $LH;
1737    }
1738
1739    require MT::I18N;
1740
1741    sub translate {
1742        my $this = shift;
1743        my $app = ref($this) ? $this : $this->app;
1744        if ( $app->{component} ) {
1745            if ( my $c = $app->component( $app->{component} ) ) {
1746                local $app->{component} = undef;
1747                return $c->translate(@_);
1748            }
1749        }
1750        my ( $format, @args ) = @_;
1751        foreach (@args) {
1752            $_ = $_->() if ref($_) eq 'CODE';
1753        }
1754        my $enc = MT->instance->config('PublishCharset') || 'utf-8';
1755        return $LH->maketext( $format, @args ) if $enc =~ m/utf-?8/i;
1756        $format = MT::I18N::encode_text( $format, $enc, 'utf-8' );
1757        MT::I18N::encode_text(
1758            $LH->maketext(
1759                $format,
1760                map { MT::I18N::encode_text( $_, $enc, 'utf-8' ) } @args
1761            ),
1762            'utf-8', $enc
1763        );
1764    }
1765
1766    sub translate_templatized {
1767        my $mt = shift;
1768        my $app = ref($mt) ? $mt : $mt->app;
1769        if ( $app->{component} ) {
1770            if ( my $c = $app->component( $app->{component} ) ) {
1771                local $app->{component} = undef;
1772                return $c->translate_templatized(@_);
1773            }
1774        }
1775        my @cstack;
1776        my ($text) = @_;
1777        while (1) {
1778            $text =~ s!(<(/)?(?:_|MT)_TRANS(_SECTION)?(?:(?:\s+((?:\w+)\s*=\s*(["'])(?:(<(?:[^"'>]|"[^"]*"|'[^']*')+)?>|[^\5]+?)*?\5))+?\s*/?)?>)!
1779            my($msg, $close, $section, %args) = ($1, $2, $3);
1780            while ($msg =~ /\b(\w+)\s*=\s*(["'])((?:<(?:[^"'>]|"[^"]*"|'[^']*')+?>|[^\2])*?)?\2/g) {  #"
1781                $args{$1} = $3;
1782            }
1783            if ($section) {
1784                if ($close) {
1785                    $mt = pop @cstack;
1786                } else {
1787                    if ($args{component}) {
1788                        push @cstack, $mt;
1789                        $mt = MT->component($args{component})
1790                            or die "Bad translation component: $args{component}";
1791                    }
1792                    else {
1793                        die "__trans_section without a component argument";
1794                    }
1795                }
1796                '';
1797            }
1798            else {
1799                $args{params} = '' unless defined $args{params};
1800                my @p = map MT::Util::decode_html($_),
1801                        split /\s*%%\s*/, $args{params}, -1;
1802                @p = ('') unless @p;
1803                my $translation = $mt->translate($args{phrase}, @p);
1804                if (exists $args{escape}) {
1805                    if (lc($args{escape}) eq 'html') {
1806                        $translation = MT::Util::encode_html($translation);
1807                    } elsif (lc($args{escape}) eq 'url') {
1808                        $translation = MT::Util::encode_url($translation);
1809                    } else {
1810                        # fallback for js/javascript/singlequotes
1811                        $translation = MT::Util::encode_js($translation);
1812                    }
1813                }
1814                $translation;
1815            }
1816            !igem or last;
1817        }
1818        return $text;
1819    }
1820
1821    sub current_language { $LH->language_tag }
1822    sub language_handle  { $LH }
1823
1824    sub charset {
1825        my $mt = shift;
1826        $mt->{charset} = shift if @_;
1827        return $mt->{charset} if $mt->{charset};
1828        $mt->{charset} = $mt->config->PublishCharset
1829          || $mt->language_handle->encoding;
1830    }
1831}
1832
1833sub supported_languages {
1834    my $mt = shift;
1835    require MT::L10N;
1836    require File::Basename;
1837    ## Determine full path to lib/MT/L10N directory...
1838    my $lib =
1839      File::Spec->catdir( File::Basename::dirname( $INC{'MT/L10N.pm'} ),
1840        'L10N' );
1841    ## ... From that, determine full path to extlib/MT/L10N.
1842    ## To do that, we look for the last instance of the string 'lib'
1843    ## in $lib and replace it with 'extlib'. reverse is a nice tricky
1844    ## way of doing that.
1845    ( my $extlib = reverse $lib ) =~ s!bil!biltxe!;
1846    $extlib = reverse $extlib;
1847    my @dirs = ( $lib, $extlib );
1848    my %langs;
1849    for my $dir (@dirs) {
1850        opendir DH, $dir or next;
1851        for my $f ( readdir DH ) {
1852            my ($tag) = $f =~ /^(\w+)\.pm$/;
1853            next unless $tag;
1854            my $lh = MT::L10N->get_handle($tag);
1855            $langs{ $lh->language_tag } = $lh->language_name;
1856        }
1857        closedir DH;
1858    }
1859    \%langs;
1860}
1861
1862# For your convenience
1863sub trans_error {
1864    my $app = shift;
1865    $app->error( $app->translate(@_) );
1866}
1867
1868sub all_text_filters {
1869    unless (%Text_filters) {
1870        if ( my $filters = MT->registry('text_filters') ) {
1871            %Text_filters = %$filters if ref($filters) eq 'HASH';
1872        }
1873    }
1874    if (my $enabled_filters = MT->config('AllowedTextFilters')) {
1875        my %enabled = map { $_ => 1 } split /\s*,\s*/, $enabled_filters;
1876        %Text_filters = map { $_ => $Text_filters{$_} }
1877                        grep { exists $enabled{$_} }
1878                        keys %Text_filters;
1879    }
1880    return \%Text_filters;
1881}
1882
1883sub apply_text_filters {
1884    my $mt = shift;
1885    my ( $str, $filters, @extra ) = @_;
1886    my $all_filters = $mt->all_text_filters;
1887    for my $filter (@$filters) {
1888        my $f = $all_filters->{$filter} or next;
1889        my $code = $f->{code} || $f->{handler};
1890        unless ( ref($code) eq 'CODE' ) {
1891            $code = $mt->handler_to_coderef($code);
1892            $f->{code} = $code;
1893        }
1894        if ( !$code ) {
1895            warn "Bad text filter: $filter";
1896            next;
1897        }
1898        $str = $code->( $str, @extra );
1899    }
1900    return $str;
1901}
1902
1903sub static_path {
1904    my $app = shift;
1905    my $spath = $app->config->StaticWebPath;
1906    if (!$spath) {
1907        $spath = $app->config->CGIPath;
1908        $spath .= '/' unless $spath =~ m!/$!;
1909        $spath .= 'mt-static/';
1910    } else {
1911        $spath .= '/' unless $spath =~ m!/$!;
1912    }
1913    $spath;
1914}
1915
1916sub static_file_path {
1917    my $app = shift;
1918    return $app->{__static_file_path}
1919        if exists $app->{__static_file_path};
1920
1921    my $path = $app->config('StaticFilePath');
1922    return $app->{__static_file_path} = $path if defined $path;
1923
1924    # Attempt to derive StaticFilePath based on environment
1925    my $web_path = $app->config->StaticWebPath || 'mt-static';
1926    $web_path =~ s!^https?://[^/]+/!!;
1927    if ($app->can('document_root')) {
1928        my $doc_static_path = File::Spec->catdir($app->document_root(), $web_path);
1929        return $app->{__static_file_path} = $doc_static_path
1930            if -d $doc_static_path;
1931    }
1932    my $mtdir_static_path = File::Spec->catdir($app->mt_dir, 'mt-static');
1933    return $app->{__static_file_path} = $mtdir_static_path
1934        if -d $mtdir_static_path;
1935    return;
1936}
1937
1938sub template_paths {
1939    my $mt = shift;
1940    my @paths;
1941    my $path = $mt->config->TemplatePath;
1942    if ($mt->{plugin_template_path}) {
1943        if (File::Spec->file_name_is_absolute($mt->{plugin_template_path})) {
1944            push @paths, $mt->{plugin_template_path}
1945                if -d $mt->{plugin_template_path};
1946        } else {
1947            my $dir = File::Spec->catdir($mt->app_dir,
1948                                         $mt->{plugin_template_path});
1949            if (-d $dir) {
1950                push @paths, $dir;
1951            } else {
1952                $dir = File::Spec->catdir($mt->mt_dir,
1953                                          $mt->{plugin_template_path});
1954                push @paths, $dir if -d $dir;
1955            }
1956        }
1957    }
1958    if (my $alt_path = $mt->config->AltTemplatePath) {
1959        if (-d $alt_path) {    # AltTemplatePath is absolute
1960            push @paths, File::Spec->catdir($alt_path,
1961                                            $mt->{template_dir})
1962                if $mt->{template_dir};
1963            push @paths, $alt_path;
1964        }
1965    }
1966 
1967    for my $addon ( @{ $mt->find_addons('pack') } ) {
1968        push @paths, File::Spec->catdir($addon->{path}, 'tmpl', $mt->{template_dir})
1969            if $mt->{template_dir};
1970        push @paths, File::Spec->catdir($addon->{path}, 'tmpl');
1971    }
1972
1973    push @paths, File::Spec->catdir($path, $mt->{template_dir})
1974        if $mt->{template_dir};
1975    push @paths, $path;
1976 
1977    return @paths;
1978}
1979
1980sub find_file {
1981    my $mt = shift;
1982    my ($paths, $file) = @_;
1983    my $filename;
1984    foreach my $p (@$paths) {
1985        my $filepath = File::Spec->canonpath(File::Spec->catfile($p, $file));
1986        $filename = File::Spec->canonpath($filepath);
1987        return $filename if -f $filename;
1988    }
1989    undef;
1990}
1991
1992sub load_tmpl {
1993    my $mt = shift;
1994    if ( exists($mt->{component}) && ( $mt->{component} ne 'Core' ) ) {
1995        if (my $c = $mt->component($mt->{component})) {
1996            return $c->load_tmpl(@_);
1997        }
1998    }
1999
2000    my($file, @p) = @_;
2001    my $param;
2002    if (@p && (ref($p[$#p]) eq 'HASH')) {
2003        $param = pop @p;
2004    }
2005    my $cfg = $mt->config;
2006    require MT::Template;
2007    my $tmpl;
2008    my @paths = $mt->template_paths;
2009
2010    my $type = {'SCALAR' => 'scalarref', 'ARRAY' => 'arrayref'}->{ref $file}
2011        || 'filename';
2012    $tmpl = MT::Template->new(
2013        type => $type, source => $file,
2014        path => \@paths,
2015        filter => sub {
2016            my ($str, $fname) = @_;
2017            if ($fname) {
2018                $fname = File::Basename::basename($fname);
2019                $fname =~ s/\.tmpl$//;
2020                $mt->run_callbacks("template_source.$fname", $mt, @_);
2021            } else {
2022                $mt->run_callbacks("template_source", $mt, @_);
2023            }
2024            return $str;
2025        },
2026        @p);
2027    return $mt->error(
2028        $mt->translate("Loading template '[_1]' failed.", $file)) unless $tmpl;
2029    $mt->set_default_tmpl_params($tmpl);
2030    $tmpl->param($param) if $param;
2031    $tmpl;
2032}
2033
2034sub set_default_tmpl_params {
2035    my $mt = shift;
2036    my ($tmpl) = @_;
2037    my $param = {};
2038    $param->{mt_debug} = $MT::DebugMode;
2039    $param->{mt_beta} = 1 if MT->version_id =~ m/^\d+\.\d+(?:a|b|rc)/;
2040    $param->{static_uri} = $mt->static_path;
2041    $param->{mt_version} = MT->version_number;
2042    $param->{mt_version_id} = MT->version_id;
2043    $param->{mt_product_code} = MT->product_code;
2044    $param->{mt_product_name} = $mt->translate(MT->product_name);
2045    $param->{language_tag} = substr($mt->current_language, 0, 2);
2046    $param->{language_encoding} = $mt->charset;
2047    if ($mt->isa('MT::App')) {
2048        if (my $author = $mt->user) {
2049            $param->{author_id} = $author->id;
2050            $param->{author_name} = $author->name;
2051        }
2052        ## We do this in load_tmpl because show_error and login don't call
2053        ## build_page; so we need to set these variables here.
2054        require MT::Auth;
2055        $param->{can_logout} = MT::Auth->can_logout;
2056        $param->{script_url} = $mt->uri;
2057        $param->{mt_url} = $mt->mt_uri;
2058        $param->{script_path} = $mt->path;
2059        $param->{script_full_url} = $mt->base . $mt->uri;
2060        $param->{agent_mozilla} = ( $ENV{HTTP_USER_AGENT} || '' ) =~ /gecko/i;
2061        $param->{agent_ie} = ( $ENV{HTTP_USER_AGENT} || '' ) =~ /\bMSIE\b/;
2062    }
2063    if (!$tmpl->param('template_filename')) {
2064        if (my $fname = $tmpl->{__file}) {
2065            $fname =~ s!\\!/!g;
2066            $fname =~ s/\.tmpl$//;
2067            $param->{template_filename} = $fname;
2068        }
2069    }
2070    $tmpl->param($param);
2071}
2072
2073sub process_mt_template {
2074    my $mt = shift;
2075    my ($body) = @_;
2076    $body =~ s@<(?:_|MT)_ACTION\s+mode="([^"]+)"(?:\s+([^>]*))?>@
2077        my $mode = $1; my %args;
2078        %args = $2 =~ m/\s*(\w+)="([^"]*?)"\s*/g if defined $2; # "
2079        MT::Util::encode_html($mt->uri(mode => $mode, args => \%args));
2080    @geis;
2081    # Strip out placeholder wrappers to facilitate tmpl_* callbacks
2082    $body =~ s/<\/?MT_(\w+):(\w+)>//g;
2083    $body;
2084}
2085
2086sub build_page {
2087    my $mt = shift;
2088    my($file, $param) = @_;
2089    my $tmpl;
2090    my $mode = $mt->mode;
2091    $param->{"mode_$mode"} ||= 1;
2092    $param->{breadcrumbs} = $mt->{breadcrumbs};
2093    if ($param->{breadcrumbs}[-1]) {
2094        $param->{breadcrumbs}[-1]{is_last} = 1;
2095        $param->{page_titles} = [ reverse @{ $mt->{breadcrumbs} } ];
2096    }
2097    pop @{ $param->{page_titles} };
2098    if (my $lang_id = $mt->current_language) {
2099        $param->{local_lang_id} ||= lc $lang_id;
2100    }
2101    $param->{magic_token} = $mt->current_magic if $mt->user;
2102
2103    # List of installed packs in the application footer
2104    my @packs_installed;
2105    my $packs = $mt->find_addons('pack');
2106    if ($packs) {
2107        foreach my $pack (@$packs) {
2108            my $c = $mt->component(lc $pack->{id});
2109            if ($c) {
2110                my $label = $c->label || $pack->{label};
2111                $label = $label->() if ref($label) eq 'CODE';
2112                # if the component did not declare a label,
2113                # it isn't wanting to be visible on the app footer.
2114                next if $label eq $c->{plugin_sig};
2115                push @packs_installed, {
2116                    label => $label,
2117                    version => $c->version,
2118                    id => $c->id,
2119                };
2120            }
2121        }
2122    }
2123    @packs_installed = sort { $a->{label} cmp $b->{label} } @packs_installed;
2124    $param->{packs_installed} = \@packs_installed;
2125    $param->{portal_url} = $mt->translate("__PORTAL_URL__");
2126
2127    for my $config_field (keys %{ MT::ConfigMgr->instance->{__var} || {} }) {
2128        $param->{ $config_field . '_readonly' } = 1;
2129    }
2130
2131    my $tmpl_file = '';
2132    if (UNIVERSAL::isa($file, 'MT::Template')) {
2133        $tmpl = $file;
2134        $tmpl_file = (exists $file->{__file}) ? $file->{__file} : '';
2135    } else {
2136        $tmpl = $mt->load_tmpl($file) or return;
2137        $tmpl_file = $file unless ref($file);
2138    }
2139
2140    if (($mode && ($mode !~ m/delete/)) && ($mt->{login_again} ||
2141        ($mt->{requires_login} && !$mt->user))) {
2142        ## If it's a login screen, direct the user to where they were going
2143        ## (query params including mode and all) unless they were logging in,
2144        ## logging out, or deleting something.
2145        my $q = $mt->{query};
2146        if ($mode) {
2147            my @query = map { { name => $_, value => scalar encode_text( $q->param($_) ) }; }
2148                grep { ($_ ne 'username') && ($_ ne 'password') && ($_ ne 'submit') && ($mode eq 'logout' ? ($_ ne '__mode') : 1) } $q->param;
2149            $param->{query_params} = \@query;
2150        }
2151        $param->{login_again} = $mt->{login_again};
2152    }
2153
2154    my $blog = $mt->blog;
2155    $tmpl->context()->stash('blog', $blog) if $blog;
2156
2157    $tmpl->param($param) if $param;
2158
2159    if ($tmpl_file) {
2160        $tmpl_file = File::Basename::basename($tmpl_file);
2161        $tmpl_file =~ s/\.tmpl$//;
2162        $tmpl_file = '.' . $tmpl_file;
2163    }
2164    $mt->run_callbacks('template_param' . $tmpl_file, $mt, $tmpl->param, $tmpl);
2165
2166    my $output = $mt->build_page_in_mem($tmpl);
2167    return unless defined $output;
2168
2169    $mt->run_callbacks('template_output' . $tmpl_file, $mt, \$output, $tmpl->param, $tmpl);
2170    return $output;
2171}
2172
2173sub build_page_in_mem {
2174    my $mt = shift;
2175    my($tmpl, $param) = @_;
2176    $tmpl->param($param) if $param;
2177    my $out = $tmpl->output;
2178    return $mt->error($tmpl->errstr) unless defined $out;
2179    return $mt->translate_templatized($mt->process_mt_template($out));
2180}
2181
2182sub new_ua {
2183    my $class = shift;
2184    my ($opt) = @_;
2185    $opt ||= {};
2186    my $lwp_class = 'LWP::UserAgent';
2187    if ($opt->{paranoid}) {
2188        eval { require LWPx::ParanoidAgent; };
2189        $lwp_class = 'LWPx::ParanoidAgent' unless $@;
2190    }
2191    eval "require $lwp_class;";
2192    return undef if $@;
2193    my $cfg = $class->config;
2194    my $max_size = exists $opt->{max_size} ? $opt->{max_size} : 100_000;
2195    my $timeout = exists $opt->{timeout} ? $opt->{timeout} : $cfg->HTTPTimeout || $cfg->PingTimeout;
2196    my $proxy = exists $opt->{proxy} ? $opt->{proxy} : $cfg->HTTPProxy || $cfg->PingProxy;
2197    my $no_proxy = exists $opt->{no_proxy} ? $opt->{no_proxy} : $cfg->HTTPNoProxy || $cfg->PingNoProxy;
2198    my $agent = $opt->{agent} || 'MovableType/' . $MT::VERSION;
2199    my $interface = exists $opt->{interface} ? $opt->{interface} : $cfg->HTTPInterface || $cfg->PingInterface;
2200
2201    if ( my $localaddr = $interface ) {
2202        @LWP::Protocol::http::EXTRA_SOCK_OPTS = (
2203            LocalAddr => $localaddr,
2204            Reuse     => 1
2205        );
2206    }
2207
2208    my $ua = $lwp_class->new;
2209    $ua->max_size($max_size) if (defined $max_size) && $ua->can('max_size');
2210    $ua->agent( $agent );
2211    $ua->timeout( $timeout ) if defined $timeout;
2212    if ( defined $proxy ) {
2213        $ua->proxy( http => $proxy );
2214        my @domains = split( /,\s*/, $no_proxy ) if $no_proxy;
2215        $ua->no_proxy(@domains) if @domains;
2216    }
2217    return $ua;
2218}
2219
2220sub build_email {
2221    my $class = shift;
2222    my ( $file, $param ) = @_;
2223    my $mt = $class->instance;
2224
2225    # basically, try to load from database
2226    my $blog = $param->{blog} || undef;
2227    my $id = $file;
2228    $id =~ s/(\.tmpl|\.mtml)$//;
2229
2230    require MT::Template;
2231    my @tmpl = MT::Template->load(
2232        {
2233            ( $blog ? ( blog_id => [ $blog->id, 0 ] ) : ( blog_id => 0 ) ),
2234            identifier => $id,
2235            type       => 'email',
2236        }
2237    );
2238    my $tmpl =
2239      @tmpl
2240      ? (
2241        scalar @tmpl > 1
2242        ? ( $tmpl[0]->blog_id ? $tmpl[0] : $tmpl[1] )
2243        : $tmpl[0]
2244      )
2245      : undef;
2246
2247    # try to load from file
2248    unless ($tmpl) {
2249        local $mt->{template_dir} = 'email';
2250        $tmpl = $mt->load_tmpl($file);
2251    }
2252    return unless $tmpl;
2253
2254    my $ctx = $tmpl->context;
2255    $ctx->stash( 'blog_id', $blog->id ) if $blog;
2256    $ctx->stash( 'blog',   delete $param->{'blog'} )   if $param->{'blog'};
2257    $ctx->stash( 'entry',  delete $param->{'entry'} )  if $param->{'entry'};
2258    $ctx->stash( 'author', delete $param->{'author'} ) if $param->{'author'};
2259    $ctx->stash( 'commenter', delete $param->{'commenter'} )
2260      if $param->{'commenter'};
2261    $ctx->stash( 'comment', delete $param->{'comment'} ) if $param->{'comment'};
2262    $ctx->stash( 'category', delete $param->{'category'} )
2263      if $param->{'category'};
2264    $ctx->stash( 'ping', delete $param->{'ping'} ) if $param->{'ping'};
2265
2266    foreach my $p (%$param) {
2267        if ( ref($p) ) {
2268            $tmpl->param( $p, $param->{$p} );
2269        }
2270    }
2271    return $mt->build_page_in_mem( $tmpl, $param );
2272}
2273
2274sub get_next_sched_post_for_user {
2275    my ( $author_id, @further_blog_ids ) = @_;
2276    require MT::Permission;
2277    my @perms = MT::Permission->load( { author_id => $author_id }, {} );
2278    my @blogs = @further_blog_ids;
2279    for my $perm (@perms) {
2280        next
2281          unless ( $perm->can_edit_config
2282            || $perm->can_publish_post
2283            || $perm->can_edit_all_posts );
2284        push @blogs, $perm->blog_id;
2285    }
2286    my $next_sched_utc = undef;
2287    require MT::Entry;
2288    for my $blog_id (@blogs) {
2289        my $blog           = MT::Blog->load($blog_id)
2290            or next;
2291        my $earliest_entry = MT::Entry->load(
2292            {
2293                status  => MT::Entry::FUTURE(),
2294                blog_id => $blog_id
2295            },
2296            { 'sort' => 'created_on' }
2297        );
2298        if ($earliest_entry) {
2299            my $entry_utc =
2300              MT::Util::ts2iso( $blog, $earliest_entry->created_on );
2301            if ( $entry_utc < $next_sched_utc || !defined($next_sched_utc) ) {
2302                $next_sched_utc = $entry_utc;
2303            }
2304        }
2305    }
2306    return $next_sched_utc;
2307}
2308
2309our %Commenter_Auth;
2310
2311sub init_commenter_authenticators {
2312    my $self = shift;
2313    my $auths = $self->registry("commenter_authenticators") || {};
2314    foreach my $auth ( keys %$auths ) {
2315        delete $auths->{$auth}
2316          if exists( $auths->{$auth}->{condition} )
2317          && !( $auths->{$auth}->{condition}->() );
2318    }
2319    %Commenter_Auth = %$auths;
2320    $Commenter_Auth{$_}{key} ||= $_ for keys %Commenter_Auth;
2321}
2322
2323sub commenter_authenticator {
2324    my $self = shift;
2325    my ($key) = @_;
2326    %Commenter_Auth or $self->init_commenter_authenticators();
2327    return $Commenter_Auth{$key};
2328}
2329
2330sub commenter_authenticators {
2331    my $self = shift;
2332    %Commenter_Auth or $self->init_commenter_authenticators();
2333    return values %Commenter_Auth;
2334}
2335
2336sub _commenter_auth_params {
2337    my ( $key, $blog_id, $entry_id, $static ) = @_;
2338    my $params = {
2339        blog_id => $blog_id,
2340        static  => $static,
2341    };
2342    $params->{entry_id} = $entry_id if defined $entry_id;
2343    return $params;
2344}
2345
2346sub _openid_commenter_condition {
2347    eval "require Digest::SHA1;";
2348    return $@ ? 0 : 1;
2349}
2350
2351sub core_commenter_authenticators {
2352    return {
2353        'OpenID' => {
2354            class      => 'MT::Auth::OpenID',
2355            label      => 'OpenID',
2356            login_form => <<OpenID,
2357<form method="post" action="<mt:var name="script_url">">
2358<input type="hidden" name="__mode" value="login_external" />
2359<input type="hidden" name="blog_id" value="<mt:var name="blog_id">" />
2360<input type="hidden" name="entry_id" value="<mt:var name="entry_id">" />
2361<input type="hidden" name="static" value="<mt:var name="static" escape="html">" />
2362<fieldset>
2363<mtapp:setting
2364    id="openid_display"
2365    label="<__trans phrase="OpenID URL">"
2366    hint="<__trans phrase="Sign in using your OpenID identity.">">
2367<input type="hidden" name="key" value="OpenID" />
2368<input name="openid_url" style="background: #fff url('<mt:var name="static_uri">images/comment/openid_logo.png') no-repeat left; padding-left: 18px; padding-bottom: 1px; border: 1px solid #5694b6; width: 304px; font-size: 110%;" />
2369    <p class="hint"><__trans phrase="OpenID is an open and decentralized single sign-on identity system."></p>
2370</mtapp:setting>
2371<img src="<mt:var name="static_uri">images/comment/openid_enabled.png" class="right" />
2372<div class="actions-bar actions-bar-login">
2373    <div class="actions-bar-inner pkg actions">
2374        <button
2375            type="submit"
2376            class="primary-button"
2377            ><__trans phrase="Sign in"></button>
2378    </div>
2379</div>
2380<p><img src="<mt:var name="static_uri">images/comment/blue_moreinfo.png"> <a href="http://www.openid.net/"><__trans phrase="Learn more about OpenID."></a></p>
2381</fieldset>
2382</form>
2383OpenID
2384            login_form_params => \&_commenter_auth_params,
2385            condition         => \&_openid_commenter_condition,
2386            logo              => 'images/comment/signin_openid.png',
2387            logo_small        => 'images/comment/openid_logo.png',
2388        },
2389        'LiveJournal' => {
2390            class      => 'MT::Auth::LiveJournal',
2391            label      => 'LiveJournal',
2392            login_form => <<LiveJournal,
2393<form method="post" action="<mt:var name="script_url">">
2394<input type="hidden" name="__mode" value="login_external" />
2395<input type="hidden" name="blog_id" value="<mt:var name="blog_id">" />
2396<input type="hidden" name="entry_id" value="<mt:var name="entry_id">" />
2397<input type="hidden" name="static" value="<mt:var name="static" escape="html">" />
2398<input type="hidden" name="key" value="LiveJournal" />
2399<fieldset>
2400<mtapp:setting
2401    id="livejournal_display"
2402    label="<__trans phrase="Your LiveJournal Username">"
2403    hint="<__trans phrase="Sign in using your Vox blog URL">">
2404<input name="openid_userid" style="background: #fff url('<mt:var name="static_uri">images/comment/livejournal_logo.png') no-repeat 2px center; padding: 2px 2px 2px 20px; border: 1px solid #5694b6; width: 300px; font-size: 110%;" />
2405</mtapp:setting>
2406<div class="actions-bar actions-bar-login">
2407    <div class="actions-bar-inner pkg actions">
2408        <button
2409            type="submit"
2410            class="primary-button"
2411            ><__trans phrase="Sign in"></button>
2412    </div>
2413</div>
2414<p><img src="<mt:var name="static_uri">images/comment/blue_moreinfo.png"> <a href="http://www.livejournal.com/"><__trans phrase="Learn more about LiveJournal."></a></p>
2415</fieldset>
2416</form>
2417LiveJournal
2418            login_form_params => \&_commenter_auth_params,
2419            condition         => \&_openid_commenter_condition,
2420            logo              => 'images/comment/signin_livejournal.png',
2421            logo_small        => 'images/comment/livejournal_logo.png',
2422        },
2423        'Vox' => {
2424            class      => 'MT::Auth::Vox',
2425            label      => 'Vox',
2426            login_form => <<Vox,
2427<form method="post" action="<mt:var name="script_url">">
2428<input type="hidden" name="__mode" value="login_external" />
2429<input type="hidden" name="blog_id" value="<mt:var name="blog_id">" />
2430<input type="hidden" name="entry_id" value="<mt:var name="entry_id">" />
2431<input type="hidden" name="static" value="<mt:var name="static" escape="html">" />
2432<input type="hidden" name="key" value="Vox" />
2433<fieldset>
2434<mtapp:setting
2435    id="vox_display"
2436    label="<__trans phrase="Your Vox Blog URL">">
2437http:// <input name="openid_userid" style="background: #fff url('<mt:var name="static_uri">images/comment/vox_logo.png') no-repeat 2px center; padding: 2px 2px 2px 20px; border: 1px solid #5694b6; width: 200px; font-size: 110%; vertical-align: middle" />.vox.com
2438</mtapp:setting>
2439<div class="actions-bar actions-bar-login">
2440    <div class="actions-bar-inner pkg actions">
2441        <button
2442            type="submit"
2443            class="primary-button"
2444            ><__trans phrase="Sign in"></button>
2445    </div>
2446</div>
2447<p><img src="<mt:var name="static_uri">images/comment/blue_moreinfo.png"> <a href="http://www.vox.com/"><__trans phrase="Learn more about Vox."></a></p>
2448</fieldset>
2449</form>
2450Vox
2451            login_form_params => \&_commenter_auth_params,
2452            condition         => \&_openid_commenter_condition,
2453            logo              => 'images/comment/signin_vox.png',
2454            logo_small        => 'images/comment/vox_logo.png',
2455        },
2456        'TypeKey' => {
2457            class      => 'MT::Auth::TypeKey',
2458            label      => 'TypeKey',
2459            login_form => <<TypeKey,
2460<p class="hint"><__trans phrase="TypeKey is a free, open system providing you a central identity for posting comments on weblogs and logging into other websites. You can register for free."></p>
2461<p><img src="<mt:var name="static_uri">images/comment/blue_goto.png"> <a href="<mt:var name="tk_signin_url">"><__trans phrase="Sign in or register with TypeKey."></a></p>
2462TypeKey
2463            login_form_params => sub {
2464                my ( $key, $blog_id, $entry_id, $static ) = @_;
2465                my $entry = MT::Entry->load($entry_id) if $entry_id;
2466
2467                ## TypeKey URL
2468                require MT::Template::Context;
2469                my $ctx = MT::Template::Context->new;
2470                $ctx->stash( 'blog_id', $blog_id );
2471                my $blog = MT::Blog->load($blog_id);
2472                $ctx->stash( 'blog',  $blog );
2473                $ctx->stash( 'entry', $entry );
2474                my $params = {};
2475                $params->{tk_signin_url} =
2476                  MT::Template::Context::_hdlr_remote_sign_in_link( $ctx,
2477                    { static => $static } );
2478                return $params;
2479            },
2480            logo => 'images/comment/signin_typekey.png',
2481            logo_small        => 'images/comment/typekey_logo.png',
2482        },
2483    };
2484}
2485
2486our %Captcha_Providers;
2487
2488sub captcha_provider {
2489    my $self = shift;
2490    my ($key) = @_;
2491    $self->init_captcha_providers() unless %Captcha_Providers;
2492    return $Captcha_Providers{$key};
2493}
2494
2495sub captcha_providers {
2496    my $self = shift;
2497    $self->init_captcha_providers() unless %Captcha_Providers;
2498    my $def  = delete $Captcha_Providers{'mt_default'};
2499    my @vals = values %Captcha_Providers;
2500    if ( defined($def) && $def->{condition}->() ) {
2501        unshift @vals, $def;
2502    }
2503    @vals;
2504}
2505
2506sub core_captcha_providers {
2507    return {
2508        'mt_default' => {
2509            label     => 'Movable Type default',
2510            class     => 'MT::Util::Captcha',
2511            condition => sub {
2512                require MT::Util::Captcha;
2513                if ( my $error = MT::Util::Captcha->check_availability ) {
2514                    return 0;
2515                }
2516                1;
2517            },
2518        }
2519    };
2520}
2521
2522sub init_captcha_providers {
2523    my $self = shift;
2524    my $providers = $self->registry("captcha_providers") || {};
2525    foreach my $provider ( keys %$providers ) {
2526        delete $providers->{$provider}
2527          if exists( $providers->{$provider}->{condition} )
2528          && !( $providers->{$provider}->{condition}->() );
2529    }
2530    %Captcha_Providers = %$providers;
2531    $Captcha_Providers{$_}{key} ||= $_ for keys %Captcha_Providers;
2532}
2533
2534sub effective_captcha_provider {
2535    my $class = shift;
2536    my ($key) = @_;
2537    return undef unless $key;
2538    my $cp = $class->captcha_provider($key) or return;
2539    if ( exists $cp->{condition} ) {
2540        return undef unless $cp->{condition}->();
2541    }
2542    my $pkg = $cp->{class};
2543    $pkg =~ s/;//g;
2544    eval "require $pkg" or return;
2545    return $cp->{class};
2546}
2547
2548sub handler_to_coderef {
2549    my $pkg = shift;
2550    my ( $name, $delayed ) = @_;
2551
2552    return $name if ref($name) eq 'CODE';
2553    return undef unless defined $name && $name ne '';
2554
2555    my $code;
2556    if ( $name !~ m/->/ ) {
2557
2558        # check for Package::Routine first; if defined, return coderef
2559        no strict 'refs';
2560        $code = \&$name if defined &$name;
2561        return $code if $code;
2562    }
2563
2564    my $component;
2565    if ( $name =~ m!^\$! ) {
2566        if ( $name =~ s/^\$(\w+)::// ) {
2567            $component = $1;
2568        }
2569    }
2570    if ( $name =~ m/^\s*sub\s*\{/s ) {
2571        $code = eval $name or die $@;
2572
2573        if ($component) {
2574            return sub {
2575                my $mt_inst = MT->instance;
2576                local $mt_inst->{component} = $component;
2577                $code->(@_);
2578            };
2579        }
2580        else {
2581            return $code;
2582        }
2583    }
2584
2585    my $hdlr_pkg = $name;
2586    my $method;
2587    if ( $hdlr_pkg =~ s/(->|::)([^:]+)$// ) {    # strip routine name
2588        $method = $2 if $1 eq '->';
2589    }
2590    if ( !defined(&$name) && !$pkg->can( 'AUTOLOAD' ) ) {
2591
2592        # The delayed option will return a coderef that delays the loading
2593        # of the package holding the handler routine.
2594        if ($delayed) {
2595            if ($method) {
2596                return sub {
2597                    eval "# line " . __LINE__ . " " . __FILE__ . "\nrequire $hdlr_pkg;"
2598                      or Carp::confess(
2599                        "failed loading package $hdlr_pkg for routine $name: $@");
2600                    my $mt_inst = MT->instance;
2601                    local $mt_inst->{component} = $component
2602                      if $component;
2603                    return $hdlr_pkg->$method(@_);
2604                };
2605            }
2606            else {
2607                return sub {
2608                    eval "# line " . __LINE__ . " " . __FILE__ . "\nrequire $hdlr_pkg;"
2609                      or Carp::confess(
2610                        "failed loading package $hdlr_pkg for routine $name: $@");
2611                    my $mt_inst = MT->instance;
2612                    local $mt_inst->{component} = $component
2613                      if $component;
2614                    no strict 'refs';
2615                    my $hdlr = \&$name;
2616                    use strict 'refs';
2617                    return $hdlr->(@_);
2618                };
2619            }
2620        }
2621        else {
2622            eval "# line " . __LINE__ . " " . __FILE__ . "\nrequire $hdlr_pkg;"
2623              or Carp::confess(
2624                "failed loading package $hdlr_pkg for routine $name: $@");
2625        }
2626    }
2627    if ($method) {
2628        $code = sub {
2629            my $mt_inst = MT->instance;
2630            local $mt_inst->{component} = $component
2631              if $component;
2632            return $hdlr_pkg->$method(@_);
2633        };
2634    }
2635    else {
2636        if ($component) {
2637            $code = sub {
2638                no strict 'refs';
2639                my $hdlr = (
2640                    defined &$name ? \&$name
2641                    : ( $pkg->can( 'AUTOLOAD' ) ? \&$name
2642                        : undef )
2643                );
2644                use strict 'refs';
2645                if ($hdlr) {
2646                    my $mt_inst = MT->instance;
2647                    local $mt_inst->{component} = $component
2648                      if $component;
2649                    return $hdlr->(@_);
2650                }
2651                return undef;
2652              }
2653        }
2654        else {
2655            no strict 'refs';
2656            $code =
2657              (
2658                defined &$name
2659                ? \&$name
2660                : ( $hdlr_pkg->can( 'AUTOLOAD' ) ? \&$name : undef )
2661              );
2662        }
2663    }
2664    return $code;
2665}
2666
2667sub help_url {
2668    my $pkg = shift;
2669    my ( $append ) = @_;
2670
2671    my $url = $pkg->config->HelpURL;
2672    return $url if defined $url;
2673    $url = $pkg->translate('http://www.movabletype.org/documentation/');
2674    if ( $append ) {
2675        $url .= $append;
2676    }
2677    $url;
2678}
2679
2680sub register_refresh_cache_event {
2681    my $pkg = shift;
2682    my ($callback) = @_;
2683    return unless $callback;
2684
2685    MT->_register_core_callbacks({
2686        "$callback" => \&refresh_cache,
2687    });
2688}
2689
2690sub refresh_cache {
2691    my ($cb, %args) = @_;
2692
2693    require MT::Cache::Negotiate;
2694    my $cache_driver = MT::Cache::Negotiate->new();
2695    return unless $cache_driver;
2696
2697    $cache_driver->flush_all();
2698}
2699
2700sub DESTROY {
2701    # save_config here so not to miss any dirty config change to persist
2702    # particulary for those which does not construct MT::App.
2703    $_[0]->config->save_config();
2704}
2705
27061;
2707
2708__END__
2709
2710=head1 NAME
2711
2712MT - Movable Type
2713
2714=head1 SYNOPSIS
2715
2716    use MT;
2717    my $mt = MT->new;
2718    $mt->rebuild(BlogID => 1)
2719        or die $mt->errstr;
2720
2721=head1 DESCRIPTION
2722
2723The I<MT> class is the main high-level rebuilding/pinging interface in the
2724Movable Type library. It handles all rebuilding operations. It does B<not>
2725handle any of the application functionality--for that, look to I<MT::App> and
2726I<MT::App::CMS>, both of which subclass I<MT> to handle application requests.
2727
2728=head1 PLUGIN APPLICATIONS
2729
2730At any given time, the user of the Movable Type platform is
2731interacting with either the core Movable Type application, or a plugin
2732application (or "sub-application").
2733
2734A plugin application is a plugin with a user interface that inherits
2735functionality from Movable Type, and appears to the user as a
2736component of Movable Type. A plugin application typically has its own
2737templates displaying its own special features; but it inherits some
2738templates from Movable Type, such as the navigation chrome and error
2739pages.
2740
2741=head2 The MT Root and the Application Root
2742
2743To locate assets of the core Movable Type application and any plugin
2744applications, the platform uses two directory paths, C<mt_dir> and
2745C<app_dir>. These paths are returned by the MT class methods with the
2746same names, and some other methods return derivatives of these paths.
2747
2748Conceptually, mt_dir is the root of the Movable Type installation, and
2749app_dir is the root of the "currently running application", which
2750might be Movable Type or a plugin application. It is important to
2751understand the distinction between these two values and what each is
2752used for.
2753
2754The I<mt_dir> is the absolute path to the directory where MT itself is
2755located. Most importantly, the MT configuration file and the CGI scripts that
2756bootstrap an MT request are found here. This directory is also the
2757default base path under which MT's core templates are found (but this
2758can be overridden using the I<TemplatePath> configuration setting).
2759
2760Likewise, the I<app_dir> is the directory where the "current"
2761application's assets are rooted. The platform will search for
2762application templates underneath the I<app_dir>, but this search also
2763searches underneath the I<mt_dir>, allowing the application to make
2764use of core headers, footers, error pages, and possibly other
2765templates.
2766
2767In order for this to be useful, the plugin's templates and
2768code should all be located underneath the same directory. The relative
2769path from the I<app_dir> to the application's templates is
2770configurable. For details on how to indicate the location of your
2771plugin's templates, see L<MT::App>.
2772
2773=head2 Finding the Root Paths
2774
2775When a plugin application initializes its own application class (a
2776subclass of MT::App), the I<mt_dir> should be discovered and passed
2777constructor. This comes either from the C<Directory> parameter or the
2778C<Config> parameter.
2779
2780Since plugins are loaded from a descendent of the MT root directory,
2781the plugin bootstrap code can discover the MT configuration file (and thus
2782the MT root directory) by traversing the filesystem; the absolute path
2783to that file can be passed as the C<Config> parameter to
2784MT::App::new. Working code to do this can be found in the
2785examples/plugins/mirror/mt-mirror.cgi file.
2786
2787The I<app_dir>, on the other hand, always derives from the location of
2788the currently-running program, so it typically does not need to be
2789specified.
2790
2791=head1 USAGE
2792
2793I<MT> has the following interface. On failure, all methods return C<undef>
2794and set the I<errstr> for the object or class (depending on whether the
2795method is an object or class method, respectively); look below at the section
2796L<ERROR HANDLING> for more information.
2797
2798=head2 MT->new( %args )
2799
2800Constructs a new I<MT> instance and returns that object. Returns C<undef>
2801on failure.
2802
2803I<new> will also read your MT configuration file (provided that it can find it--if
2804you find that it can't, take a look at the I<Config> directive, below). It
2805will also initialize the chosen object driver; the default is the C<DBM>
2806object driver.
2807
2808I<%args> can contain:
2809
2810=over 4
2811
2812=item * Config
2813
2814Path to the MT configuration file.
2815
2816If you do not specify a path, I<MT> will try to find your MT configuration file
2817in the current working directory.
2818
2819=item * Directory
2820
2821Path to the MT home directory.
2822
2823If you do not specify a path, I<MT> will try to find the MT directory using
2824the discovered path of the MT configuration file.
2825
2826=back
2827
2828=head2 $mt->init(%params)
2829
2830Initializes the Movable Type instance, including registration of basic
2831resources and callbacks. This method also invokes the C<init_config>
2832and C<init_plugins> methods.
2833
2834=head2 $mt->init_core()
2835
2836A method that the base MT class uses to initialize all the 'core'
2837functionality of Movable Type. If you want to subclass MT and extensively
2838modify it's core behavior, this method can be overridden to do that.
2839The L<MT::Core> module is a L<MT::Component> that defines the core
2840features of MT, and this method loads that component. Non-core components
2841are loaded by the L<init_addons> method.
2842
2843=head2 $mt->init_paths()
2844
2845Establishes some key file paths for the MT environment. Assigns
2846C<$MT_DIR>, C<$APP_DIR> and C<$CFG_FILE> package variables.
2847
2848=head2 $mt->init_permissions()
2849
2850Loads the L<MT::Permission> class and runs the
2851MT::Permission->init_permissions method to establish system permissions.
2852
2853=head2 $mt->init_schema()
2854
2855Completes the initialization of the Movable Type schema following the
2856loading of plugins. After this method runs, any MT object class may
2857safely be used.
2858
2859=head2 MT->instance
2860
2861MT and all it's subclasses are now singleton classes, meaning you can only
2862have one instance per package. MT->instance() returns the active instance.
2863MT->new() is now an alias to instance_of.
2864
2865=head2 MT->app
2866
2867An alias for the 'instance' method.
2868
2869=head2 $class->instance_of
2870
2871Returns the singleton instance of the MT subclass identified by C<$class>.
2872
2873=head2 $class->construct
2874
2875Constructs a new instance of the MT subclass identified by C<$class>.
2876
2877=head2 MT->set_instance
2878
2879Assigns the active MT instance object. This value is returned when
2880C<MT-E<gt>instance> is invoked.
2881
2882=head2 MT->run_app( $pkg, $params )
2883
2884Instantiates and runs a MT application (identified by C<$pkg>), passing
2885the C<$params> hashref as the parameters to the constructor method. This
2886method is a self-contained version found in L<MT::Bootstrap> and will
2887eventually be the manner in which MT applications are run (eliminating
2888the need for the bootstrap module). The MT::import module calls this
2889method when the MT module is used with an 'App' parameter. So, you can
2890write a mt.cgi script that looks like this:
2891
2892    #!/usr/bin/perl
2893    use strict;
2894    use lib $ENV{MT_HOME} ? "$ENV{MT_HOME}/lib" : 'lib';
2895    use MT App => 'MT::App::CMS';
2896
2897=head2 $mt->find_config($params)
2898
2899Handles the discovery of the MT configuration file. The path and filename
2900for the configuration file is returned as the result. The C<$params>
2901parameter is a reference to the hash of settings passed to the MT
2902constructor.
2903
2904=head2 $mt->init_config($params)
2905
2906Reads the MT configuration settingss from the MT configuration file.
2907
2908The C<$params> parameter is a reference to the hash of settings passed to
2909the MT constructor.
2910
2911=head2 $mt->init_config_from_db($param)
2912
2913Reads any MT configuration settings from the MT database (L<MT::Config>).
2914
2915The C<$params> parameter is a reference to the hash of settings passed to
2916the MT constructor.
2917
2918=head2 $mt->init_addons(%param)
2919
2920Loads any discoverable addons that are available. This is called from
2921the C<init> method, after C<init_config> method has loaded the
2922configuration settings, but prior to making a database connection.
2923
2924=head2 $mt->init_plugins(%param)
2925
2926Loads any discoverable plugins that are available. This is called from
2927the C<init> method, after the C<init_config> method has loaded the
2928configuration settings.
2929
2930=head2 $mt->init_callbacks()
2931
2932Installs any MT callbacks. This is called from the C<init> method very,
2933early; prior to loading any addons or plugins.
2934
2935=head2 $mt->init_tasks()
2936
2937Registers the standard set of periodic tasks that Movable Type provides
2938and then invokes the C<init_tasks> method for each available plugin.
2939
2940=head2 MT->run_tasks
2941
2942Initializes the tasks, running C<init_tasks> and invokes the task system
2943through L<MT::TaskMgr> to run any registered tasks that are pending
2944execution. See L<MT::TaskMgr> for further documentation.
2945
2946=head2 MT->find_addons( $type )
2947
2948Returns an array of all 'addons' that are found within the MT 'addons'
2949directory of the given C<$type>. What is returned is an array reference
2950of hash data. Each hash will contain these elements: 'label' (the name
2951of the addon), 'id' (the unique identifier of the addon), 'envelope'
2952(the subpath of the addon, relative to the MT home directory), and 'path'
2953(the full path to the addon subdirectory).
2954
2955=head2 MT->unplug
2956
2957Removes the global reference to the MT instance.
2958
2959=head2 MT::log( $message ) or $mt->log( $message )
2960
2961Adds an entry to the application's log table. Also writes message to
2962STDERR which is typically routed to the web server's error log.
2963
2964=head2 $mt->server_path, $mt->mt_dir
2965
2966Both of these methods return the physical file path to the directory
2967that is the home of the MT installation. This would be the value of
2968the 'Directory' parameter given in the MT constructor, or would be
2969determined based on the path of the configuration file.
2970
2971=head2 $mt->app_dir
2972
2973Returns the physical file path to the active application directory. This
2974is determined by the directory of the active script.
2975
2976=head2 $mt->config_dir
2977
2978Returns the path to the MT configuration file.
2979
2980=head2 $mt->config([$setting[, $value]])
2981
2982This method is used to get and set configuration settings. When called
2983without any parameters, it returns the active MT::ConfigMgr instance
2984used by the application.
2985
2986Specifying the C<$setting> parameter will return the value for that setting.
2987When passing the C<$value> parameter, this will update the config object,
2988assigning that value for the named C<$setting>.
2989
2990=head2 $mt->user_class
2991
2992Returns the package name for the class used for user authentication.
2993This is typically L<MT::Author>.
2994
2995=head2 $mt->request([$element[,$data]])
2996
2997The request method provides a request-scoped storage object. It is an
2998access interface for the L<MT::Request> package. Calling without any
2999parameters will return the L<MT::Request> instance.
3000
3001When called with the C<$element> parameter, the data stored for that
3002element is returned (or undef, if it didn't exist). When called with
3003the C<$data> parameter, it will store the data into the specified
3004element in the request object.
3005
3006All values placed in the request object are lost at the end of the
3007request. If the running application is not a web-based application,
3008the request object exists for the lifetime of the process and is
3009released when the process ends.
3010
3011See the L<MT::Request> package for more information.
3012
3013=head2 MT->new_ua
3014
3015Returns a new L<LWP::UserAgent> instance that is configured according to the
3016Movable Type configuration settings (specifically C<HTTPInterface>, C<HTTPTimeout>, C<HTTPProxy> and C<HTTPNoProxy>). The agent string is set
3017to "MovableType/(version)" and is also limited to receiving a response of
3018100,000 bytes by default (you can override this by using the 'max_size'
3019method on the returned instance). Using this method is recommended for
3020any HTTP requests issued by Movable Type since it uses the MT configuration
3021settings to prepare the UserAgent object.
3022
3023=head2 $mt->ping( %args )
3024
3025Sends all configured XML-RPC pings as a way of notifying other community
3026sites that your blog has been updated.
3027
3028I<%args> can contain:
3029
3030=over 4
3031
3032=item * Blog
3033
3034An I<MT::Blog> object corresponding to the blog for which you would like to
3035send the pings.
3036
3037Either this or C<BlogID> is required.
3038
3039=item * BlogID
3040
3041The ID of the blog for which you would like to send the pings.
3042
3043Either this or C<Blog> is required.
3044
3045=back
3046
3047=head2 $mt->ping_and_save( %args )
3048
3049Handles the task of issuing any pending ping operations for a given
3050entry and then saving that entry back to the database.
3051
3052The I<%args> hash should contain an element named C<Entry> that is a
3053reference to a L<MT::Entry> object.
3054
3055=head2 $mt->needs_ping(%param)
3056
3057Returns a list of URLs that have not been pinged for a given entry. Named
3058parameters for this method are:
3059
3060=over 4
3061
3062=item Entry
3063
3064The L<MT::Entry> object to examine.
3065
3066=item Blog
3067
3068The L<MT::Blog> object that is the parent of the entry given.
3069
3070=back
3071
3072The return value is an array reference of URLs that have not been pinged
3073for the given entry.
3074
3075An empty list is returned for entries that have a non 'RELEASE' status.
3076
3077=head2 $mt->update_ping_list($blog)
3078
3079Returns a list of URLs for ping services that have been configured to
3080be notified when posting new entries.
3081
3082=head2 $mt->set_language($tag)
3083
3084Loads the localization plugin for the language specified by I<$tag>, which
3085should be a valid and supported language tag--see I<supported_languages> to
3086obtain a list of supported languages.
3087
3088The language is set on a global level, and affects error messages and all
3089text in the administration system.
3090
3091This method can be called as either a class method or an object method; in
3092other words,
3093
3094    MT->set_language($tag)
3095
3096will also work. However, the setting will still be global--it will not be
3097specified to the I<$mt> object.
3098
3099The default setting--set when I<MT::new> is called--is U.S. English. If a
3100I<DefaultLanguage> is set in the MT configuration file, the default is then
3101set to that language.
3102
3103=head2 MT->translate($str[, $param, ...])
3104
3105Translates I<$str> into the currently-set language (set by I<set_language>),
3106and returns the translated string. Any parameters following I<$str> are
3107passed through to the C<maketext> method of the active localization module.
3108
3109=head2 MT->translate_templatized($str)
3110
3111Translates a string that has embedded E<lt>MT_TRANSE<gt> tags. These
3112tags identify the portions of the string that require localization.
3113Each tag is processed separately and passed through the MT->translate
3114method. Examples (used in your application's HTML::Template templates):
3115
3116    <p><MT_TRANS phrase="Hello, world"></p>
3117
3118and
3119
3120    <p><MT_TRANS phrase="Hello, [_1]" params="<TMPL_VAR NAME=NAME>"></p>
3121
3122=head2 $mt->trans_error( $str[, $arg1, $arg2] )
3123
3124Translates I<$str> into the currently-set language (set by I<set_language>),
3125and assigns it as the active error for the MT instance. It returns undef,
3126which is the usual return value upon generating an error in the application.
3127So when an error occurs, the typical return result would be:
3128
3129    if ($@) {
3130        return $app->trans_error("An error occurred: [_1]", $@);
3131    }
3132
3133The optional I<$arg1> (and so forth) parameters are passed as parameters to
3134any parameterized error message.
3135
3136=head2 $mt->current_language
3137
3138Returns the language tag for the currently-set language.
3139
3140=head2 MT->supported_languages
3141
3142Returns a reference to an associative array mapping language tags to their
3143proper names. For example:
3144
3145    use MT;
3146    my $langs = MT->supported_languages;
3147    print map { $_ . " => " . $langs->{$_} . "\n" } keys %$langs;
3148
3149=head2 MT->language_handle
3150
3151Returns the active MT::L10N language instance for the active language.
3152
3153=head2 MT->add_plugin($plugin)
3154
3155Adds the plugin described by $plugin to the list of plugins displayed
3156on the welcome page. The argument should be an object of the
3157I<MT::Plugin> class.
3158
3159=head2 MT->all_text_filters
3160
3161Returns a reference to a hash containing the registry of text filters.
3162
3163=head2 MT->apply_text_filters($str, \@filters)
3164
3165Applies the set of filters I<\@filters> to the string I<$str> and returns
3166the result (the filtered string).
3167
3168I<\@filters> should be a reference to an array of filter keynames--these
3169are the short names passed in as the first argument to I<add_text_filter>.
3170I<$str> should be a scalar string to be filtered.
3171
3172If one of the filters listed in I<\@filters> is not found in the list of
3173registered filters (that is, filters added through I<add_text_filter>),
3174it will be skipped silently. Filters are executed in the order in which they
3175appear in I<\@filters>.
3176
3177As it turns out, the I<MT::Entry::text_filters> method returns a reference
3178to the list of text filters to be used for that entry. So, for example, to
3179use this method to apply filters to the main entry text for an entry
3180I<$entry>, you would use
3181
3182    my $out = MT->apply_text_filters($entry->text, $entry->text_filters);
3183
3184=head2 MT->add_callback($meth, $priority, $plugin, $code)
3185
3186Registers a new callback handler for a particular registered callback.
3187
3188The first parameter is the name of the callback method. The second
3189parameter is a priority (a number in the range of 1-10) which will control
3190the order that the handler is executed in relation to other handlers. If
3191two handlers register with the same priority, they will be executed in
3192the order that they registered. The third parameter is a C<MT::Plugin> object
3193reference that is associated with the handler (this parameter is optional).
3194The fourth parameter is a code reference that is invoked to handle the
3195callback. For example:
3196
3197    MT->add_callback('BuildFile', 1, undef, \&rebuild_file_hdlr);
3198
3199The code reference should expect to receive an object of type
3200L<MT::Callback> as its first argument. This object is used to
3201communicate errors to the caller:
3202
3203    sub rebuild_file_hdlr {
3204        my ($cb, ...) = @_;
3205        if (something bad happens) {
3206            return $cb->error("Something bad happened!");
3207        }
3208    }
3209
3210Other parameters to the callback function depend on the callback point.
3211
3212The treatment of the error string depends on the callback point.
3213Typically, either it is ignored or the user's action fails and the
3214error message is displayed.
3215
3216The value returned from this method is the new L<MT::Callback> object.
3217
3218=head2 MT->remove_callback($callback)
3219
3220Removes a callback that was previously registered.
3221
3222=head2 MT->register_callbacks([...])
3223
3224Registers several callbacks simultaneously. Each element in the array
3225parameter given should be a hashref containing these elements: C<name>,
3226C<priority>, C<plugin> and C<code>.
3227
3228=head2 MT->run_callbacks($meth[, $arg1, $arg2, ...])
3229
3230Invokes a particular callback, running any associated callback handlers.
3231
3232The first parameter is the name of the callback to execute. This is one
3233of the global callback methods (see L<Callbacks> section) or can be
3234a class-specific method that includes the package name associated with
3235the callback.
3236
3237The remaining arguments are passed through to any callback handlers that
3238are invoked.
3239
3240For "Filter"-type callbacks, this routine will return a 0 if any of the
3241handlers return a false result. If all handlers return a true result,
3242a value of 1 is returned.
3243
3244Example:
3245
3246    MT->run_callbacks('MyClass::frobnitzes', \@whirlygigs);
3247
3248Which would execute any handlers that registered in this fashion:
3249
3250    MT->add_callback('MyClass::frobnitzes', 4, $plugin, \&frobnitz_hdlr);
3251
3252=head2 MT->run_callback($cb[, $arg1, $arg2, ...])
3253
3254An internal routine used by C<run_callbacks> to invoke a single
3255L<MT::Callback>.
3256
3257=head2 callback_error($str)
3258
3259This routine is used internally by C<MT::Callback> to set any error response
3260that comes from invoking a callback.
3261
3262=head2 callback_errstr
3263
3264This internal routine returns the error response stored using the
3265C<callback_error> routine.
3266
3267=head2 MT->handler_to_coderef($handler[, $delayed])
3268
3269Translates a registry handler signature into a Perl coderef. Handlers
3270are in one of the following forms:
3271
3272    $<COMPONENTID>::<PERL_PACKAGE>::<SUBROUTINE>
3273
3274    <PERL_PACKAGE>::<SUBROUTINE>
3275
3276    <PERL_PACKAGE>-><SUBROUTINE>
3277
3278    sub { ... }
3279
3280When invoked with a '-E<gt>' operator, the subroutine is invoked as
3281a package method.
3282
3283When the handler is a string that starts with 'sub {', it is eval'd
3284to compile it, and the resulting coderef is returned.
3285
3286The coderef that is returned can be passed any parameters you wish.
3287
3288When the coderef is invoked, any component that was identified in
3289the handler signature becomes the active component when running the
3290code (this affects how strings are translated, and the search paths
3291for templates that are loaded).
3292
3293If the C<$delayed> parameter is given, a special coderef is constructed
3294that will delay the 'require' of the identified Perl package until
3295the coderef is actually invoked.
3296
3297=head2 MT->registry( @path )
3298
3299Queries the Movable Type registry data structure for a given resource
3300path. The MT registry is a collection of hash structures that contain
3301resources MT and/or plugins can utilize.
3302
3303When this method is invoked, it actually issues a registry request
3304against each component registered with MT, then merges the resulting
3305hashes and returns them. See L<MT::Component> for further details.
3306
3307=head2 MT->component( $id )
3308
3309Returns a loaded L<MT::Component> based on the requested C<$id> parameter.
3310For example:
3311
3312    # Returns the MT 'core' component
3313    MT->component('core');
3314
3315=head2 MT->model( $id )
3316
3317Returns a Perl package name for the MT object type identified by C<$id>.
3318For example:
3319
3320    # Assigns (by default) 'MT::Blog' to $blog_class
3321    my $blog_class = MT->model('blog');
3322
3323It is a recommended practice to utilize the model method to derive the
3324implementation package name, instead of hardcoding Perl package names.
3325
3326=head2 MT->models( $id )
3327
3328Returns a list of object types that are registered as sub-types. For
3329instance, the MT 'asset' object type has several sub-types associated
3330with it:
3331
3332    my @types = MT->models('asset');
3333    # @types now contains ('asset', 'asset.image', 'asset.video', etc.)
3334
3335=head2 MT->product_code
3336
3337The product code identifying the Movable Type product that is installed.
3338This is either 'MTE' for Movable Type Enterprise or 'MT' for the
3339non-Enterprise product.
3340
3341=head2 MT->product_name
3342
3343The name of the Movable Type product that is installed. This is either
3344'Movable Type Enterprise' or 'Movable Type Publishing Platform'.
3345
3346=head2 MT->product_version
3347
3348The version number of the product. This is different from the C<version_id>
3349and C<version_number> methods as they report the API version information.
3350
3351=head2 MT->VERSION
3352
3353Returns the API version of MT. When using the MT module with the version
3354requirement, this method will also load the suitable API 'compatibility'
3355module, if available. For instance, if your plugin declares:
3356
3357    use MT 4;
3358
3359Then, once MT 5 is available, that statement will cause the C<VERSION> method
3360to attempt to load a module named "MT::Compat::v4". This module would contain
3361compatibility support for MT 4-based plugins.
3362
3363=head2 MT->version_id
3364
3365Returns the API version of MT (including any beta/alpha designations).
3366
3367=head2 MT->version_number
3368
3369Returns the numeric API version of MT (without any beta/alpha designations).
3370For example, if I<version_id> returned C<2.5b1>, I<version_number> would
3371return C<2.5>.
3372
3373=head2 MT->schema_version
3374
3375Returns the version of the MT database schema.
3376
3377=head2 $mt->id
3378
3379Provides an identifier for the application, one that relates to the
3380'application' paths of the MT registry. This method may be overridden
3381for any subclass of MT to provide the appropriate identifier. By
3382default, the base 'id' method will return an id taken from the
3383Perl package name, by stripping off any 'MT::App::' prefix, and lowercasing
3384the remaining string.
3385
3386=head2 MT->version_slug
3387
3388Returns a string of text that is appended to emails sent through the
3389C<build_email> method.
3390
3391=head2 $mt->publisher
3392
3393Returns the L<MT::WeblogPublisher> object that is used for managing the
3394MT publishing process. See L<MT::WeblogPublisher> for more information.
3395
3396=head2 $mt->rebuild
3397
3398An alias to L<MT::WeblogPublisher::rebuild>. See L<MT::WeblogPublisher>
3399for documentation of this method.
3400
3401=head2 $mt->rebuild_entry
3402
3403An alias to L<MT::WeblogPublisher::rebuild_entry>. See L<MT::WeblogPublisher>
3404for documentation of this method.
3405
3406=head2 $mt->rebuild_indexes
3407
3408An alias to L<MT::WeblogPublisher::rebuild_indexes>. See
3409L<MT::WeblogPublisher> for documentation of this method.
3410
3411=head2 $mt->rebuild_archives
3412
3413An alias to L<MT::WeblogPublisher::rebuild_archives>. See
3414L<MT::WeblogPublisher> for documentation of this method.
3415
3416=head2 $app->template_paths
3417
3418Returns an array of directory paths where application templates exist.
3419
3420=head2 $app->find_file(\@paths, $filename)
3421
3422Returns the path and filename for a file found in any of the given paths.
3423If the file cannot be found, it returns undef.
3424
3425=head2 $app->load_tmpl($file[, @params])
3426
3427Loads a L<MT::Template> template using the filename specified. See the
3428documentation for the C<build_page> method to learn about how templates
3429are located. The optional C<@params> are passed to the L<MT::Template>
3430constructor.
3431
3432=head2 $app->set_default_tmpl_params($tmpl)
3433
3434Assigns standard parameters to the given L<MT::Template> C<$tmpl> object.
3435Refer to the L<STANDARD APPLICATION TEMPLATE PARAMETERS> section for a
3436complete list of these parameters.
3437
3438=head2 $app->charset( [$charset] )
3439
3440Gets or sets the application's character set based on the "PublishCharset"
3441configuration setting or the encoding of the active language
3442(C<$app-E<gt>current_language>).
3443
3444=head2 $app->build_page($tmpl_name, \%param)
3445
3446Builds an application page to be sent to the client; the page name is specified
3447in C<$tmpl_name>, which should be the name of a template containing valid
3448L<MT::Template> markup. C<\%param> is a hash ref whose keys and values will
3449be passed to L<MT::Template::param> for use in the template.
3450
3451On success, returns a scalar containing the page to be sent to the client. On
3452failure, returns C<undef>, and the error message can be obtained from
3453C<$app-E<gt>errstr>.
3454
3455=head3 How does build_page find a template?
3456
3457The C<build_page> function looks in several places for an app
3458template. Two configuration directives can modify these search paths,
3459and application and plugin code can also affect them.
3460
3461The I<TemplatePath> config directive is an absolute path to the directory
3462where MT's core application templates live. It defaults to the I<mt_dir>
3463plus an additional path segment of 'tmpl'.
3464
3465The optional I<AltTemplatePath> config directive is a path (absolute
3466or relative) to a directory where some 'override templates' may
3467live. An override template takes the place of one of MT's core
3468application templates, and is used interchangeably with the core
3469template. This allows power users to customize the look and feel of
3470the MT application. If I<AltTemplatePath> is relative, its base path
3471is the value of the Movable Type configuration file.
3472
3473Next, any application built on the C<MT::App> foundation can define
3474its own I<template_dir> parameter, which identifies a subdirectory of
3475TemplatePath (or AltTemplatePath) where that application's templates
3476can be found. I<template_dir> defaults to C<cms>. Most templates will
3477be found in this directory, but sometimes the template search will
3478fall through to the parent directory, where a default error template
3479is found, for example. I<template_dir> should rightly have been named
3480I<application_template_dir>, since it is application-specific.
3481
3482Finally, a plugin can specify its I<plugin_template_path>, which
3483locates a directory where the templates for that plugin's own
3484interface are found. If the I<plugin_template_path> is relative, it
3485may be relative to either the I<app_dir>, or the I<mt_dir>; the former
3486takes precedence if it exists. (for a definition of I<app_dir> and
3487I<mt_dir>, see L<MT>)
3488
3489Given these values, the order of search is as follows:
3490
3491=over 4
3492
3493=item * I<plugin_template_path>
3494
3495=item * I<AltTemplatePath>
3496
3497=item * I<AltTemplatePath>F</>I<template_dir>
3498
3499=item * I<TemplatePath>/I<template_dir>
3500
3501=item * I<TemplatePath>
3502
3503=back
3504
3505If a template with the given name is not found in any of these
3506locations, an ugly error is thrown to the user.
3507
3508=head2 $app->build_page_in_mem($tmpl, \%param)
3509
3510Used internally by the L<build_page> method to render the output
3511of a L<MT::Template> object (the first parameter) using the parameter
3512data (the second parameter). It additionally calls the L<process_mt_template>
3513method (to process any E<lt>MT_ACTIONE<gt> and E<lt>MT_X:YE<gt> marker tags)
3514and then L<translate_templatized> (to process any E<lt>MT_TRANSE<gt> tags).
3515
3516=head2 $app->process_mt_template($str)
3517
3518Processes the E<lt>__action<gt> tags that are present in C<$str>. These tags
3519are in the following format:
3520
3521    <__action mode="mode_name" parameter="value">
3522
3523The mode parameter is required (and must be the first attribute). The
3524following attributes are appended as regular query parameters.
3525
3526The MT_ACTION tag is a preferred way to specify application links rather
3527than using this syntax:
3528
3529    <mt:var name="script_url">?__mode=mode_name&parameter=value
3530
3531C<process_mt_templates> also strips the C<$str> variable of any tags in
3532the format of C<E<lt>MT_\w+:\w+E<gt>>. These are 'marker' tags that are
3533used to identify specific portions of the template page and used in
3534conjunction with the transformer callback helper methods C<tmpl_prepend>,
3535C<tmpl_append>, C<tmpl_replace>, C<tmpl_select>.
3536
3537=head2 $mt->build_email($file, $param)
3538
3539Loads a template from the application's 'email' template directory and
3540processes it as a HTML::Template. The C<$param> argument is a hash reference
3541of parameter data for the template. The return value is the output of the
3542template.
3543
3544=head2 MT::get_next_sched_post_for_user($author_id, @blog_ids)
3545
3546This is an internal routine used by L<MT::XMLRPCServer> and the
3547getNextScheduled XMLRPC method to determine the timestamp for the next
3548entry that is scheduled for publishing. The return value is the timestamp
3549in UTC time in the format "YYYY-MM-DDTHH:MM:SSZ".
3550
3551=head2 $mt->commenter_authenticator($id)
3552
3553Returns a specific comment authenication option using the identifier
3554C<$id> parameter.
3555
3556=head2 $mt->commenter_authenticators()
3557
3558Returns the available comment authentication identifiers that are
3559installed in the MT registry.
3560
3561=head2 $mt->core_commenter_authenticators()
3562
3563A method that returns the MT-supplied comment authentication registry
3564data.
3565
3566=head2 $mt->init_commenter_authenticators()
3567
3568Initializes the list of installed MT comment authentication options,
3569drawing from the MT registry.
3570
3571=head2 $mt->captcha_provider($id)
3572
3573Returns a specific CAPTCHA provider configuration using the identifier
3574C<$id> parameter. This is a convenience method that accesses the CAPTCHA
3575providers installed into the MT registry.
3576
3577=head2 $mt->captcha_providers()
3578
3579Returns the available CAPTCHA providers. This is a convenience method
3580that accesses the MT registry for available CAPTCHA providers (it also
3581invokes the 'condition' key for each provider to filter the list).
3582
3583=head2 $mt->core_captcha_providers()
3584
3585A method that returns the MT-supplied CAPTCHA provider registry data.
3586
3587=head2 $mt->init_captcha_providers()
3588
3589Initializes the list of installed CAPTCHA providers, drawing from
3590the MT registry.
3591
3592=head2 $mt->effective_captcha_provider()
3593
3594Returns the Perl package name for the configured CAPTCHA provider.
3595
3596=head2 $app->static_path()
3597
3598Returns the application's static web path.
3599
3600=head2 $app->static_file_path()
3601
3602Returns the application's static file path.
3603
3604=head2 MT::core_upload_file_to_sync
3605
3606A MT callback handler routine that forwards to the L<upload_file_to_sync>
3607method.
3608
3609=head2 MT->upload_file_to_sync(%param)
3610
3611A routine that will make record of a file that is to be transmitted
3612to one or more servers (typically via rsync). This method runs when
3613the C<SyncTarget> MT configuration setting is configured. Normally
3614published files are automatically processed for syncing operations,
3615but this routine is used for files that are created through other
3616means, such as uploading an asset.
3617
3618=head2 MT->help_url( [ $suffix ] )
3619
3620Returns a help URL for the application. This method is used to construct
3621the URL directing users to online documentation. If called without any
3622parameters, it returns the base URL for providing help. If a parameter is
3623given, the URL is appended with the given subpath. The base URL by default
3624is 'http://www.movabletype.org/documentation/'. This string is passed
3625through MT's localization modules, so it can be changed on a per-language
3626basis. The C<$suffix> parameter, however, is always appended to this base URL.
3627
3628=head2 MT->get_timer
3629
3630Returns an instance of L<MT::Util::ReqTimer> for use in timing MT's
3631operations.
3632
3633=head2 MT->log_times
3634
3635Used as part of Movable Type's performance logging framework. This method
3636is called internally, once at the startup of Movable Type, and once as it
3637is shutting down.
3638
3639=head2 MT->time_this($string, $code)
3640
3641Utility method to time a particular routine. This will log the execution
3642time of the C<$code> coderef with the identifying phrase C<$string> using
3643MT's performance logging framework.
3644
3645=head2 MT::refresh_cache($cb)
3646
3647A callback handler that invalidates the cache of MT's caching driver.
3648See L<MT::Cache::Negotiate>.
3649
3650=head2 MT->register_refresh_cache_event($callback)
3651
3652Registers a callback that will cause the MT cache to invalidate itself.
3653See L<MT::Cache::Negotiate>.
3654
3655=head1 ERROR HANDLING
3656
3657On an error, all of the above methods return C<undef>, and the error message
3658can be obtained by calling the method I<errstr> on the class or the object
3659(depending on whether the method called was a class method or an instance
3660method).
3661
3662For example, called on a class name:
3663
3664    my $mt = MT->new or die MT->errstr;
3665
3666Or, called on an object:
3667
3668    $mt->rebuild(BlogID => $blog_id)
3669        or die $mt->errstr;
3670
3671=head1 DEBUGGING
3672
3673MT has a package variable C<$MT::DebugMode> which is assigned through
3674your MT configuration file (DebugMode setting). If this is set to
3675any non-zero value, MT applications will display any C<warn>'d
3676statements to a panel that is displayed within the app.
3677
3678The DebugMode is a bit-wise setting and offers the following options:
3679
3680    1 - Display debug messages
3681    2 - Display a stack trace for messages captured
3682    4 - Lists queries issued by Data::ObjectDriver
3683    8 - Reports on MT templates that take more than 1/4 second to build*
3684    128 - Outputs app-level request/response information to STDERR.
3685
3686These can be combined, so if you want to display queries and debug messages,
3687use a DebugMode of 5 for instance.
3688
3689You may also use the local statement to temporarily apply a particular bit,
3690if you want to scope the debug messages you receive to a block of code:
3691
3692    local $MT::DebugMode |= 4;  # show me the queries for the following
3693    my $obj = MT::Entry->load({....});
3694
3695*DebugMode bit 8 actually outputs it's messages to STDERR (which typically
3696is sent to your web server's error log).
3697
3698=head1 CALLBACKS
3699
3700Movable Type has a variety of hook points at which a plugin can attach
3701a callback.
3702
3703In each case, the first parameter is an L<MT::Callback> object which
3704can be used to pass error information back to the caller.
3705
3706The app-level callbacks related to rebuilding are documented
3707in L<MT::WeblogPublisher>. The specific apps document the callbacks
3708which they invoke.
3709
3710=head2 NewUserProvisioning($cb, $user)
3711
3712This callback is invoked when a user is being added to Movable Type.
3713Movable Type itself registers for this callback (with a priority of 5)
3714to provision the user with a new weblog if the system has been configured
3715to do so.
3716
3717=head2 post_init($cb, \%param)
3718
3719This callback is invoked when MT is initialized and ready to run.
3720This callback is invoked after MT initialized addons, plugins, schema
3721and permissions.  The arguments passed to initialize MT is passed
3722through to the callback.
3723
3724=head1 LICENSE
3725
3726The license that applies is the one you agreed to when downloading
3727Movable Type.
3728
3729=head1 AUTHOR & COPYRIGHT
3730
3731Except where otherwise noted, MT is Copyright 2001-2008 Six Apart.
3732All rights reserved.
3733
3734=cut
Note: See TracBrowser for help on using the browser.