root/trunk/PluginManager/plugins/PluginManager/lib/PluginManager/CMS.pm @ 1

Revision 1, 17.2 kB (checked in by breese, 4 years ago)

added PluginManager?

Line 
1package PluginManager::CMS;
2
3use strict;
4@PluginManager::CMS::ISA = qw( MT::App );
5
6use MT::App;
7use MT::App::CMS;
8use MT::ConfigMgr;
9use MT::PluginData;
10
11use PluginManager::Plugin;
12use PluginManager::Util qw(debug scrub_path);
13
14use XML::Simple;
15use LWP::Simple;
16use File::Copy::Recursive qw(pathrm dircopy);
17use Archive::Extract;
18use File::Copy;
19use File::Basename;
20use File::Spec qw(catfile);
21use File::Temp;
22use File::Path;
23use File::Find;
24
25sub init {
26    my $app = shift;
27    my %param = @_;
28    $app->SUPER::init(%param) or return;
29
30    $app->add_methods(
31            'init'        => \&initialize,
32            'install'     => \&install_plugin,
33            'uninstall'   => \&uninstall_plugin,
34            'upgrade'     => \&upgrade_plugin,
35            'list'        => \&list_plugins,
36            'check_ajax'  => \&check_ajax,
37    );
38
39    $app->{default_mode}   = 'list';
40    $app->{requires_login} = 1;
41    $app;
42}
43
44sub check_ajax {
45    my $app = shift;
46    my $q = $app->{query};
47
48    my $id = $q->param('id');
49
50    my $m;
51    $m = PluginManager::Plugin->load( { id => $id } );
52    if (!$m) {
53        return 'error';
54    }
55
56    my $record;
57    for my $sig (keys %MT::Plugins) {
58        if ($sig eq $m->sig) {
59            $record = $MT::Plugins{$sig}{object};
60            last;
61        }
62    }
63    my $plugin = {
64        key         => $m->id,
65        version     => $record->{version},
66        name        => $m->name,
67        icon        => $m->icon,
68        description => $record->{description},
69        url         => $m->url,
70        created     => $m->created_on,
71        updated     => $m->updated_on,
72    };
73
74
75    my $local_cfg      = XMLin($m->config);
76    my $local_version  = $plugin->{version};
77
78    my $version_url    = $local_cfg->{version_url};
79    my $remote_data    = get($version_url) or return;
80    my $remote_cfg     = XMLin($remote_data);
81    my $remote_version = $remote_cfg->{version};
82
83    debug($m->name.": ".$remote_version." > ".$local_version."?");
84    my $param = {
85        key               => $m->id,
86        upgrade_available => $remote_version > $local_version,
87    };
88    my $tmpl = $app->init_tmpl('check_ajax.tmpl');
89    for my $key (keys %$param) {
90        $tmpl->param($key, $param->{$key});
91    }
92    $app->l10n_filter($tmpl->output);
93}
94
95sub list_plugins {
96    my $app = shift;
97    my $q = $app->{query};
98
99#    my $data = MT::PluginData->load({ plugin => 'PluginManager',
100#                                     key    => 'static-path' });
101#    my $static_path = $data->data;
102
103    my (%constraints, %options);
104    $options{sort}        = 'name';
105    $options{direction}   = 'ascend';
106    my $iter = PluginManager::Plugin->load_iter( \%constraints, \%options );
107    my @plugins;
108    my $count = 0;
109    my $plugin_keys = '';
110    while (my $m = $iter->()) {
111        my $record;
112        for my $sig (keys %MT::Plugins) {
113            if ($sig eq $m->sig) {
114                $record = $MT::Plugins{$sig}{object};
115                last;
116            }
117        }
118        my $plugin = {
119            key         => $m->id,
120            version     => $record->{version},
121            name        => $m->name,
122            description => $record->{description},
123            url         => $m->url,
124            created     => $m->created_on,
125            updated     => $m->updated_on,
126            count       => $count++,
127            entry_odd   => $count % 2 == 0,
128        };
129        $plugin->{icon} = MT->instance->{cfg}->CGIPath . $m->icon
130            if $m->icon;
131
132        $plugin_keys .= ',' if ($plugin_keys ne '');
133        $plugin_keys .= $plugin->{key};
134
135        push @plugins,$plugin;
136    }
137   
138    my $param = {
139        url         => $q->param('url'),
140        tmpfile     => $q->param('tmpdir'),
141        tmpdir      => $q->param('tmpfile'),
142        plugin      => $q->param('plugin'),
143        plugin_keys => $plugin_keys,
144        plugin_loop => \@plugins,
145    };
146
147    my $tmpl = $app->init_tmpl('list.tmpl');
148    $app->add_breadcrumb("Main Menu",$app->{mtscript_url});
149    $app->add_breadcrumb("Plugin Manager");
150    $app->{breadcrumbs}[-1]{is_last} = 1;
151
152    $tmpl->param(breadcrumbs    => $app->{breadcrumbs});
153    $tmpl->param(blog_id        => $app->{query}->param('blog_id'));
154    $tmpl->param(plugin_version => $MT::Plugin::PluginManager::VERSION);
155    for my $key (keys %$param) {
156        $tmpl->param($key, $param->{$key});
157    }
158    $app->l10n_filter($tmpl->output);
159}
160
161sub extract_linkrel {
162    my ($url,$match) = @_;
163    my $content = get($url)
164        or die "Download failed ($url): $@";
165    my @links = ($content =~ /(<link [^\>]*>)/gmi);
166    foreach my $link (@links) {
167        my ($rel) = ($link =~ /rel=\"([^\"]*)\"/i);
168        my ($href) = ($link =~ /href=\"([^\"]*)\"/i);
169        if ($rel eq $match) {
170            return $href;
171        }
172    }
173}
174
175sub repair_class {
176    my $app = shift;
177    my ($class) = @_;
178    require MT::Upgrade;
179    my $driver = MT::Object->driver;
180    my $diff = MT::Upgrade->class_diff($class);
181    debug("diff=$diff");
182    if ($diff) {
183        my @stmt;
184        if ($diff->{fix}) {
185            @stmt = $driver->fix_class($class);
186        } else {
187            if ($diff->{add}) {
188                push @stmt, $driver->add_column($class, $_->{name})
189                    foreach @{$diff->{add}};
190            }
191            if ($diff->{alter}) {
192                push @stmt, $driver->alter_column($class, $_->{name})
193                    foreach @{$diff->{alter}};
194            }
195            if ($diff->{drop}) {
196                push @stmt, $driver->drop_column($class, $_->{name})
197                    foreach @{$diff->{drop}};
198            }
199        }
200        if (@stmt) {
201            $driver->sql(\@stmt) or return $app->error($driver->errstr);
202        }
203    }
204    return $diff;
205}
206
207sub initialize {
208    my $app = shift;
209    my $q = $app->{query};
210
211    require PluginManager::Plugin;
212
213    my $diff = $app->repair_class("PluginManager::Plugin");
214
215    my $data = MT::PluginData->new;
216    $data->plugin('PluginManager');
217    $data->key('static-path');
218    $data->data($q->param('static_path'));
219    $data->save or die $data->errstr;
220
221    my $filename = File::Spec->catfile(MT->instance->mt_dir,
222                                       'plugins','PluginManager','mtplugin.pkg');
223    open FILE,$filename or die "Could not open file: $!";
224    my $data = join('',<FILE>);
225    close FILE;
226    my $package = XMLin($data);
227
228    my $plugin = PluginManager::Plugin->load( { 
229        sig => 'PluginManager/PluginManager.pl' 
230    } );
231    if (!$plugin) {
232        $plugin = PluginManager::Plugin->new;
233    }
234    $plugin->name($package->{name});
235    $plugin->sig($package->{sig});
236    $plugin->icon($package->{icon}->{content});
237    $plugin->config($data);
238    $plugin->url($package->{version_url});
239
240    $plugin->save or
241        return $app->error("Error initializing Plugin Manager: " . $plugin->errstr);
242
243    $app->list_plugins();
244}
245
246sub uninstall_plugin {
247    my $app = shift;
248    my $q = $app->{query};
249
250    my $id = $q->param('id');
251    my $commit = $q->param('commit');
252
253    my $plugin;
254    $plugin = PluginManager::Plugin->load( { id => $id } );
255    if (!$plugin) {
256        return $app->list_plugins();
257    }
258
259    my $cfg;
260    eval {
261        $cfg = XMLin($plugin->config, 
262                     ForceArray => 1,
263                     ForceContent => 1);
264    };
265    if ($@) {
266        return $app->errtrans("Could not load plugin descriptor: $@");
267    }
268
269    my @preview;
270    eval {
271        my $files = $cfg->{files}->{file};
272        foreach my $file (@$files) {
273            my $destination = File::Spec->catfile(MT->instance->mt_dir,
274                                                  $file->{destination});
275            $destination = scrub_path($destination);
276            if (-f $destination) {
277                if ($commit) {
278                    unlink($destination);
279                    debug("Executing 'unlink $destination'");
280                } else {
281                    push @preview, { text => "Deleting file: $destination" };
282                }
283            } elsif (-d $destination) {
284            }
285        }
286
287        my @protected;
288        push @protected,MT->instance->mt_dir;
289        foreach (qw(plugins mt-static alt-tmpl docs extlib examples extras
290                    import lib php tools schemas search_templates)) {
291            push @protected,File::Spec->catfile(MT->instance->mt_dir,$_);
292        }
293
294        my $data = MT::PluginData->load({ plugin => 'PluginManager',
295                                          key    => 'static-path' });
296        my $static_path = $data->data;
297       
298        my $cmds = $cfg->{uninstall}->[0];
299        use Data::Dumper;
300        foreach my $cmd_name (keys %$cmds) {
301            my $tmp = $cmds->{$cmd_name};
302            foreach my $cmd (@$tmp) {
303                debug("$cmd_name = ".Dumper($cmd));;
304                if ($cmd_name eq 'rmdir' && 
305                    !in_array($cmd->{content},@protected)) {
306                    my $dir;
307                    if ($cmd->{static}) {
308                        $dir = File::Spec->catfile($static_path,
309                                                   $cmd->{content});
310                    } else {
311                        $dir = File::Spec->catfile(MT->instance->mt_dir,
312                                                   $cmd->{content});
313                    }
314                    if ($commit) {
315                        debug("Executing 'pathrm($dir)'");
316                        File::Copy::Recursive::pathrm($dir,1);
317                    } else {
318                        push @preview, { text => "Removing the directory $dir" }; 
319                    }
320                }
321            }
322        }
323    };
324    if ($@) {
325        return $app->errtrans("Could not clean up after plugin installation: $@");
326    }
327
328    if ($commit) {
329        $plugin->remove or
330            return $app->error("Error removing plugin: " . $plugin->errstr);
331        $app->redirect($app->{cfg}->CGIPath . "plugins/PluginManager/plugins.cgi?__mode=list");
332    } else {
333        my $param = {
334            plugin_id    => $plugin->id,
335            plugin_name  => $plugin->name,
336            preview_loop => \@preview,
337        };
338       
339        my $tmpl = $app->init_tmpl('preview.tmpl');
340        $app->add_breadcrumb("Main Menu",$app->{mtscript_url});
341        $app->add_breadcrumb("Plugin Manager");
342        $app->{breadcrumbs}[-1]{is_last} = 1;
343       
344        $tmpl->param(breadcrumbs    => $app->{breadcrumbs});
345        $tmpl->param(blog_id        => $app->{query}->param('blog_id'));
346        $tmpl->param(plugin_version => $MT::Plugin::PluginManager::VERSION);
347        for my $key (keys %$param) {
348            $tmpl->param($key, $param->{$key});
349        }
350        $app->l10n_filter($tmpl->output);
351    }
352}
353
354sub upgrade_plugin {
355    my $app = shift;
356    my $q = $app->{query};
357
358    my $id = $q->param('id');
359    my $plugin;
360    $plugin = PluginManager::Plugin->load( { id => $id } );
361    if (!$plugin) {
362        return $app->list_plugins();
363    }
364
365    my $cfg;
366    eval {
367        $cfg = XMLin($plugin->config);
368    };
369    if ($@) {
370        return $app->errtrans("Could not load plugin descriptor: $@");
371    }
372
373    my ($tmp_dir,$tmp_file) = $app->create_tmp_file();
374
375    my $url = $cfg->{version_url};
376    my $content = get($url);
377    my ($mti) = XMLin($content);
378    $url = $mti->{download_url};
379   
380    # get the data
381    debug("Fetching data.");
382    is_success(getstore($url, $tmp_file))
383        or return $app->errtrans("Download failed ($url): $@");
384
385    my $package = $app->handle_install($tmp_dir,$tmp_file, 1);
386
387    $plugin->name($package->{name});
388    $plugin->sig($package->{sig});
389    $plugin->icon($package->{icon}->{content});
390    $plugin->config($package->{raw_data});
391    $plugin->url($package->{version_url});
392    $plugin->save or
393        return $app->error("Error updating plugin: " . $plugin->errstr);
394
395    $q->param('blog_id'   => $app->{query}->param('blog_id'));
396    $q->param('url'       => $url);
397    $q->param('plugin'    => $package->{name});
398    $app->redirect($app->{cfg}->CGIPath . "plugins/PluginManager/plugins.cgi?__mode=list");
399
400    $app->list_plugins();
401}
402
403sub create_tmp_file {
404    my $app = shift;
405    my $tmp_dir = $app->config('TempDir');
406    my ($tmp_fh, $tmp_file);
407    $tmp_dir = File::Temp::tempdir( 'PluginManager/XXXX', 
408                                    DIR => $tmp_dir);
409    debug("Making path: $tmp_dir");
410    eval { 
411        File::Path::mkpath($tmp_dir);
412        chmod(0775,$tmp_dir);
413    };
414    if ($@) {
415        return $app->errtrans("Error creating temporary directory: $@.");
416    }
417    debug("Creating temp file.");
418    eval {
419        ($tmp_fh, $tmp_file) = File::Temp::tempfile("PluginXXXX", 
420                                                    DIR => $tmp_dir,
421                                                    SUFFIX => '.tar.gz',
422                                                    );
423    };
424    if ($@) {
425        return $app->errtrans(
426            "Error creating temporary file; please check your TempDir ".
427            "setting in mt.cfg (currently '[_1]') " .
428            "this location should be writable. ($@)",
429            ($tmp_dir ? $tmp_dir : '['.$app->translate('unassigned').']'));
430    }
431    return ($tmp_dir, $tmp_file);
432}
433
434sub handle_install {
435    my $app = shift;
436    my ($tmp_dir,$tmp_file,$force) = @_;
437    my $q = $app->{query};
438
439    my @files;
440    eval {
441        debug("Extracting archive found at: $tmp_file");
442        my $ae = Archive::Extract->new( archive => $tmp_file );
443        my $ok = $ae->extract( to => $tmp_dir );
444        debug("Extracted file to $tmp_dir.");
445    };
446    if ($@) {
447        return $app->errtrans("Could not unpack plugin. $@");
448    }
449
450    my $package;
451    my $data;
452    File::Find::find( 
453                      sub { 
454                          if (/^mtplugin.pkg$/) {
455                              my $file = File::Spec->catfile($tmp_dir,$_);
456                              debug("Found ".$File::Find::name);
457                              open FILE,$File::Find::name or die "Could not open file: $!";
458                              $data = join('',<FILE>);
459                              close FILE;
460                              my ($cfg) = XMLin($data);
461                              $package = $app->process_package($cfg,$tmp_dir,$force);
462                              $package->{raw_data} = $data;
463                              debug("Successfully installed plugin.");
464                          }
465                      },
466                      $tmp_dir);
467    if (!$package) {
468        return $app->errtrans("Error processing plugin descriptor.");
469    }
470    eval {
471        File::Copy::Recursive::pathrm($tmp_dir,1);
472    };
473    if ($@) {
474        return $app->errtrans("Could not clean up after plugin installation: $@");
475    }
476    return $package;
477}
478
479sub install_plugin {
480    my $app = shift;
481    my $q = $app->{query};
482
483    my $file = $q->param('uploaded_plugin');
484    my $url  = $q->param('url');
485
486    my ($tmp_dir,$tmp_file) = $app->create_tmp_file();
487
488    if ($url) {
489        debug("User specified a URL for installation: $url");
490        my $link = extract_linkrel($url,'mt-installer');
491        my $content = get($link);
492        my ($mti) = XMLin($content);
493        $url = $mti->{download_url};
494
495        # get the data
496        debug("Fetching data.");
497        is_success(getstore($url, $tmp_file))
498            or return $app->errtrans("Download failed ($url): $@");
499
500    } elsif ($file) {
501        debug("User uploaded a file: ".$q->param('uploaded_plugin'));
502        my $fh = $q->upload('uploaded_plugin');
503        open UPLOADFILE, ">$tmp_file";
504        binmode UPLOADFILE;
505        while ( <$fh> ){
506            print UPLOADFILE;
507        }
508        close UPLOADFILE;
509
510    } else {
511        debug("Fetching data.");
512        # no file specified
513    }
514
515    debug("Calling handle_install($tmp_dir,$tmp_file)");
516    my $package = $app->handle_install($tmp_dir,$tmp_file);
517
518#    use Data::Dumper;
519#    debug("package=".Dumper($package));
520
521    my $plugin;
522    $plugin = PluginManager::Plugin->new;
523    $plugin->name($package->{name});
524    $plugin->sig($package->{sig});
525    $plugin->icon($package->{icon}->{content});
526    $plugin->config($package->{raw_data});
527    $plugin->url($package->{version_url});
528    $plugin->save or
529        return $app->error("Error adding plugin: " . $plugin->errstr);
530
531    $q->param('blog_id'   => $app->{query}->param('blog_id'));
532    $q->param('url'       => $url);
533    $q->param('plugin'    => $package->{name});
534    $app->redirect($app->{cfg}->CGIPath . "plugins/PluginManager/plugins.cgi?__mode=list");
535}
536
537sub process_package {
538    my ($app,$cfg,$tmp_dir,$force) = @_;
539    my $plugin = PluginManager::Plugin->load( { 
540        sig => $cfg->{sig} 
541    } );
542
543#    use Data::Dumper;
544#    debug(Dumper($cfg));
545
546    if ($plugin && !$force) {
547        die "It appears this plugin has already been installed.";
548    }
549
550    my $data = MT::PluginData->load({ plugin => 'PluginManager',
551                                      key    => 'static-path' });
552    my $static_path = $data->data;
553
554    my $files = $cfg->{files}->{file};
555    foreach my $file (@$files) {
556        my $filename = File::Spec->catfile($tmp_dir,$file->{src});
557        debug("Processing $filename");
558        my $destination;
559        if ($file->{static}) {
560            $destination = File::Spec->catfile($static_path,
561                                               $file->{destination});
562        } else {
563            $destination = File::Spec->catfile(MT->instance->mt_dir,
564                                               $file->{destination});
565        }
566        $destination = scrub_path($destination);
567        if (-d $filename) {
568            debug("Processing directory: ".$file->{src});
569            File::Copy::Recursive::pathmk($destination) 
570                or die "Plugin Manager failed to create the directory tree for $destination: $!\n";
571            if (!(-w $destination)) {
572                debug("File is not writable: $destination");
573                die "The directory that Plugin Manager is trying to write to is not writable by the web server: $destination\n";
574            }
575            dircopy($filename,$destination) or die "Could not copy $filename into directory $destination: $!";
576            if ($file->{permissions}) {
577                chmod(oct($file->{permissions}),$destination);
578            }
579        } elsif (-f $filename) {
580            debug("Processing file: ".$file->{src});
581            my $destdir = dirname($destination);
582            File::Copy::Recursive::pathmk($destdir);
583            if (!(-w $destdir)) {
584                debug("Directory is not writable: $destdir");
585                die "The directory that Plugin Manager is trying to write to is not writable by the web server: $destdir";
586            }
587            debug("Copying $filename to $destination");
588            copy($filename,$destination) or die $!;
589            if ($file->{permissions}) {
590                chmod(oct($file->{permissions}),$destination);
591            }
592        }
593    }
594    return $cfg;
595}
596
597sub in_array {
598    my ($needle,@haystack) = @_;
599    foreach my $e (@haystack) {
600        if ($e eq $needle) {
601            return 1;
602        }
603    }
604    return 0;
605}
606
607sub init_tmpl {
608    my $app = shift;
609#    PluginManager::Util::debug("Initializing template file.","  >");
610#    PluginManager::Util::debug("Calling MT::App::load_tmpl(".join(", ",@_).")","    >");
611    my $tmpl = $app->load_tmpl(@_);
612    if (!$tmpl) {
613        my $err = $app->translate("Loading template '[_1]' ".
614                                  "failed: [_2]",
615                                  $_[0], $@);
616#       PluginManager::Util::debug($err,"    >");
617        return $app->error($err);
618    } else {
619#       PluginManager::Util::debug("Template file successfully loaded.","    >");
620    }
621
622    $tmpl->param(plugin_name       => "Plugin Manager");
623    $tmpl->param(plugin_version    => $MT::Plugin::PluginManager::VERSION);
624    $tmpl->param(plugin_author     => "Byrne Reese");
625    $tmpl->param(page_titles       => [ reverse @{ $app->{breadcrumbs} } ]);
626
627    return $tmpl;
628}
629
6301;
Note: See TracBrowser for help on using the browser.