diff --git a/lib/MT.pm b/lib/MT.pm index 0d2dce04a..bf5197773 100644 --- a/lib/MT.pm +++ b/lib/MT.pm @@ -567,15 +567,18 @@ sub run_tasks { } sub add_plugin { - my $class = shift; + my $class = shift; my ($plugin) = @_; + my $sig = $plugin_sig || $plugin->{sig}; + my $name = eval { $plugin->name } || $plugin->{name}; if ( ref $plugin eq 'HASH' ) { require MT::Plugin; $plugin = new MT::Plugin($plugin); } - $plugin->{name} ||= $plugin_sig; - $plugin->{plugin_sig} = $plugin_sig; + + $plugin->{name} ||= $name || $plugin_sig; + $plugin->{plugin_sig} = $plugin_sig; my $id = $plugin->id; unless ($plugin_envelope) { @@ -584,19 +587,21 @@ sub add_plugin { return; } $plugin->envelope($plugin_envelope); + Carp::confess( "You cannot register multiple plugin objects from a single script. $plugin_sig" ) if exists( $Plugins{$plugin_sig} ) && ( exists $Plugins{$plugin_sig}{object} ); - $Components{ lc $id } = $plugin if $id; + $Components{ lc $id } = $plugin if $id; $Plugins{$plugin_sig}{object} = $plugin; - $plugin->{full_path} = $plugin_full_path; + $plugin->{full_path} = $plugin_full_path; $plugin->path($plugin_full_path); - unless ( $plugin->{registry} && ( %{ $plugin->{registry} } ) ) { - $plugin->{registry} = $plugin_registry; - } + + $plugin->{registry} = $plugin_registry + unless eval { keys %{ $plugin->{registry} } }; + if ( $plugin->{registry} ) { if ( my $settings = $plugin->{registry}{config_settings} ) { $settings = $plugin->{registry}{config_settings} = $settings->() @@ -1418,7 +1423,6 @@ sub upload_file_to_sync { MT::TheSchwartz->insert($job); } ## end sub upload_file_to_sync -# use Data::Dumper; sub init_addons { my $mt = shift; my $cfg = $mt->config; @@ -1451,74 +1455,13 @@ sub _init_plugins_core { $timer = $mt->get_timer(); } - # TODO Refactor/abstract this load_plugin anonymous subroutine out of here - # For testing purposes and maintainability, we need to make it a point - # to continually make large methods like this smaller and more focused. - my $load_plugin = sub { - my ( $plugin, $sig ) = @_; - die "Bad plugin filename '$plugin'" - if ( $plugin !~ /^([-\\\/\@\:\w\.\s~]+)$/ ); - local $plugin_sig = $sig; - local $plugin_registry = {}; - $plugin = $1; - if ( - !$use_plugins - || ( exists $PluginSwitch->{$plugin_sig} - && !$PluginSwitch->{$plugin_sig} ) - ) - { - $Plugins{$plugin_sig}{full_path} = $plugin_full_path; - $Plugins{$plugin_sig}{enabled} = 0; - return 0; - } - return 0 if exists $Plugins{$plugin_sig}; - $Plugins{$plugin_sig}{full_path} = $plugin_full_path; - $timer->pause_partial if $timer; - eval "# line " - . __LINE__ . " " - . __FILE__ - . "\nrequire '" - . $plugin_full_path . "';"; - $timer->mark( "Loaded plugin " . $sig ) if $timer; - if ($@) { - $Plugins{$plugin_sig}{error} = $@; - - # Log the issue but do it in a post_init callback so - # that we won't prematurely initialize the schema before - # all plugins are finished loading - my $msg = $mt->translate( "Plugin error: [_1] [_2]", - $plugin, $Plugins{$plugin_sig}{error} ); - $mt->log( - { message => $msg, class => 'system', level => 'ERROR', } ); - return 0; - } - else { - if ( my $obj = $Plugins{$plugin_sig}{object} ) { - $obj->init_callbacks(); - } - else { - - # A plugin did not register itself, so - # create a dummy plugin object which will - # cause it to show up in the plugin listing - # by it's filename. - MT->add_plugin( {} ); - } - } - $Plugins{$plugin_sig}{enabled} = 1; - return 1; - }; # end load_plugin sub - - my @deprecated_perl_init = (); foreach my $plugin (@$plugins) { # TODO in Melody 1.1: do NOT load plugin, .pl is deprecated if ( $plugin->{file} =~ /\.pl$/ ) { push( @deprecated_perl_init, $plugin ); - $plugin_envelope = $plugin->{envelope}; - $plugin_full_path = $plugin->{path}; - $load_plugin->( $plugin->{path}, $plugin->{file} ); + $mt->load_perl_plugin( $plugin ); } else { my $pclass @@ -1592,6 +1535,93 @@ sub _init_plugins_core { 1; } ## end sub _init_plugins_core +# Documenting the silly variations of the exact same thing. These should +# simply be transformations lazily rendered and cached +# +# base - The absolute filesystem path to the plugin's top-level folder. +# Example: /var/www/cgi/mt/plugins/MyPlugin +# +# dir - The name of the plugin's top-level folder. +# Transformation: File::Basename::basename( $plugin->{base} ) +# Example: ConfigAssistant.pack +# +# envelope - For PluginPath's within the Melody application folder, this is +# the relative path from the Melody directory to the plugin's +# top-level folder. (WHAT ABOUT OUTSIDE OF MELODY DIR?) +# Transformation: ( $plugin->{envelope} = $plugin->{base} ) +# =~ s!$ENV{MT_HOME}/?!!; +# Examples: +# * addons/ConfigAssistant.pack +# * plugins/FieldDay +# +# sig - The path to the yaml/perl initialization file from the +# perspective of its plugin directory. Examples: +# Transformation: $plugin->{dir} + init_file +# * ConfigAssistant.pack/config.yaml +# * Log4MT.plugin/config.yaml +# * Log4MT.plugin/config.yaml +# * MT-OldPlugin/OldPlugin.pl +# +sub load_perl_plugin { + my $mt = shift; + my ( $plugin ) = @_; + die "Bad plugin filename '".$plugin->{file}."'" + if ( $plugin->{file} !~ /^([-\\\/\@\:\w\.\s~]+)$/ ); + my $timer = undef; # FIXME JAY Had to disable timer + my $sig = $plugin_sig = $plugin->{sig}; + $plugin_full_path + = File::Spec->catfile( $plugin->{path}, $plugin->{file} ); + # local $plugin_registry = {}; + # FIXME JAY Temporarily disabled this check, uses more fucking globals + # if ( + # !$use_plugins + # || ( exists $PluginSwitch->{$sig} + # && !$PluginSwitch->{$sig} ) + # ) + # { + # $Plugins{$sig}{full_path} = $plugin_full_path; + # $Plugins{$sig}{enabled} = 0; + # return 0; + # } + return 0 if exists $Plugins{$sig}; + $Plugins{$sig}{full_path} = $plugin_full_path; + $timer->pause_partial if $timer; + eval "# line " + . __LINE__ . " " + . __FILE__ + . "\nrequire '" + . $plugin_full_path . "';"; + $timer->mark( "Loaded plugin " . $plugin->{sig} ) if $timer; + if ($@) { + $Plugins{$sig}{error} = $@; + + # Log the issue but do it in a post_init callback so + # that we won't prematurely initialize the schema before + # all plugins are finished loading + my $msg = $mt->translate( "Plugin error: [_1] [_2]", + $plugin, $Plugins{$sig}{error} ); + $mt->log( + { message => $msg, class => 'system', level => 'ERROR', } ); + return 0; + } + else { + if ( my $obj = $Plugins{$sig}{object} ) { + $obj->init_callbacks(); + } + else { + + # A plugin did not register itself, so + # create a dummy plugin object which will + # cause it to show up in the plugin listing + # by it's filename. + MT->add_plugin( {} ); + } + } + $Plugins{$sig}{enabled} = 1; + return 1; +}; # end load_plugin sub + + ### ### FIXME Handle perl plugin deprecation properly ### @@ -1784,8 +1814,8 @@ sub scan_directory_for_addons { my $plugin_file = File::Spec->catfile( $plugin_full_path, $file ); if ( -f $plugin_file ) { + ## Plugin is a file, add it to list for processing my $sig = File::Spec->catfile( $plugin_dir, $file ); - # Plugin is a file, add it to list for processing push @{ $plugins{$type} }, { label => $label, # used only by packs id => lc($label), # used only by packs diff --git a/t/93-plugins.t b/t/93-plugins.t index 7ab9331f4..70306283c 100644 --- a/t/93-plugins.t +++ b/t/93-plugins.t @@ -4,7 +4,7 @@ use warnings; use lib 't/lib', 'lib', 'extlib', 'addons/Log4MT.plugin/extlib'; use Data::Dumper; use Test::Warn; -use Test::More tests => 17; +use Test::More tests => 11; use MT; use MT::Log::Log4perl qw( l4mtdump ); use Log::Log4perl qw( :resurrect ); ###l4p our $logger = MT::Log::Log4perl->new(); $logger->trace(); @@ -22,7 +22,6 @@ my %bundled_plugins = ( plugin_path => 'addons', # <----- DEFAULT enabled => 1, # <----- DEFAULT }, - 'Awesome/config.yaml' => { name => 'Oh Awesome' }, 'ThemeExport.plugin/config.yaml' => { name => 'Theme Exporter' }, 'ThemeManager.plugin/config.yaml' => { name => 'Theme Manager' }, 'DePoClean.plugin/config.yaml' => { name => 'DePoClean' }, @@ -45,36 +44,12 @@ my %bundled_plugins = ( name => 'Configuration Assistant', base_class => 'MT::Component', }, - 'Rebless/config.yaml' => { - name => 'Rebless Me', - base_class => 'Rebless::Plugin', - }, - 'stray.pl' => { - message => ': Stray, unloaded perl plugin', - not_loaded => 1, - }, - 'stray.yaml' => { - message => ': Stray, unloaded yaml plugin', - not_loaded => 1, - }, - 'subfoldered/subfoldered.pl' => { - name => 'Subfoldered plugin', - } ); -# Initialize plugins - Should emit warnings that we test for -warnings_like { - require MT::Test; - import MT::Test qw( :app :db ); - - $app = MT->instance(); - $app->init_plugins(); -} -[ - qr/stray.pl.+not loaded.+plugins.+without.+enclosing folder/, - qr/.+plugin \(subfoldered.pl\).+deprecated plugin file format/ -], -'Deprecation and compatibility break warnings'; +# Initialize app and plugins +use MT::Test qw( :app :db ); +$app = MT->instance(); +$app->init_plugins(); ###l4p $logger->debug("PLUGIN: $_") foreach keys %MT::Plugins; diff --git a/t/lib/MT/Test.pm b/t/lib/MT/Test.pm index c2f925ec4..ff6814845 100644 --- a/t/lib/MT/Test.pm +++ b/t/lib/MT/Test.pm @@ -17,6 +17,9 @@ use lib 't/lib', 'extlib', 'lib', '../lib', '../extlib'; use File::Spec; use File::Temp qw( tempfile ); use File::Basename; +use Data::Dumper; +use List::Util qw( first ); +use Carp qw( longmess croak ); use MT; MT->add_callback( 'post_init', 1, undef, \&add_plugin_test_libs ); @@ -122,6 +125,7 @@ sub init_app { } ) or die( MT->errstr ); { + no warnings 'once'; local $SIG{__WARN__} = sub { }; my $orig_login = \&MT::App::login; *MT::App::print = sub { @@ -174,12 +178,14 @@ sub init_time { sub init_testdb { my $pkg = shift; + no warnings 'once'; + # This is a bit of MT::Upgrade magic to prevent the full # instantiation of the MT schema. We force the classes list # to only contain the test 'Foo', 'Bar' classes and neuter # the require MT::Upgrade; - + # Add our test 'Foo' and 'Bar' classes to the list of # object classes to install. %MT::Upgrade::classes = ( foo => 'Foo', bar => 'Bar' ); @@ -214,6 +220,7 @@ if ( $ENV{PREFILLED_CACHE} ) { sub init_memcached { my $pkg = shift; + no warnings 'once'; eval { require MT::Memcached; }; if ($@) { die "Cannot fake MT::Memcached, as it's not available"; @@ -392,6 +399,107 @@ sub init_db { $pkg->init_newdb(@_) && $pkg->init_upgrade(@_); } +sub debug_handle { + return $_[0]->DEBUG ? sub { diag( @_ ) } # Diagnostic output function + : sub { }; # No-op function +} + +sub revert_component_init { + my $pkg = shift; + my $debug = $pkg->debug_handle( shift ); + my $mt = MT->instance; + + # MT package scalar variables we need to reset + my @global_scalars = qw( plugin_sig plugin_envelope + plugin_registry plugins_installed + $plugin_full_path ); + my $c_hash = \%MT::Components; # Aliased... + my $c_arry = \@MT::Components; # for... + my $p_hash = \%MT::Plugins; # brevity! + $debug->( 'INITIAL %MT::Components: ' . Dumper( $c_hash )); + + # We are reinitializing everything *BUT* the core component + # so we need to preserve it before destroying the rest. + # Die if it's not initialized because that's just wrong + my $core = delete $c_hash->{core} or die "No core component found!"; + + + $debug->( 'Undefining all MT package scalar vars ' + . 'related to component/plugin initialization' ); + no strict 'refs'; + undef ${"MT::$_"} and $debug->( "\t\$MT::$_" ) for @global_scalars; + + # %MT::addons Anyone??????Bueller?? Bueller?? + + { + # As it says in MT.pm: + # Reset the Text_filters hash in case it was preloaded by plugins + # by calling all_text_filters (Markdown in particular does this). + # Upon calling all_text_filters again, it will be properly loaded + # by querying the registry. + no warnings 'once'; + %MT::Text_filters = (); + } + + $debug->('Unloading plugins\' perl init scripts from %INC cache'); + # This forces both perl and MT to treat the file as if it's never been + # loaded previously which is necessary for making MT process the plugin + # as it does in its own init methods. + foreach my $pdata ( values %$p_hash ) { + my $path = first { defined($_) and m{\.pl}i } + $pdata->{object}{"full_path","path"}; + next unless $path; + delete $INC{ $path } and $debug->("\t$path"); + } + + # And finally: Re-initialize %MT::Components and @MT::Components + # with only the 'core' component and undef %MT::Plugins completely + $c_arry = [ $core ]; + $c_hash = { core => $core }; + $p_hash = { }; + + # Find and initialize all non-core components + #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# + my %path_params = ( + Config => $mt->{config_dir}, + Directory => $mt->{mt_dir} + ); + my $killme = sub { die "FAIL ".Carp::longmess() }; + #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# + eval { + $debug->('Re-initializing addons'); + $mt->init_addons() or $killme->(); + + $mt->init_config_from_db( \%path_params ) or $killme->(); + $mt->init_debug_mode; + + $debug->('Re-initializing plugins'); + $mt->init_plugins() or $killme->(); + + # Set the plugins_installed flag signalling that it's + # okay to initialize the schema and use the database. + no warnings 'once'; + $MT::plugins_installed = 1; + }; + die "Failed: $@".Carp::longmess() if $@; + $debug->('Plugins re-initialization complete'); +} + +{ + my $re_looped = 0; + sub mt_package_hashvars_dump { + my $pkg = shift; + my $re = $re_looped++ ? 're' : ''; + my $sep = '---'x25; + return join( "\n\n", + $sep, + 'Components ${re}initialized: ' . Dumper(\%MT::Components), + 'Plugins ${re}initialized: ' . Dumper(\%MT::Plugins), + $sep, + ); + } +} + sub progress { } sub error { @@ -1366,7 +1474,10 @@ sub _run_app { grep { defined $_ } values %ENV ) ); my $filename = basename($src); - $CGITempFile::TMPDIRECTORY = '/tmp'; + { + no warnings 'once'; + $CGITempFile::TMPDIRECTORY = '/tmp'; + } my $tmpfile = new CGITempFile($seqno) or die "CGITempFile: $!"; my $tmp = $tmpfile->as_string; my $cgi_fh = Fh->new( $filename, $tmp, 0 ) or die "FH? $!"; diff --git a/t/plugins/subfoldered/subfoldered.pl b/t/plugins/subfoldered/subfoldered.pl index d8c37dafd..c3c71362e 100644 --- a/t/plugins/subfoldered/subfoldered.pl +++ b/t/plugins/subfoldered/subfoldered.pl @@ -7,8 +7,10 @@ package MT::Plugin::Subfoldered; our $VERSION = '0.1'; my $plugin; -MT->add_plugin($plugin = __PACKAGE__->new({ - name => "Subfoldered", +MT->add_plugin( $plugin = __PACKAGE__->new({ + name => 'A Subfoldered Plugin', + id => 'Subfoldered', + key => 'Subfoldered', version => $VERSION, description => "Subfoldered legacy format plugin", author_name => "Whomever",