root/branches/release-39/lib/MT/CMS/Tools.pm @ 2498

Revision 2498, 62.3 kB (checked in by bsmith, 18 months ago)

bugzid:80009 - fixing label/field styling on Confirm Publishing Configuration

  • Property svn:keywords set to Id Revision
Line 
1package MT::CMS::Tools;
2
3use strict;
4use Symbol;
5
6use MT::I18N qw( encode_text wrap_text );
7use MT::Util qw( encode_url encode_html decode_html encode_js );
8
9sub system_check {
10    my $app = shift;
11
12    if ( my $blog_id = $app->param('blog_id') ) {
13        return $app->redirect(
14            $app->uri(
15                'mode' => 'view_log',
16                args => { blog_id => $blog_id }
17            )
18        );
19    }
20
21    my %param;
22    # licensed user count: someone who has logged in within 90 days 
23    my $sess_class = $app->model('session'); 
24    my $from = time - ( 60 * 60 * 24 * 90 + 60 * 60 * 24 ); 
25    $sess_class->remove(
26        { kind => 'UA', start => [ undef, $from ] }, 
27        { range => { start => 1 } }
28    ); 
29    $param{licensed_user_count} = $sess_class->count( { kind => 'UA' } );
30
31    my $author_class = $app->model('author');
32    $param{user_count} = $author_class->count(
33        { type => MT::Author::AUTHOR() } );
34
35    # commeters: users with only comment permission and MT::Author::COMMENTER
36    my $cmntrs = $author_class->count(
37        { type => MT::Author::COMMENTER() } );
38
39    my @perms = $app->model('permission')->load(
40      {
41        permissions => "%'comment'%", 
42        blog_id     => '0',
43      },
44      {
45        'like' => { 'permissions' => 1 },
46        'not'  => { 'blog_id'     => 1 },
47      }
48    );
49    @perms = grep { $_->permissions =~ m/'comment'/ } @perms;
50    $param{commenter_count} = scalar(@perms) + $cmntrs;
51    $param{screen_id} = "system-check";
52    $param{syscheck_html} = get_syscheck_content($app) || '';
53   
54    $app->load_tmpl( 'system_check.tmpl', \%param );
55}
56
57sub get_syscheck_content {
58    my $app = shift;
59
60    my $syscheck_url = $app->base . $app->mt_path . $app->config('CheckScript') .
61        '?view=tools&version=' . MT->version_id;
62    if ( $syscheck_url && $syscheck_url ne 'disable' ) {
63        my $SYSCHECKCACHE_TIMEOUT = 60 * 60 * 24;
64        my $sess_class        = $app->model('session');
65        my ($syscheck_object)     = ("");
66        my $retries           = 0;
67        $syscheck_object = $sess_class->load( { id => 'SC' } );
68        if ( $syscheck_object
69            && ( $syscheck_object->start() < ( time - $SYSCHECKCACHE_TIMEOUT ) ) )
70        {
71            $syscheck_object->remove;
72            $syscheck_object = undef;
73        }
74        return encode_text( $syscheck_object->data(), 'utf-8', undef )
75          if ($syscheck_object);
76
77        my $ua = $app->new_ua({ timeout => 20 });
78        return unless $ua;
79        $ua->max_size(undef) if $ua->can('max_size');
80
81        my $req = new HTTP::Request( GET => $syscheck_url );
82        my $resp = $ua->request($req);
83        return unless $resp->is_success();
84        my $result = $resp->content();
85        if ($result) {
86            require MT::Sanitize;
87
88            # allowed html
89            my $spec = '* style class id,ul,li,div,span,br,h2,h3,strong,code,blockquote,p';
90            $result = MT::Sanitize->sanitize( $result, $spec );
91            $syscheck_object = MT::Session->new();
92            $syscheck_object->set_values(
93                {
94                    id    => 'SC',
95                    kind  => 'SC',
96                    start => time(),
97                    data  => $result
98                }
99            );
100            $syscheck_object->save();
101            $result = encode_text( $result, 'utf-8', undef );
102        }
103        return $result;
104    }
105}
106
107sub start_recover {
108    my $app = shift;
109    $app->add_breadcrumb( $app->translate('Password Recovery') );
110    $app->load_tmpl('dialog/recover.tmpl');
111}
112
113sub recover_password {
114    my $app   = shift;
115    my $q     = $app->param;
116    my $name  = $q->param('name');
117    my $class = ref $app eq 'MT::App::Upgrader' ? 'MT::BasicAuthor' : $app->model('author');
118    eval "use $class;";
119    my @author = $class->load( { name => $name } );
120    my $author;
121    foreach (@author) {
122        next unless $_->password && ( $_->password ne '(none)' );
123        $author = $_;
124    }
125
126    my ( $rc, $res ) =
127      reset_password( $app, $author, $q->param('hint'), $name );
128
129    if ($rc) {
130        $app->add_breadcrumb( $app->translate('Password Recovery') );
131        $app->load_tmpl(
132            'dialog/recover.tmpl',
133            {
134                recovered => 1,
135                email     => $author->email
136            }
137        );
138    }
139    else {
140        $app->error($res);
141    }
142}
143
144sub do_list_action {
145    my $app = shift;
146    $app->validate_magic or return;
147
148    # plugin_action_selector should always (?) be in the query; use it?
149    my $action_name = $app->param('action_name');
150    my $type        = $app->param('_type');
151    my ($the_action) =
152      ( grep { $_->{key} eq $action_name } @{ $app->list_actions($type) } );
153    return $app->errtrans( "That action ([_1]) is apparently not implemented!",
154        $action_name )
155      unless $the_action;
156
157    unless ( ref( $the_action->{code} ) ) {
158        if ( my $plugin = $the_action->{plugin} ) {
159            $the_action->{code} =
160              $app->handler_to_coderef( $the_action->{handler}
161                  || $the_action->{code} );
162        }
163    }
164    $the_action->{code}->($app);
165}
166
167sub do_page_action {
168    my $app = shift;
169
170    # plugin_action_selector should always (?) be in the query; use it?
171    my $action_name = $app->param('action_name');
172    my $type        = $app->param('_type');
173    my ($the_action) =
174      ( grep { $_->{key} eq $action_name } @{ $app->page_actions($type) } );
175    return $app->errtrans( "That action ([_1]) is apparently not implemented!",
176        $action_name )
177      unless $the_action;
178
179    unless ( ref( $the_action->{code} ) ) {
180        if ( my $plugin = $the_action->{plugin} ) {
181            $the_action->{code} =
182              $app->handler_to_coderef( $the_action->{handler}
183                  || $the_action->{code} );
184        }
185    }
186    $the_action->{code}->($app);
187}
188
189sub cfg_system_general {
190    my $app = shift;
191    my %param;
192    if ( $app->param('blog_id') ) {
193        return $app->return_to_dashboard( redirect => 1 );
194    }
195
196    return $app->errtrans("Permission denied.")
197      unless $app->user->is_superuser();
198    my $cfg = $app->config;
199    $app->add_breadcrumb( $app->translate('General Settings') );
200    $param{nav_config}   = 1;
201    $param{nav_settings} = 1;
202    $param{languages} =
203      $app->languages_list( $app->config('DefaultUserLanguage') );
204    my $tag_delim = $app->config('DefaultUserTagDelimiter') || 'comma';
205    $param{"tag_delim_$tag_delim"} = 1;
206
207    ( my $tz = $app->config('DefaultTimezone') ) =~ s![-\.]!_!g;
208    $tz =~ s!_00$!!;
209    $param{ 'server_offset_' . $tz } = 1;
210
211    $param{default_site_root} = $app->config('DefaultSiteRoot');
212    $param{default_site_url}  = $app->config('DefaultSiteURL');
213    $param{personal_weblog_readonly} =
214      $app->config->is_readonly('NewUserAutoProvisioning');
215    $param{personal_weblog} = $app->config->NewUserAutoProvisioning ? 1 : 0;
216    if ( my $id = $param{new_user_template_blog_id} =
217        $app->config('NewUserTemplateBlogId') || '' )
218    {
219        my $blog = MT::Blog->load($id);
220        if ($blog) {
221            $param{new_user_template_blog_name} = $blog->name;
222        }
223        else {
224            $app->config( 'NewUserTemplateBlogId', undef, 1 );
225            $cfg->save_config();
226            delete $param{new_user_template_blog_id};
227        }
228    }
229    $param{system_email_address} = $cfg->EmailAddressMain;
230    $param{saved}                = $app->param('saved');
231    $param{error}                = $app->param('error');
232    $param{screen_class}         = "settings-screen system-general-settings";
233    $app->load_tmpl( 'cfg_system_general.tmpl', \%param );
234}
235
236sub save_cfg_system_general {
237    my $app = shift;
238    $app->validate_magic or return;
239    return $app->errtrans("Permission denied.")
240      unless $app->user->is_superuser();
241
242    my $cfg = $app->config;
243    $app->config( 'EmailAddressMain',
244        $app->param('system_email_address') || undef, 1 );
245
246    $cfg->save_config();
247
248    my $args = ();
249    $args->{saved} = 1;
250    $app->redirect(
251        $app->uri(
252            'mode' => 'cfg_system',
253            args   => $args
254        )
255    );
256}
257
258sub upgrade {
259    my $app = shift;
260
261    # check for an empty database... no author table would do it...
262    my $driver         = MT::Object->driver;
263    my $upgrade_script = $app->config('UpgradeScript');
264    my $user_class     = MT->model('author');
265    if ( !$driver || !$driver->table_exists($user_class) ) {
266        return $app->redirect( $app->path
267              . $upgrade_script
268              . $app->uri_params( mode => 'install' ) );
269    }
270
271    return $app->redirect( $app->path . $upgrade_script );
272}
273
274sub recover_profile_password {
275    my $app = shift;
276    $app->validate_magic or return;
277    return $app->errtrans("Permission denied.")
278      unless $app->user->is_superuser();
279
280    my $q = $app->param;
281
282    require MT::Auth;
283    require MT::Log;
284    if ( !MT::Auth->can_recover_password ) {
285        $app->log(
286            {
287                message => $app->translate(
288"Invalid password recovery attempt; can't recover password in this configuration"
289                ),
290                level    => MT::Log::SECURITY(),
291                class    => 'system',
292                category => 'recover_profile_password',
293            }
294        );
295        return $app->error("Can't recover password in this configuration");
296    }
297
298    my $author_id = $q->param('author_id');
299    my $author    = MT::Author->load($author_id);
300
301    return $app->error( $app->translate("Invalid author_id") )
302      if !$author || $author->type != MT::Author::AUTHOR() || !$author_id;
303
304    my ( $rc, $res ) =
305      reset_password( $app, $author, $author->hint, $author->name );
306
307    if ($rc) {
308        my $url = $app->uri(
309            'mode' => 'view',
310            args   => { _type => 'author', recovered => 1, id => $author_id }
311        );
312        $app->redirect($url);
313    }
314    else {
315        $app->error($res);
316    }
317}
318
319sub start_backup {
320    my $app     = shift;
321    my $user    = $app->user;
322    my $blog_id = $app->param('blog_id');
323    my $perms   = $app->permissions;
324
325    unless ( $user->is_superuser ) {
326        return $app->errtrans("Permission denied.")
327          unless defined($blog_id) && $perms->can_administer_blog;
328    }
329
330    my %param = ();
331    if ( defined($blog_id) ) {
332        $param{blog_id} = $blog_id;
333        $app->add_breadcrumb( $app->translate('Backup') );
334    }
335    else {
336        $app->add_breadcrumb( $app->translate('Backup & Restore') );
337    }
338    $param{system_overview_nav} = 1 unless $blog_id;
339    $param{nav_backup} = 1;
340    require MT::Util::Archive;
341    my @formats = MT::Util::Archive->available_formats();
342    $param{archive_formats} = \@formats;
343
344    my $limit = $app->config('CGIMaxUpload') || 2048;
345    $param{over_300}  = 1 if $limit >= 300 * 1024;
346    $param{over_500}  = 1 if $limit >= 500 * 1024;
347    $param{over_1024} = 1 if $limit >= 1024 * 1024;
348    $param{over_2048} = 1 if $limit >= 2048 * 1024;
349
350    my $tmp = $app->config('TempDir');
351    unless ( ( -d $tmp ) && ( -w $tmp ) ) {
352        $param{error} =
353          $app->translate(
354'Temporary directory needs to be writable for backup to work correctly.  Please check TempDir configuration directive.'
355          );
356    }
357    $app->load_tmpl( 'backup.tmpl', \%param );
358}
359
360sub start_restore {
361    my $app     = shift;
362    my $user    = $app->user;
363    my $blog_id = $app->param('blog_id');
364    my $perms   = $app->permissions;
365
366    unless ( $user->is_superuser ) {
367        return $app->errtrans("Permission denied.")
368          unless defined($blog_id) && $perms->can_administer_blog;
369    }
370
371    my %param = ();
372    if ( defined($blog_id) ) {
373        $param{blog_id} = $blog_id;
374        $app->add_breadcrumb( $app->translate('Backup') );
375    }
376    else {
377        $app->add_breadcrumb( $app->translate('Backup & Restore') );
378    }
379    $param{system_overview_nav} = 1 unless $blog_id;
380    $param{nav_backup} = 1;
381
382    eval "require XML::SAX";
383    $param{missing_sax} = 1 if $@;
384
385    my $tmp = $app->config('TempDir');
386    unless ( ( -d $tmp ) && ( -w $tmp ) ) {
387        $param{error} =
388          $app->translate(
389'Temporary directory needs to be writable for restore to work correctly.  Please check TempDir configuration directive.'
390          );
391    }
392    $app->load_tmpl( 'restore.tmpl', \%param );
393}
394
395sub backup {
396    my $app     = shift;
397    my $user    = $app->user;
398    my $q       = $app->param;
399    my $blog_id = $q->param('blog_id');
400    my $perms   = $app->permissions;
401    unless ( $user->is_superuser ) {
402        return $app->errtrans("Permission denied.")
403          unless defined($blog_id) && $perms->can_administer_blog;
404    }
405    $app->validate_magic() or return;
406
407    my $blog_ids = $q->param('backup_what');
408
409    my $size = $q->param('size_limit') || 0;
410    return $app->errtrans( '[_1] is not a number.',
411        encode_html($size) )
412      if $size !~ /^\d+$/;
413
414    my @blog_ids = split ',', $blog_ids;
415
416    my $archive = $q->param('backup_archive_format');
417    my $enc     = $app->charset || 'utf-8';
418    my @ts      = gmtime(time);
419    my $ts = sprintf "%04d-%02d-%02d-%02d-%02d-%02d", $ts[5] + 1900, $ts[4] + 1,
420      @ts[ 3, 2, 1, 0 ];
421    my $file = "Movable_Type-$ts" . '-Backup';
422
423    my $param = { return_args => '__mode=start_backup' };
424    $app->{no_print_body} = 1;
425    $app->add_breadcrumb(
426        $app->translate('Backup & Restore'),
427        $app->uri( mode => 'start_backup' )
428    );
429    $app->add_breadcrumb( $app->translate('Backup') );
430    $param->{system_overview_nav} = 1 if defined($blog_ids) && $blog_ids;
431    $param->{blog_id} = $blog_id if $blog_id;
432    $param->{blog_ids} = $blog_ids if $blog_ids;
433    $param->{nav_backup} = 1;
434
435    local $| = 1;
436    $app->send_http_header('text/html');
437    $app->print( $app->build_page( 'include/backup_start.tmpl', $param ) );
438    require File::Temp;
439    require File::Spec;
440    use File::Copy;
441    my $temp_dir = $app->config('TempDir');
442
443    require MT::BackupRestore;
444    my $count_term =
445      $blog_id
446      ? { class => '*', blog_id => $blog_id }
447      : { class => '*' };
448    my $num_assets = $app->model('asset')->count($count_term);
449    my $printer;
450    my $splitter;
451    my $finisher;
452    my $progress = sub { _progress($app, @_); };
453    my $fh;
454    my $fname;
455    my $arc_buf;
456
457    if ( !( $size || $num_assets ) ) {
458        $splitter = sub { };
459
460        if ( '0' eq $archive ) {
461            ( $fh, my $filepath ) =
462              File::Temp::tempfile( 'xml.XXXXXXXX', DIR => $temp_dir );
463            ( my $vol, my $dir, $fname ) = File::Spec->splitpath($filepath);
464            $printer =
465              sub { my ($data) = @_; print $fh $data; return length($data); };
466            $finisher = sub {
467                my ($asset_files) = @_;
468                close $fh;
469                _backup_finisher( $app, $fname, $param );
470            };
471        }
472        else {  # archive/compress files
473            $printer =
474              sub { my ($data) = @_; $arc_buf .= $data; return length($data); };
475            $finisher = sub {
476                require MT::Util::Archive;
477                my ($asset_files) = @_;
478                ( my $fh, my $filepath ) =
479                  File::Temp::tempfile( $archive . '.XXXXXXXX', DIR => $temp_dir );
480                ( my $vol, my $dir, $fname ) = File::Spec->splitpath($filepath);
481                close $fh;
482                unlink $filepath;
483                my $arc = MT::Util::Archive->new($archive, $filepath);
484                $arc->add_string( $arc_buf, "$file.xml" );
485                $arc->add_string(
486                        "<manifest xmlns='"
487                      . MT::BackupRestore::NS_MOVABLETYPE()
488                      . "'><file type='backup' name='$file.xml' /></manifest>",
489                      "$file.manifest");
490                $arc->close;
491                _backup_finisher( $app, $fname, $param );
492            };
493        }
494    }
495    else {
496        my @files;
497        my $filename = File::Spec->catfile( $temp_dir, $file . "-1.xml" );
498        $fh = gensym();
499        open $fh, ">$filename";
500        my $url =
501            $app->uri
502          . "?__mode=backup_download&name=$file-1.xml&magic_token="
503          . $app->current_magic;
504        $url .= "&blog_id=$blog_id" if defined($blog_id);
505        push @files,
506          {
507            url      => $url,
508            filename => $file . "-1.xml"
509          };
510        $printer =
511          sub { my ($data) = @_; print $fh $data; return length($data); };
512        $splitter = sub {
513            my ($findex) = @_;
514            print $fh '</movabletype>';
515            close $fh;
516            my $filename =
517              File::Spec->catfile( $temp_dir, $file . "-$findex.xml" );
518            $fh = gensym();
519            open $fh, ">$filename";
520            my $url =
521                $app->uri
522              . "?__mode=backup_download&name=$file-$findex.xml&magic_token="
523              . $app->current_magic;
524            $url .= "&blog_id=$blog_id" if defined($blog_id);
525            push @files,
526              {
527                url      => $url,
528                filename => $file . "-$findex.xml"
529              };
530            my $header .=
531              "<movabletype xmlns='"
532              . MT::BackupRestore::NS_MOVABLETYPE() . "'>\n";
533            $header = "<?xml version='1.0' encoding='$enc'?>\n$header"
534              if $enc !~ m/utf-?8/i;
535            print $fh $header;
536        };
537        $finisher = sub {
538            my ($asset_files) = @_;
539            close $fh;
540            my $filename = File::Spec->catfile( $temp_dir, "$file.manifest" );
541            $fh = gensym();
542            open $fh, ">$filename";
543            print $fh "<manifest xmlns='"
544              . MT::BackupRestore::NS_MOVABLETYPE() . "'>\n";
545            for my $file (@files) {
546                my $name = $file->{filename};
547                print $fh "<file type='backup' name='$name' />\n";
548            }
549            for my $id ( keys %$asset_files ) {
550                my $name = $id . '-' . $asset_files->{$id}->[2];
551                my $tmp = File::Spec->catfile( $temp_dir, $name );
552                unless ( copy( $asset_files->{$id}->[1], $tmp ) ) {
553                    $app->log(
554                        {
555                            message => $app->translate(
556                                'Copying file [_1] to [_2] failed: [_3]',
557                                $asset_files->{$id}->[1],
558                                $tmp, $!
559                            ),
560                            level    => MT::Log::INFO(),
561                            class    => 'system',
562                            category => 'backup'
563                        }
564                    );
565                    next;
566                }
567                print $fh "<file type='asset' name='"
568                  . $asset_files->{$id}->[2]
569                  . "' asset_id='"
570                  . $id
571                  . "' />\n";
572                my $url =
573                    $app->uri
574                  . "?__mode=backup_download&assetname=$name&magic_token="
575                  . $app->current_magic;
576                $url .= "&blog_id=$blog_id" if defined($blog_id);
577                push @files,
578                  {
579                    url      => $url,
580                    filename => $name,
581                  };
582            }
583            print $fh "</manifest>\n";
584            close $fh;
585            my $url =
586                $app->uri
587              . "?__mode=backup_download&name=$file.manifest&magic_token="
588              . $app->current_magic;
589            $url .= "&blog_id=$blog_id" if defined($blog_id);
590            push @files,
591              {
592                url      => $url,
593                filename => "$file.manifest"
594              };
595            if ( '0' eq $archive ) {
596                $param->{files_loop} = \@files;
597                $param->{tempdir}    = $temp_dir;
598                my @fnames = map { $_->{filename} } @files;
599                _backup_finisher( $app, \@fnames, $param );
600            }
601            else {
602                my ( $fh_arc, $filepath ) =
603                  File::Temp::tempfile( $archive . '.XXXXXXXX', DIR => $temp_dir );
604                ( my $vol, my $dir, $fname ) = File::Spec->splitpath($filepath);
605                require MT::Util::Archive;
606                close $fh_arc;
607                unlink $filepath;
608                my $arc = MT::Util::Archive->new($archive, $filepath);
609                for my $f (@files) {
610                    $arc->add_file( $temp_dir, $f->{filename} );
611                }
612                $arc->close;
613                # for safery, don't unlink before closing $arc here.
614                for my $f (@files) {
615                    unlink File::Spec->catfile( $temp_dir, $f->{filename} );
616                }
617                _backup_finisher( $app, $fname, $param );
618            }
619        };
620    }
621
622    my @tsnow    = gmtime(time);
623    my $metadata = {
624        backup_by => $app->user->name . '(ID: ' . $app->user->id . ')',
625        backup_on => sprintf(
626            "%04d-%02d-%02dT%02d:%02d:%02d",
627            $tsnow[5] + 1900,
628            $tsnow[4] + 1,
629            @tsnow[ 3, 2, 1, 0 ]
630        ),
631        backup_what    => join( ',', @blog_ids ),
632        schema_version => $app->config('SchemaVersion'),
633    };
634    MT::BackupRestore->backup( \@blog_ids, $printer, $splitter, $finisher,
635        $progress, $size * 1024,
636        $enc, $metadata );
637}
638
639sub backup_download {
640    my $app     = shift;
641    my $user    = $app->user;
642    my $blog_id = $app->param('blog_id');
643    unless ( $user->is_superuser ) {
644        my $perms = $app->permissions;
645        return $app->errtrans("Permission denied.")
646          unless defined($blog_id) && $perms->can_administer_blog;
647    }
648    $app->validate_magic() or return;
649    my $filename  = $app->param('filename');
650    my $assetname = $app->param('assetname');
651    my $temp_dir  = $app->config('TempDir');
652    my $newfilename;
653    if ( defined($assetname) ) {
654        my $sess = MT::Session->load( { kind => 'BU', name => $assetname } );
655        if ( !defined($sess) || !$sess ) {
656            return $app->errtrans("Specified file was not found.");
657        }
658        $newfilename = $filename = $assetname;
659        $sess->remove;
660    }
661    elsif ( defined($filename) ) {
662        my $sess = MT::Session->load( { kind => 'BU', name => $filename } );
663        if ( !defined($sess) || !$sess ) {
664            return $app->errtrans("Specified file was not found.");
665        }
666        my @ts = gmtime( $sess->start );
667        my $ts = sprintf "%04d-%02d-%02d-%02d-%02d-%02d", $ts[5] + 1900,
668          $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
669        $newfilename = "Movable_Type-$ts" . '-Backup';
670        $sess->remove;
671    }
672    else {
673        $newfilename = $app->param('name');
674        return
675          if $newfilename !~
676/Movable_Type-\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}-Backup(?:-\d+)?\.\w+/;
677        $filename = $newfilename;
678    }
679
680    require File::Spec;
681    my $fname = File::Spec->catfile( $temp_dir, $filename );
682
683    my $contenttype;
684    if ( !defined($assetname) && ( $filename =~ m/^xml\..+$/i ) ) {
685        my $enc = $app->charset || 'utf-8';
686        $contenttype = "text/xml; charset=$enc";
687        $newfilename .= '.xml';
688    }
689    elsif ( $filename =~ m/^tgz\..+$/i ) {
690        $contenttype = 'application/x-tar-gz';
691        $newfilename .= '.tar.gz';
692    }
693    elsif ( $filename =~ m/^zip\..+$/i ) {
694        $contenttype = 'application/zip';
695        $newfilename .= '.zip';
696    }
697    else {
698        $contenttype = 'application/octet-stream';
699    }
700
701    if ( open( my $fh, "<", $fname ) ) {
702        binmode $fh;
703        $app->{no_print_body} = 1;
704        $app->set_header(
705            "Content-Disposition" => "attachment; filename=$newfilename" );
706        $app->send_http_header($contenttype);
707        my $data;
708        while ( read $fh, my ($chunk), 8192 ) {
709            $data .= $chunk;
710        }
711        close $fh;
712        $app->print($data);
713        $app->log(
714            {
715                message => $app->translate(
716                    '[_1] successfully downloaded backup file ([_2])',
717                    $app->user->name, $fname
718                ),
719                level    => MT::Log::INFO(),
720                class    => 'system',
721                category => 'restore'
722            }
723        );
724        unlink $fname;
725    }
726    else {
727        $app->errtrans('Specified file was not found.');
728    }
729}
730
731sub restore {
732    my $app  = shift;
733    my $user = $app->user;
734    return $app->errtrans("Permission denied.") if !$user->is_superuser;
735    $app->validate_magic() or return;
736
737    my $q = $app->param;
738
739    my ($fh) = $app->upload_info('file');
740    my $uploaded = $q->param('file');
741    my ( $volume, $directories, $uploaded_filename ) =
742      File::Spec->splitpath($uploaded)
743      if defined($uploaded);
744    if ( defined($uploaded_filename)
745        && ( $uploaded_filename =~ /^.+\.manifest$/i ) )
746    {
747        return restore_upload_manifest( $app, $fh );
748    }
749
750    my $param = { return_args => '__mode=dashboard' };
751
752    $app->add_breadcrumb(
753        $app->translate('Backup & Restore'),
754        $app->uri( mode => 'start_restore' )
755    );
756    $app->add_breadcrumb( $app->translate('Restore') );
757    $param->{system_overview_nav} = 1;
758    $param->{nav_backup}          = 1;
759
760    $app->{no_print_body} = 1;
761
762    local $| = 1;
763    $app->send_http_header('text/html');
764
765    $app->print( $app->build_page( 'restore_start.tmpl', $param ) );
766
767    require File::Path;
768
769    my $error = '';
770    my $result;
771    if (!$fh) {
772        $param->{restore_upload} = 0;
773        my $dir = $app->config('ImportPath');
774        my ( $blog_ids, $asset_ids ) = restore_directory( $app, $dir, \$error );
775        if ( defined $blog_ids ) {
776            $param->{open_dialog} = 1;
777            $param->{blog_ids}    = join( ',', @$blog_ids );
778            $param->{asset_ids}   = join( ',', @$asset_ids )
779              if defined $asset_ids;
780            $param->{tmp_dir} = $dir;
781        }
782        elsif ( defined $asset_ids ) {
783            my %asset_ids = @$asset_ids;
784            my %error_assets;
785            _restore_non_blog_asset( $app, $dir, $asset_ids, \%error_assets );
786            if (%error_assets) {
787                my $data;
788                while ( my ( $key, $value ) = each %error_assets ) {
789                    $data .=
790                      $app->translate( 'MT::Asset#[_1]: ', $key ) . $value . "\n";
791                }
792                my $message = $app->translate(
793                    'Some of the actual files for assets could not be restored.');
794                $app->log(
795                    {
796                        message  => $message,
797                        level    => MT::Log::WARNING(),
798                        class    => 'system',
799                        category => 'restore',
800                        metadata => $data,
801                    }
802                );
803                $error .= $message;
804            }
805        }
806    }
807    else {
808        $param->{restore_upload} = 1;
809        if ( $uploaded_filename =~ /^.+\.xml$/i ) {
810            my $blog_ids = restore_file( $app, $fh, \$error );
811            if ( defined $blog_ids ) {
812                $param->{open_dialog} = 1;
813                $param->{blog_ids} = join( ',', @$blog_ids );
814            }
815        }
816        else {
817            require MT::Util::Archive;
818            my $arc;
819            my $error;
820            if ( $uploaded_filename =~ /^.+\.tar(\.gz)?$/i ) {
821                $arc = MT::Util::Archive->new('tgz', $fh);
822            }
823            elsif ( $uploaded_filename =~ /^.+\.zip$/i ) {
824                $arc = MT::Util::Archive->new('zip', $fh);
825            }
826            else {
827                $error =
828                  $app->translate(
829                    'Please use xml, tar.gz, zip, or manifest as a file extension.'
830                  );
831            }
832            unless ($arc) {
833                $result = 0;
834                $param->{restore_success} = 0;
835                if ($error) {
836                    $param->{error}           = $error;
837                }
838                else {
839                    $error = MT->translate('Unknown file format');
840                    $app->log(
841                        {
842                            message  => $error . ":$uploaded_filename",
843                            level    => MT::Log::WARNING(),
844                            class    => 'system',
845                            category => 'restore',
846                            metadata => MT::Util::Archive->errstr,
847                        }
848                    );
849                }
850                $app->print( $error );
851                $app->print(
852                    $app->build_page( "restore_end.tmpl", $param ) );
853                close $fh if $fh;
854                return 1;
855            }
856            my $temp_dir = $app->config('TempDir');
857            require File::Temp;
858            my $tmp = File::Temp::tempdir( $uploaded_filename . 'XXXX',
859                DIR => $temp_dir );
860            $arc->extract($tmp);
861            $arc->close;
862            my ( $blog_ids, $asset_ids ) =
863              restore_directory( $app, $tmp, \$error );
864            if (defined $blog_ids) {
865                $param->{open_dialog} = 1;
866                $param->{blog_ids} = join( ',', @$blog_ids )
867                  if defined $blog_ids;
868                $param->{asset_ids} = join( ',', @$asset_ids )
869                  if defined $asset_ids;
870                $param->{tmp_dir} = $tmp;
871            }
872            elsif ( defined $asset_ids ) {
873                my %asset_ids = @$asset_ids;
874                my %error_assets;
875                _restore_non_blog_asset( $app, $tmp, \%asset_ids, \%error_assets );
876                if (%error_assets) {
877                    my $data;
878                    while ( my ( $key, $value ) = each %error_assets ) {
879                        $data .=
880                          $app->translate( 'MT::Asset#[_1]: ', $key ) . $value . "\n";
881                    }
882                    my $message = $app->translate(
883                        'Some of the actual files for assets could not be restored.');
884                    $app->log(
885                        {
886                            message  => $message,
887                            level    => MT::Log::WARNING(),
888                            class    => 'system',
889                            category => 'restore',
890                            metadata => $data,
891                        }
892                    );
893                    $error .= $message;
894                }
895            }
896        }
897    }
898    $param->{restore_success} = !$error;
899    $param->{error} = $error if $error;
900    if ( ( exists $param->{open_dialog} ) && ( $param->{open_dialog} ) ) {
901        $param->{dialog_mode} = 'dialog_adjust_sitepath';
902        $param->{dialog_params} =
903            'magic_token='
904          . $app->current_magic
905          . '&amp;blog_ids='
906          . $param->{blog_ids}
907          . '&amp;asset_ids='
908          . $param->{asset_ids}
909          . '&amp;tmp_dir='
910          . encode_url( $param->{tmp_dir} );
911        if ( ( $param->{restore_upload} ) && ( $param->{restore_upload} ) ) {
912            $param->{dialog_params} .= '&amp;restore_upload=1';
913        }
914        if ( ( $param->{error} ) && ( $param->{error} ) ) {
915            $param->{dialog_params} .=
916              '&amp;error=' . encode_url( $param->{error} );
917        }
918    }
919
920    $app->print( $app->build_page( "restore_end.tmpl", $param ) );
921    close $fh if $fh;
922    1;
923}
924
925sub restore_premature_cancel {
926    my $app  = shift;
927    my $user = $app->user;
928    return $app->errtrans("Permission denied.") if !$user->is_superuser;
929    $app->validate_magic() or return;
930
931    require JSON;
932    my $deferred = JSON::jsonToObj( $app->param('deferred_json') )
933      if $app->param('deferred_json');
934    my $param = { restore_success => 1 };
935    if ( defined $deferred && ( scalar( keys %$deferred ) ) ) {
936        _log_dirty_restore( $app, $deferred );
937        my $log_url = $app->uri( mode => 'view_log', args => {} );
938        $param->{restore_success} = 0;
939        my $message =
940          $app->translate(
941'Some objects were not restored because their parent objects were not restored.'
942          );
943        $param->{error} = $message . '  '
944          . $app->translate(
945"Detailed information is in the <a href='javascript:void(0)' onclick='closeDialog(\"[_1]\")'>activity log</a>.",
946            $log_url
947          );
948    }
949    else {
950        $app->log(
951            {
952                message => $app->translate(
953'[_1] has canceled the multiple files restore operation prematurely.',
954                    $app->user->name
955                ),
956                level    => MT::Log::WARNING(),
957                class    => 'system',
958                category => 'restore',
959            }
960        );
961    }
962    $app->redirect( $app->uri( mode => 'view_log', args => {} ) );
963}
964
965sub _restore_non_blog_asset {
966    my ( $app, $tmp_dir, $asset_ids, $error_assets ) = @_;
967    require MT::FileMgr;
968    my $fmgr = MT::FileMgr->new('Local');
969    foreach my $new_id ( keys %$asset_ids ) {
970        my $asset = $app->model('asset')->load($new_id);
971        next unless $asset;
972        my $old_id = $asset_ids->{$new_id};
973        my $filename = $old_id . '-' . $asset->file_name;
974        my $file    = File::Spec->catfile( $tmp_dir, $filename );
975        MT::BackupRestore->restore_asset( $file, $asset, $old_id, $fmgr,
976            $error_assets, sub { $app->print(@_); } );
977    }
978}
979
980sub adjust_sitepath {
981    my $app  = shift;
982    my $user = $app->user;
983    return $app->errtrans("Permission denied.") if !$user->is_superuser;
984    $app->validate_magic() or return;
985
986    require MT::BackupRestore;
987
988    my $q         = $app->param;
989    my $tmp_dir   = $q->param('tmp_dir');
990    my $error     = $q->param('error') || q();
991    my %asset_ids = split ',', $q->param('asset_ids');
992
993    $app->{no_print_body} = 1;
994
995    local $| = 1;
996    $app->send_http_header('text/html');
997
998    $app->print( $app->build_page( 'dialog/restore_start.tmpl', {} ) );
999
1000    my $asset_class = $app->model('asset');
1001    my %error_assets;
1002    my %blogs_meta;
1003    my @p = $q->param;
1004    foreach my $p (@p) {
1005        next unless $p =~ /^site_path_(\d+)/;
1006        my $id            = $1;
1007        my $blog          = $app->model('blog')->load($id)
1008            or return $app->error($app->translate('Can\'t load blog #[_1].', $id));
1009        my $old_site_path = scalar $q->param("old_site_path_$id");
1010        my $old_site_url  = scalar $q->param("old_site_url_$id");
1011        my $site_path     = scalar $q->param("site_path_$id") || q();
1012        my $site_url      = scalar $q->param("site_url_$id") || q();
1013        $blog->site_path($site_path);
1014        $blog->site_url($site_url);
1015
1016        if ( $site_url || $site_path ) {
1017            $app->print(
1018                $app->translate(
1019                    "Changing Site Path for the blog '[_1]' (ID:[_2])...",
1020                    $blog->name, $blog->id
1021                )
1022            );
1023        }
1024        else {
1025            $app->print(
1026                $app->translate(
1027                    "Removing Site Path for the blog '[_1]' (ID:[_2])...",
1028                    $blog->name, $blog->id
1029                )
1030            );
1031        }
1032        my $old_archive_path = scalar $q->param("old_archive_path_$id");
1033        my $old_archive_url  = scalar $q->param("old_archive_url_$id");
1034        my $archive_path     = scalar $q->param("archive_path_$id") || q();
1035        my $archive_url      = scalar $q->param("archive_url_$id") || q();
1036        $blog->archive_path($archive_path);
1037        $blog->archive_url($archive_url);
1038        if (   ( $old_archive_url && $archive_url )
1039            || ( $old_archive_path && $archive_path ) )
1040        {
1041            $app->print(
1042                "\n"
1043                  . $app->translate(
1044                    "Changing Archive Path for the blog '[_1]' (ID:[_2])...",
1045                    $blog->name, $blog->id
1046                  )
1047            );
1048        }
1049        elsif ( $old_archive_url || $old_archive_path ) {
1050            $app->print(
1051                "\n"
1052                  . $app->translate(
1053                    "Removing Archive Path for the blog '[_1]' (ID:[_2])...",
1054                    $blog->name, $blog->id
1055                  )
1056            );
1057        }
1058        $blog->save or $app->print( $app->translate("failed") . "\n" ), next;
1059        $app->print( $app->translate("ok") . "\n" );
1060
1061        $blogs_meta{$id} = {
1062            'old_archive_path' => $old_archive_path,
1063            'old_archive_url'  => $old_archive_url,
1064            'archive_path'     => $archive_path,
1065            'archive_url'      => $archive_url,
1066            'old_site_path'    => $old_site_path,
1067            'old_site_url'     => $old_site_url,
1068            'site_path'        => $site_path,
1069            'site_url'         => $site_url,
1070        };
1071        next unless %asset_ids;
1072
1073        my $fmgr = ( $site_path || $archive_path ) ? $blog->file_mgr : undef;
1074        next unless defined $fmgr;
1075
1076        my @assets =
1077          $asset_class->load( { blog_id => $id, class => '*' } );
1078        foreach my $asset (@assets) {
1079            my $path = $asset->column('file_path');
1080            my $url  = $asset->column('url');
1081            if ($archive_path) {
1082                $path =~ s/^\Q$old_archive_path\E/$archive_path/i;
1083                $asset->file_path($path);
1084            }
1085            if ($archive_url) {
1086                $url =~ s/^\Q$old_archive_url\E/$archive_url/i;
1087                $asset->url($url);
1088            }
1089            if ($site_path) {
1090                $path =~ s/^\Q$old_site_path\E/$site_path/i;
1091                $asset->file_path($path);
1092            }
1093            if ($site_url) {
1094                $url =~ s/^\Q$old_site_url\E/$site_url/i;
1095                $asset->url($url);
1096            }
1097            $app->print(
1098                $app->translate(
1099                    "Changing file path for the asset '[_1]' (ID:[_2])...",
1100                    $asset->label, $asset->id
1101                )
1102            );
1103            $asset->save
1104              or $app->print( $app->translate("failed") . "\n" ), next;
1105            $app->print( $app->translate("ok") . "\n" );
1106            unless ( $q->param('redirect') ) {
1107                my $old_id   = delete $asset_ids{ $asset->id };
1108                my $filename = "$old_id-" . $asset->file_name;
1109                my $file     = File::Spec->catfile( $tmp_dir, $filename );
1110                MT::BackupRestore->restore_asset( $file, $asset, $old_id, $fmgr,
1111                    \%error_assets, sub { $app->print(@_); } );
1112            }
1113        }
1114    }
1115    unless ( $q->param('redirect') ) {
1116        _restore_non_blog_asset( $app, $tmp_dir, \%asset_ids, \%error_assets );
1117    }
1118    if (%error_assets) {
1119        my $data;
1120        while ( my ( $key, $value ) = each %error_assets ) {
1121            $data .=
1122              $app->translate( 'MT::Asset#[_1]: ', $key ) . $value . "\n";
1123        }
1124        my $message = $app->translate(
1125            'Some of the actual files for assets could not be restored.');
1126        $app->log(
1127            {
1128                message  => $message,
1129                level    => MT::Log::WARNING(),
1130                class    => 'system',
1131                category => 'restore',
1132                metadata => $data,
1133            }
1134        );
1135        $error .= $message;
1136    }
1137
1138    if ($tmp_dir) {
1139        require File::Path;
1140        File::Path::rmtree($tmp_dir);
1141    }
1142
1143    my $param = {};
1144    if ( scalar $q->param('redirect') ) {
1145        $param->{restore_end} = 0;  # redirect=1 means we are from multi-uploads
1146        require JSON;
1147        $param->{blogs_meta} = JSON::objToJson( \%blogs_meta );
1148        $param->{next_mode}  = 'dialog_restore_upload';
1149    }
1150    else {
1151        $param->{restore_end} = 1;
1152    }
1153    if ($error) {
1154        $param->{error}     = $error;
1155        $param->{error_url} = $app->uri( mode => 'view_log', args => {} );
1156    }
1157    for my $key (
1158        qw(files last redirect is_dirty is_asset objects_json deferred_json))
1159    {
1160        $param->{$key} = scalar $q->param($key);
1161    }
1162    $param->{name}   = $q->param('current_file');
1163    $param->{assets} = encode_html( $q->param('assets') );
1164    $app->print( $app->build_page( 'dialog/restore_end.tmpl', $param ) );
1165}
1166
1167sub dialog_restore_upload {
1168    my $app  = shift;
1169    my $user = $app->user;
1170    return $app->errtrans("Permission denied.") if !$user->is_superuser;
1171    $app->validate_magic() or return;
1172
1173    my $q = $app->param;
1174
1175    my $current        = $q->param('current_file');
1176    my $last           = $q->param('last');
1177    my $files          = $q->param('files');
1178    my $assets_json    = $q->param('assets');
1179    my $is_asset       = $q->param('is_asset') || 0;
1180    my $schema_version = $q->param('schema_version')
1181      || $app->config('SchemaVersion');
1182    my $overwrite_template = $q->param('overwrite_templates') ? 1 : 0;
1183
1184    my $objects  = {};
1185    my $deferred = {};
1186    require JSON;
1187    my $objects_json = $q->param('objects_json') if $q->param('objects_json');
1188    $deferred = JSON::jsonToObj( $q->param('deferred_json') )
1189      if $q->param('deferred_json');
1190
1191    my ($fh) = $app->upload_info('file');
1192
1193    my $param = {};
1194    $param->{start}          = $q->param('start') || 0;
1195    $param->{is_asset}       = $is_asset;
1196    $param->{name}           = $current;
1197    $param->{files}          = $files;
1198    $param->{assets}         = $assets_json;
1199    $param->{last}           = $last;
1200    $param->{redirect}       = 1;
1201    $param->{is_dirty}       = $q->param('is_dirty');
1202    $param->{objects_json}   = $objects_json if defined($objects_json);
1203    $param->{deferred_json}  = JSON::objToJson($deferred) if defined($deferred);
1204    $param->{blogs_meta}     = $q->param('blogs_meta');
1205    $param->{schema_version} = $schema_version;
1206    $param->{overwrite_templates} = $overwrite_template;
1207
1208    my $uploaded = $q->param('file');
1209    if ( defined($uploaded) ) {
1210        $uploaded =~ s!\\!/!g;    ## Change backslashes to forward slashes
1211        my ( $volume, $directories, $uploaded_filename ) =
1212          File::Spec->splitpath($uploaded);
1213        if ( $current ne $uploaded_filename ) {
1214            close $fh if $uploaded_filename;
1215            $param->{error} =
1216              $app->translate( 'Please upload [_1] in this page.', $current );
1217            return $app->load_tmpl( 'dialog/restore_upload.tmpl', $param );
1218        }
1219    }
1220
1221    if (!$fh) {
1222        $param->{error} = $app->translate('File was not uploaded.')
1223          if !( $q->param('redirect') );
1224        return $app->load_tmpl( 'dialog/restore_upload.tmpl', $param );
1225    }
1226
1227    $app->{no_print_body} = 1;
1228
1229    local $| = 1;
1230    $app->send_http_header('text/html');
1231
1232    $app->print( $app->build_page( 'dialog/restore_start.tmpl', $param ) );
1233
1234    if ( defined $objects_json ) {
1235        my $objects_tmp = JSON::jsonToObj($objects_json);
1236        my %class2ids;
1237
1238        # { MT::CLASS#OLD_ID => NEW_ID }
1239        for my $key ( keys %$objects_tmp ) {
1240            my ( $class, $old_id ) = split '#', $key;
1241            if ( exists $class2ids{$class} ) {
1242                my $newids = $class2ids{$class}->{newids};
1243                push @$newids, $objects_tmp->{$key};
1244                my $keymaps = $class2ids{$class}->{keymaps};
1245                push @$keymaps,
1246                  { newid => $objects_tmp->{$key}, oldid => $old_id };
1247            }
1248            else {
1249                $class2ids{$class} = {
1250                    newids => [ $objects_tmp->{$key} ],
1251                    keymaps =>
1252                      [ { newid => $objects_tmp->{$key}, oldid => $old_id } ]
1253                };
1254            }
1255        }
1256        for my $class ( keys %class2ids ) {
1257            eval "require $class;";
1258            next if $@;
1259            my $newids  = $class2ids{$class}->{newids};
1260            my $keymaps = $class2ids{$class}->{keymaps};
1261            my @objs    = $class->load( { id => $newids } );
1262            for my $obj (@objs) {
1263                my @old_ids = grep { $_->{newid} eq $obj->id } @$keymaps;
1264                my $old_id = $old_ids[0]->{oldid};
1265                $objects->{"$class#$old_id"} = $obj;
1266            }
1267        }
1268    }
1269
1270    my $assets = JSON::jsonToObj( decode_html($assets_json) )
1271      if ( defined($assets_json) && $assets_json );
1272    $assets = [] if !defined($assets);
1273    my $asset;
1274    my @errors;
1275    my $error_assets = {};
1276    require MT::BackupRestore;
1277    my $blog_ids;
1278    my $asset_ids;
1279
1280    if ($is_asset) {
1281        $asset = shift @$assets;
1282        $asset->{fh} = $fh;
1283        my $blogs_meta = JSON::jsonToObj( $q->param('blogs_meta') || '{}' );
1284        MT::BackupRestore->_restore_asset_multi( $asset, $objects,
1285            $error_assets, sub { $app->print(@_); }, $blogs_meta );
1286        if ( defined( $error_assets->{ $asset->{asset_id} } ) ) {
1287            $app->log(
1288                {
1289                    message => $app->translate('Restoring a file failed: ')
1290                      . $error_assets->{ $asset->{asset_id} },
1291                    level    => MT::Log::WARNING(),
1292                    class    => 'system',
1293                    category => 'restore',
1294                }
1295            );
1296        }
1297    }
1298    else {
1299        ( $blog_ids, $asset_ids ) = eval {
1300            MT::BackupRestore->restore_process_single_file( $fh, $objects,
1301                $deferred, \@errors, $schema_version, $overwrite_template,
1302                sub { _progress($app, @_) } );
1303        };
1304        if ($@) {
1305            $last = 1;
1306        }
1307    }
1308
1309    my @files = split( ',', $files );
1310    my $file_next = shift @files if scalar(@files);
1311    if ( !defined($file_next) ) {
1312        if ( scalar(@$assets) ) {
1313            $asset             = $assets->[0];
1314            $file_next         = $asset->{asset_id} . '-' . $asset->{name};
1315            $param->{is_asset} = 1;
1316        }
1317    }
1318    $param->{files}  = join( ',', @files );
1319    $param->{assets} = encode_html( JSON::objToJson($assets) );
1320    $param->{name}   = $file_next;
1321    if ( 0 < scalar(@files) ) {
1322        $param->{last} = 0;
1323    }
1324    elsif ( 0 >= scalar(@$assets) - 1 ) {
1325        $param->{last} = 1;
1326    }
1327    else {
1328        $param->{last} = 0;
1329    }
1330    $param->{is_dirty} = scalar( keys %$deferred );
1331    if ($last) {
1332        $param->{restore_end} = 1;
1333        if ( $param->{is_dirty} ) {
1334            _log_dirty_restore( $app, $deferred );
1335            my $log_url = $app->uri( mode => 'view_log', args => {} );
1336            $param->{error} =
1337              $app->translate(
1338'Some objects were not restored because their parent objects were not restored.'
1339              );
1340            $param->{error_url} = $log_url;
1341        }
1342        elsif ( scalar( keys %$error_assets ) ) {
1343            $param->{error} =
1344              $app->translate('Some of the files were not restored correctly.');
1345            my $log_url = $app->uri( mode => 'view_log', args => {} );
1346            $param->{error_url} = $log_url;
1347        }
1348        elsif ( scalar @errors ) {
1349            $param->{error} = join '; ', @errors;
1350            my $log_url = $app->uri( mode => 'view_log', args => {} );
1351            $param->{error_url} = $log_url;
1352        }
1353        else {
1354            $app->log(
1355                {
1356                    message => $app->translate(
1357"Successfully restored objects to Movable Type system by user '[_1]'",
1358                        $app->user->name
1359                    ),
1360                    level    => MT::Log::INFO(),
1361                    class    => 'system',
1362                    category => 'restore'
1363                }
1364            );
1365            $param->{ok_url} = $app->uri( mode => 'start_restore', args => {} );
1366        }
1367    }
1368    else {
1369        my %objects_json;
1370        %objects_json = map { $_ => $objects->{$_}->id } keys %$objects;
1371        $param->{objects_json}  = JSON::objToJson( \%objects_json );
1372        $param->{deferred_json} = JSON::objToJson($deferred);
1373
1374        $param->{error} = join( '; ', @errors );
1375        if ( defined($blog_ids) && scalar(@$blog_ids) ) {
1376            $param->{next_mode} = 'dialog_adjust_sitepath';
1377            $param->{blog_ids}  = join( ',', @$blog_ids );
1378            $param->{asset_ids} = join( ',', @$asset_ids )
1379              if defined($asset_ids);
1380        }
1381        else {
1382            $param->{next_mode} = 'dialog_restore_upload';
1383        }
1384    }
1385    MT->run_callbacks('restore', $objects, $deferred, \@errors, sub { _progress( $app, @_ ) });
1386
1387    $app->print( $app->build_page( 'dialog/restore_end.tmpl', $param ) );
1388    close $fh if $fh;
1389}
1390
1391sub dialog_adjust_sitepath {
1392    my $app  = shift;
1393    my $user = $app->user;
1394    return $app->errtrans("Permission denied.") if !$user->is_superuser;
1395    $app->validate_magic() or return;
1396
1397    my $q         = $app->param;
1398    my $tmp_dir   = $q->param('tmp_dir');
1399    my $error     = $q->param('error') || q();
1400    my $uploaded  = $q->param('restore_upload') || 0;
1401    my @blog_ids  = split ',', $q->param('blog_ids');
1402    my $asset_ids = $q->param('asset_ids');
1403    my @blogs     = $app->model('blog')->load( { id => \@blog_ids } );
1404    my @blogs_loop;
1405
1406    foreach my $blog (@blogs) {
1407        push @blogs_loop,
1408          {
1409            name          => $blog->name,
1410            id            => $blog->id,
1411            old_site_path => $blog->site_path,
1412            old_site_url  => $blog->site_url,
1413            $blog->column('archive_path')
1414            ? ( old_archive_path => $blog->archive_path )
1415            : (),
1416            $blog->column('archive_url')
1417            ? ( old_archive_url => $blog->archive_url )
1418            : (),
1419          };
1420    }
1421    my $param = { blogs_loop => \@blogs_loop, tmp_dir => $tmp_dir };
1422    $param->{error}          = $error     if $error;
1423    $param->{restore_upload} = $uploaded  if $uploaded;
1424    $param->{asset_ids}      = $asset_ids if $asset_ids;
1425    for my $key (
1426        qw(files assets last redirect is_dirty is_asset objects_json deferred_json)
1427      )
1428    {
1429        $param->{$key} = $q->param($key) if $q->param($key);
1430    }
1431    $param->{name} = $q->param('current_file');
1432    $param->{screen_id} = "adjust-sitepath";
1433    $app->load_tmpl( 'dialog/adjust_sitepath.tmpl', $param );
1434}
1435
1436sub convert_to_html {
1437    my $app    = shift;
1438    my $format = $app->param('format');
1439    my $text   = $app->param('text');
1440    # XMLHttpRequest always send text in UTF-8... right?
1441    if ( defined $text ) {
1442        $text = encode_text($text, 'utf-8', $app->config->PublishCharset);
1443    } 
1444    else {
1445        $text = '' ;
1446    }
1447    my $text_more = $app->param('text_more');
1448    if ( defined $text_more ) {
1449        $text_more = encode_text($text_more, 'utf-8', $app->config->PublishCharset);
1450    } 
1451    else {
1452        $text_more = '' ;
1453    }
1454    my $result = {
1455        text      => $app->apply_text_filters( $text,      [$format] ),
1456        text_more => $app->apply_text_filters( $text_more, [$format] ),
1457        format    => $format,
1458    };
1459    return $app->json_result($result);
1460}
1461
1462sub update_list_prefs {
1463    my $app   = shift;
1464    my $prefs = $app->list_pref( $app->param('_type') );
1465    $app->call_return;
1466}
1467
1468sub recover_passwords {
1469    my $app = shift;
1470    my @id  = $app->param('id');
1471
1472    return $app->errtrans("Permission denied.")
1473      unless $app->user->is_superuser();
1474
1475    my $class = ref $app eq 'MT::App::Upgrader' ? 'MT::BasicAuthor' : $app->model('author');
1476    eval "use $class;";
1477
1478    my @msg_loop;
1479    foreach (@id) {
1480        my $author = $class->load($_)
1481            or next;
1482        my ( $rc, $res ) = reset_password( $app, $author, $author->hint );
1483        push @msg_loop, { message => $res };
1484    }
1485
1486    $app->load_tmpl( 'recover_password_result.tmpl',
1487        { message_loop => \@msg_loop, return_url => $app->return_uri } );
1488}
1489
1490sub reset_password {
1491    my $app      = shift;
1492    my ($author) = $_[0];
1493    my $hint     = $_[1];
1494    my $name     = $_[2];
1495
1496    require MT::Auth;
1497    require MT::Log;
1498    if ( !MT::Auth->can_recover_password ) {
1499        $app->log(
1500            {
1501                message => $app->translate(
1502"Invalid password recovery attempt; can't recover password in this configuration"
1503                ),
1504                level    => MT::Log::SECURITY(),
1505                class    => 'system',
1506                category => 'recover_password',
1507            }
1508        );
1509        return ( 0,
1510            $app->translate("Can't recover password in this configuration") );
1511    }
1512
1513    $app->log(
1514        {
1515            message => $app->translate(
1516                "Invalid user name '[_1]' in password recovery attempt", $name
1517            ),
1518            level    => MT::Log::SECURITY(),
1519            class    => 'system',
1520            category => 'recover_password',
1521        }
1522      ),
1523      return ( 0, $app->translate("User name or password hint is incorrect.") )
1524      unless $author;
1525    return ( 0,
1526        $app->translate("User has not set pasword hint; cannot recover password")
1527    ) if ( $hint && !$author->hint );
1528
1529    $app->log(
1530        {
1531            message => $app->translate(
1532                "Invalid attempt to recover password (used hint '[_1]')",
1533                $hint
1534            ),
1535            level    => MT::Log::SECURITY(),
1536            class    => 'system',
1537            category => 'recover_password'
1538        }
1539      ),
1540      return ( 0, $app->translate("User name or password hint is incorrect.") )
1541      unless $author->hint eq $hint;
1542
1543    return ( 0, $app->translate("User does not have email address") )
1544      unless $author->email;
1545
1546    my @pool = ( 'a' .. 'z', 0 .. 9 );
1547    my $pass = '';
1548    for ( 1 .. 8 ) { $pass .= $pool[ rand @pool ] }
1549    $author->set_password($pass);
1550    $author->save;
1551    my $message =
1552      $app->translate(
1553"Password was reset for user '[_1]' (user #[_2]). Password was sent to the following address: [_3]",
1554        $author->name, $author->id, $author->email );
1555    $app->log(
1556        {
1557            message  => $message,
1558            level    => MT::Log::SECURITY(),
1559            class    => 'system',
1560            category => 'recover_password'
1561        }
1562    );
1563
1564    my $address =
1565      defined $author->nickname
1566      ? $author->nickname . ' <' . $author->email . '>'
1567      : $author->email;
1568    my %head = (
1569        id      => 'recover_password',
1570        To      => $address,
1571        From    => $app->config('EmailAddressMain') || $address,
1572        Subject => $app->translate("Password Recovery")
1573    );
1574    my $charset = $app->charset;
1575    my $mail_enc = uc( $app->config('MailEncoding') || $charset );
1576    $head{'Content-Type'} = qq(text/plain; charset="$mail_enc");
1577
1578    my $body = $app->build_email( 'recover-password.tmpl',
1579        { user_password => $pass, link_to_login => $app->base . $app->mt_uri } 
1580    );
1581    $body = wrap_text( $body, 72 );
1582    require MT::Mail;
1583    MT::Mail->send( \%head, $body )
1584      or return (
1585        0,
1586        $app->translate(
1587            "Error sending mail ([_1]); please fix the problem, then "
1588              . "try again to recover your password.",
1589            MT::Mail->errstr
1590        )
1591      );
1592    ( 1, $message );
1593}
1594
1595sub restore_file {
1596    my $app = shift;
1597    my ( $fh, $errormsg ) = @_;
1598    my $q = $app->param;
1599    my $schema_version =
1600      $q->param('ignore_schema_conflict')
1601      ? 'ignore'
1602      : $app->config('SchemaVersion');
1603    my $overwrite_template = $q->param('overwrite_global_templates') ? 1 : 0;
1604
1605    require MT::BackupRestore;
1606    my ( $deferred, $blogs ) =
1607      MT::BackupRestore->restore_file( $fh, $errormsg, $schema_version, $overwrite_template,
1608        sub { _progress( $app, @_ ); } );
1609
1610    if ( !defined($deferred) || scalar( keys %$deferred ) ) {
1611        _log_dirty_restore( $app, $deferred );
1612        my $log_url = $app->uri( mode => 'view_log', args => {} );
1613        $$errormsg .= '; ' if $$errormsg;
1614        $$errormsg .= $app->translate(
1615'Some objects were not restored because their parent objects were not restored.  Detailed information is in the <a href="javascript:void(0);" onclick="closeDialog(\'[_1]\');">activity log</a>.',
1616            $log_url
1617        );
1618        return $blogs;
1619    }
1620    if ($$errormsg) {
1621        $app->log(
1622            {
1623                message  => $$errormsg,
1624                level    => MT::Log::ERROR(),
1625                class    => 'system',
1626                category => 'restore',
1627            }
1628        );
1629        return $blogs;
1630    }
1631
1632    $app->log(
1633        {
1634            message => $app->translate(
1635"Successfully restored objects to Movable Type system by user '[_1]'",
1636                $app->user->name
1637            ),
1638            level    => MT::Log::INFO(),
1639            class    => 'system',
1640            category => 'restore'
1641        }
1642    );
1643
1644    $blogs;
1645}
1646
1647sub restore_directory {
1648    my $app = shift;
1649    my ( $dir, $error ) = @_;
1650
1651    if ( !-d $dir ) {
1652        $$error = $app->translate( '[_1] is not a directory.', $dir );
1653        return ( undef, undef );
1654    }
1655
1656    my $q = $app->param;
1657    my $schema_version =
1658      $q->param('ignore_schema_conflict')
1659      ? 'ignore'
1660      : $app->config('SchemaVersion');
1661
1662    my $overwrite_template = $q->param('overwrite_global_templates') ? 1 : 0;
1663
1664    my @errors;
1665    my %error_assets;
1666    require MT::BackupRestore;
1667    my ( $deferred, $blogs, $assets ) =
1668      MT::BackupRestore->restore_directory( $dir, \@errors, \%error_assets,
1669        $schema_version, $overwrite_template, sub { _progress( $app, @_ ); } );
1670
1671    if ( scalar @errors ) {
1672        $$error = $app->translate('Error occured during restore process.');
1673        $app->log(
1674            {
1675                message  => $$error,
1676                level    => MT::Log::WARNING(),
1677                class    => 'system',
1678                category => 'restore',
1679                metadata => join( '; ', @errors ),
1680            }
1681        );
1682    }
1683    return ( $blogs, $assets ) unless ( defined($deferred) && %$deferred );
1684
1685    if ( scalar( keys %error_assets ) ) {
1686        my $data;
1687        while ( my ( $key, $value ) = each %error_assets ) {
1688            $data .=
1689              $app->translate( 'MT::Asset#[_1]: ', $key ) . $value . "\n";
1690        }
1691        my $message = $app->translate('Some of files could not be restored.');
1692        $app->log(
1693            {
1694                message  => $message,
1695                level    => MT::Log::WARNING(),
1696                class    => 'system',
1697                category => 'restore',
1698                metadata => $data,
1699            }
1700        );
1701        $$error .= $message;
1702    }
1703
1704    if ( scalar( keys %$deferred ) ) {
1705        _log_dirty_restore( $app, $deferred );
1706        my $log_url = $app->uri( mode => 'view_log', args => {} );
1707        $$error = $app->translate(
1708'Some objects were not restored because their parent objects were not restored.  Detailed information is in the <a href="javascript:void(0);" onclick="closeDialog(\'[_1]\');">activity log</a>.',
1709            $log_url
1710        );
1711        return ( $blogs, $assets );
1712    }
1713
1714    return ( $blogs, $assets ) if $$error;
1715
1716    $app->log(
1717        {
1718            message => $app->translate(
1719"Successfully restored objects to Movable Type system by user '[_1]'",
1720                $app->user->name
1721            ),
1722            level    => MT::Log::INFO(),
1723            class    => 'system',
1724            category => 'restore'
1725        }
1726    );
1727    return ( $blogs, $assets );
1728}
1729
1730sub restore_upload_manifest {
1731    my $app  = shift;
1732    my ($fh) = @_;
1733    my $user = $app->user;
1734    return $app->errtrans("Permission denied.") if !$user->is_superuser;
1735    $app->validate_magic() or return;
1736
1737    my $q = $app->param;
1738
1739    require MT::BackupRestore;
1740    my $backups = MT::BackupRestore->process_manifest($fh);
1741    return $app->errtrans(
1742        "Uploaded file was not a valid Movable Type backup manifest file.")
1743      if !defined($backups);
1744
1745    my $files     = $backups->{files};
1746    my $assets    = $backups->{assets};
1747    my $file_next = shift @$files if defined($files) && scalar(@$files);
1748    my $assets_json;
1749    my $param = {};
1750
1751    if ( !defined($file_next) ) {
1752        if ( scalar(@$assets) > 0 ) {
1753            my $asset = shift @$assets;
1754            $file_next = $asset->{name};
1755            $param->{is_asset} = 1;
1756        }
1757    }
1758    require JSON;
1759    $assets_json = encode_url( JSON::objToJson($assets) )
1760      if scalar(@$assets) > 0;
1761    $param->{files}       = join( ',', @$files );
1762    $param->{assets}      = $assets_json;
1763    $param->{filename}    = $file_next;
1764    $param->{last}        = scalar(@$files) ? 0 : ( scalar(@$assets) ? 0 : 1 );
1765    $param->{open_dialog} = 1;
1766    $param->{schema_version} =
1767      $q->param('ignore_schema_conflict')
1768      ? 'ignore'
1769      : $app->config('SchemaVersion');
1770    $param->{overwrite_templates} = $q->param('overwrite_global_templates') ? 1 : 0;
1771
1772    $param->{dialog_mode} = 'dialog_restore_upload';
1773    $param->{dialog_params} =
1774        'start=1'
1775      . '&amp;magic_token='
1776      . $app->current_magic
1777      . '&amp;files='
1778      . $param->{files}
1779      . '&amp;assets='
1780      . $param->{assets}
1781      . '&amp;current_file='
1782      . $param->{filename}
1783      . '&amp;last='
1784      . $param->{'last'}
1785      . '&amp;schema_version='
1786      . $param->{schema_version}
1787      . '&amp;overwrite_templates='
1788      . $param->{overwrite_templates}
1789      . '&amp;redirect=1';
1790    $app->load_tmpl( 'restore.tmpl', $param );
1791
1792    #close $fh if $fh;
1793}
1794
1795sub _backup_finisher {
1796    my $app = shift;
1797    my ( $fnames, $param ) = @_;
1798    unless ( ref $fnames ) {
1799        $fnames = [$fnames];
1800    }
1801    $param->{filename}       = $fnames->[0];
1802    $param->{backup_success} = 1;
1803    require MT::Session;
1804    MT::Session->remove( { kind => 'BU' } );
1805    foreach my $fname (@$fnames) {
1806        my $sess = MT::Session->new;
1807        $sess->id( $app->make_magic_token() );
1808        $sess->kind('BU');    # BU == Backup
1809        $sess->name($fname);
1810        $sess->start(time);
1811        $sess->save;
1812    }
1813    my $message;
1814    if ( my $blog_id = $param->{blog_id} || $param->{blog_ids} ) {
1815        $message = $app->translate(
1816            "Blog(s) (ID:[_1]) was/were successfully backed up by user '[_2]'",
1817            $blog_id, $app->user->name
1818        );
1819    }
1820    else {
1821        $message =
1822          $app->translate(
1823            "Movable Type system was successfully backed up by user '[_1]'",
1824            $app->user->name );
1825    }
1826    $app->log(
1827        {
1828            message  => $message,
1829            level    => MT::Log::INFO(),
1830            class    => 'system',
1831            category => 'restore'
1832        }
1833    );
1834    $app->print( $app->build_page( 'include/backup_end.tmpl', $param ) );
1835}
1836
1837sub _progress {
1838    my $app = shift;
1839    my $ids = $app->request('progress_ids') || {};
1840
1841    my ( $str, $id ) = @_;
1842    if ( $id && $ids->{$id} ) {
1843        my $str_js = encode_js($str);
1844        $app->print(
1845qq{<script type="text/javascript">progress('$str_js', '$id');</script>}
1846        );
1847    }
1848    elsif ($id) {
1849        $ids->{$id} = 1;
1850        $app->print(qq{\n<span id="$id">$str</span>});
1851    }
1852    else {
1853        $app->print("<span>$str</span>");
1854    }
1855
1856    $app->request( 'progress_ids', $ids );
1857}
1858
1859sub _log_dirty_restore {
1860    my $app = shift;
1861    my ($deferred) = @_;
1862    my %deferred_by_class;
1863    for my $key ( keys %$deferred ) {
1864        my ( $class, $id ) = split( '#', $key );
1865        if ( exists $deferred_by_class{$class} ) {
1866            push @{ $deferred_by_class{$class} }, $id;
1867        }
1868        else {
1869            $deferred_by_class{$class} = [$id];
1870
1871        }
1872    }
1873    while ( my ( $class_name, $ids ) = each %deferred_by_class ) {
1874        my $message = $app->translate(
1875'Some [_1] were not restored because their parent objects were not restored.',
1876            $class_name
1877        );
1878        $app->log(
1879            {
1880                message  => $message,
1881                level    => MT::Log::WARNING(),
1882                class    => 'system',
1883                category => 'restore',
1884                metadata => join( ', ', @$ids ),
1885            }
1886        );
1887    }
1888    1;
1889}
1890
18911;
Note: See TracBrowser for help on using the browser.