root/branches/release-40/lib/MT/CMS/Tools.pm @ 2600

Revision 2600, 62.4 kB (checked in by fumiakiy, 18 months ago)

Use the variable that is already declared so error messages flow properly. BugId:80202

  • 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            if ( $uploaded_filename =~ /^.+\.tar(\.gz)?$/i ) {
820                $arc = MT::Util::Archive->new('tgz', $fh);
821            }
822            elsif ( $uploaded_filename =~ /^.+\.zip$/i ) {
823                $arc = MT::Util::Archive->new('zip', $fh);
824            }
825            else {
826                $error =
827                  $app->translate(
828                    'Please use xml, tar.gz, zip, or manifest as a file extension.'
829                  );
830            }
831            unless ($arc) {
832                $result = 0;
833                $param->{restore_success} = 0;
834                if ($error) {
835                    $param->{error}           = $error;
836                }
837                else {
838                    $error = MT->translate('Unknown file format');
839                    $app->log(
840                        {
841                            message  => $error . ":$uploaded_filename",
842                            level    => MT::Log::WARNING(),
843                            class    => 'system',
844                            category => 'restore',
845                            metadata => MT::Util::Archive->errstr,
846                        }
847                    );
848                }
849                $app->print( $error );
850                $app->print(
851                    $app->build_page( "restore_end.tmpl", $param ) );
852                close $fh if $fh;
853                return 1;
854            }
855            my $temp_dir = $app->config('TempDir');
856            require File::Temp;
857            my $tmp = File::Temp::tempdir( $uploaded_filename . 'XXXX',
858                DIR => $temp_dir );
859            $arc->extract($tmp);
860            $arc->close;
861            my ( $blog_ids, $asset_ids ) =
862              restore_directory( $app, $tmp, \$error );
863            if (defined $blog_ids) {
864                $param->{open_dialog} = 1;
865                $param->{blog_ids} = join( ',', @$blog_ids )
866                  if defined $blog_ids;
867                $param->{asset_ids} = join( ',', @$asset_ids )
868                  if defined $asset_ids;
869                $param->{tmp_dir} = $tmp;
870            }
871            elsif ( defined $asset_ids ) {
872                my %asset_ids = @$asset_ids;
873                my %error_assets;
874                _restore_non_blog_asset( $app, $tmp, \%asset_ids, \%error_assets );
875                if (%error_assets) {
876                    my $data;
877                    while ( my ( $key, $value ) = each %error_assets ) {
878                        $data .=
879                          $app->translate( 'MT::Asset#[_1]: ', $key ) . $value . "\n";
880                    }
881                    my $message = $app->translate(
882                        'Some of the actual files for assets could not be restored.');
883                    $app->log(
884                        {
885                            message  => $message,
886                            level    => MT::Log::WARNING(),
887                            class    => 'system',
888                            category => 'restore',
889                            metadata => $data,
890                        }
891                    );
892                    $error .= $message;
893                }
894            }
895        }
896    }
897    $param->{restore_success} = !$error;
898    $param->{error} = $error if $error;
899    if ( ( exists $param->{open_dialog} ) && ( $param->{open_dialog} ) ) {
900        $param->{dialog_mode} = 'dialog_adjust_sitepath';
901        $param->{dialog_params} =
902            'magic_token='
903          . $app->current_magic
904          . '&amp;blog_ids='
905          . $param->{blog_ids}
906          . '&amp;asset_ids='
907          . $param->{asset_ids}
908          . '&amp;tmp_dir='
909          . encode_url( $param->{tmp_dir} );
910        if ( ( $param->{restore_upload} ) && ( $param->{restore_upload} ) ) {
911            $param->{dialog_params} .= '&amp;restore_upload=1';
912        }
913        if ( ( $param->{error} ) && ( $param->{error} ) ) {
914            $param->{dialog_params} .=
915              '&amp;error=' . encode_url( $param->{error} );
916        }
917    }
918
919    $app->print( $app->build_page( "restore_end.tmpl", $param ) );
920    close $fh if $fh;
921    1;
922}
923
924sub restore_premature_cancel {
925    my $app  = shift;
926    my $user = $app->user;
927    return $app->errtrans("Permission denied.") if !$user->is_superuser;
928    $app->validate_magic() or return;
929
930    require JSON;
931    my $deferred = JSON::jsonToObj( $app->param('deferred_json') )
932      if $app->param('deferred_json');
933    my $param = { restore_success => 1 };
934    if ( defined $deferred && ( scalar( keys %$deferred ) ) ) {
935        _log_dirty_restore( $app, $deferred );
936        my $log_url = $app->uri( mode => 'view_log', args => {} );
937        $param->{restore_success} = 0;
938        my $message =
939          $app->translate(
940'Some objects were not restored because their parent objects were not restored.'
941          );
942        $param->{error} = $message . '  '
943          . $app->translate(
944"Detailed information is in the <a href='javascript:void(0)' onclick='closeDialog(\"[_1]\")'>activity log</a>.",
945            $log_url
946          );
947    }
948    else {
949        $app->log(
950            {
951                message => $app->translate(
952'[_1] has canceled the multiple files restore operation prematurely.',
953                    $app->user->name
954                ),
955                level    => MT::Log::WARNING(),
956                class    => 'system',
957                category => 'restore',
958            }
959        );
960    }
961    $app->redirect( $app->uri( mode => 'view_log', args => {} ) );
962}
963
964sub _restore_non_blog_asset {
965    my ( $app, $tmp_dir, $asset_ids, $error_assets ) = @_;
966    require MT::FileMgr;
967    my $fmgr = MT::FileMgr->new('Local');
968    foreach my $new_id ( keys %$asset_ids ) {
969        my $asset = $app->model('asset')->load($new_id);
970        next unless $asset;
971        my $old_id = $asset_ids->{$new_id};
972        my $filename = $old_id . '-' . $asset->file_name;
973        my $file    = File::Spec->catfile( $tmp_dir, $filename );
974        MT::BackupRestore->restore_asset( $file, $asset, $old_id, $fmgr,
975            $error_assets, sub { $app->print(@_); } );
976    }
977}
978
979sub adjust_sitepath {
980    my $app  = shift;
981    my $user = $app->user;
982    return $app->errtrans("Permission denied.") if !$user->is_superuser;
983    $app->validate_magic() or return;
984
985    require MT::BackupRestore;
986
987    my $q         = $app->param;
988    my $tmp_dir   = $q->param('tmp_dir');
989    my $error     = $q->param('error') || q();
990    my %asset_ids = split ',', $q->param('asset_ids');
991
992    $app->{no_print_body} = 1;
993
994    local $| = 1;
995    $app->send_http_header('text/html');
996
997    $app->print( $app->build_page( 'dialog/restore_start.tmpl', {} ) );
998
999    my $asset_class = $app->model('asset');
1000    my %error_assets;
1001    my %blogs_meta;
1002    my @p = $q->param;
1003    foreach my $p (@p) {
1004        next unless $p =~ /^site_path_(\d+)/;
1005        my $id            = $1;
1006        my $blog          = $app->model('blog')->load($id)
1007            or return $app->error($app->translate('Can\'t load blog #[_1].', $id));
1008        my $old_site_path = scalar $q->param("old_site_path_$id");
1009        my $old_site_url  = scalar $q->param("old_site_url_$id");
1010        my $site_path     = scalar $q->param("site_path_$id") || q();
1011        my $site_url      = scalar $q->param("site_url_$id") || q();
1012        $blog->site_path($site_path);
1013        $blog->site_url($site_url);
1014
1015        if ( $site_url || $site_path ) {
1016            $app->print(
1017                $app->translate(
1018                    "Changing Site Path for the blog '[_1]' (ID:[_2])...",
1019                    $blog->name, $blog->id
1020                )
1021            );
1022        }
1023        else {
1024            $app->print(
1025                $app->translate(
1026                    "Removing Site Path for the blog '[_1]' (ID:[_2])...",
1027                    $blog->name, $blog->id
1028                )
1029            );
1030        }
1031        my $old_archive_path = scalar $q->param("old_archive_path_$id");
1032        my $old_archive_url  = scalar $q->param("old_archive_url_$id");
1033        my $archive_path     = scalar $q->param("archive_path_$id") || q();
1034        my $archive_url      = scalar $q->param("archive_url_$id") || q();
1035        $blog->archive_path($archive_path);
1036        $blog->archive_url($archive_url);
1037        if (   ( $old_archive_url && $archive_url )
1038            || ( $old_archive_path && $archive_path ) )
1039        {
1040            $app->print(
1041                "\n"
1042                  . $app->translate(
1043                    "Changing Archive Path for the blog '[_1]' (ID:[_2])...",
1044                    $blog->name, $blog->id
1045                  )
1046            );
1047        }
1048        elsif ( $old_archive_url || $old_archive_path ) {
1049            $app->print(
1050                "\n"
1051                  . $app->translate(
1052                    "Removing Archive Path for the blog '[_1]' (ID:[_2])...",
1053                    $blog->name, $blog->id
1054                  )
1055            );
1056        }
1057        $blog->save or $app->print( $app->translate("failed") . "\n" ), next;
1058        $app->print( $app->translate("ok") . "\n" );
1059
1060        $blogs_meta{$id} = {
1061            'old_archive_path' => $old_archive_path,
1062            'old_archive_url'  => $old_archive_url,
1063            'archive_path'     => $archive_path,
1064            'archive_url'      => $archive_url,
1065            'old_site_path'    => $old_site_path,
1066            'old_site_url'     => $old_site_url,
1067            'site_path'        => $site_path,
1068            'site_url'         => $site_url,
1069        };
1070        next unless %asset_ids;
1071
1072        my $fmgr = ( $site_path || $archive_path ) ? $blog->file_mgr : undef;
1073        next unless defined $fmgr;
1074
1075        my @assets =
1076          $asset_class->load( { blog_id => $id, class => '*' } );
1077        foreach my $asset (@assets) {
1078            my $path = $asset->column('file_path');
1079            my $url  = $asset->column('url');
1080            if ($archive_path) {
1081                $path =~ s/^\Q$old_archive_path\E/$archive_path/i;
1082                $asset->file_path($path);
1083            }
1084            if ($archive_url) {
1085                $url =~ s/^\Q$old_archive_url\E/$archive_url/i;
1086                $asset->url($url);
1087            }
1088            if ($site_path) {
1089                $path =~ s/^\Q$old_site_path\E/$site_path/i;
1090                $asset->file_path($path);
1091            }
1092            if ($site_url) {
1093                $url =~ s/^\Q$old_site_url\E/$site_url/i;
1094                $asset->url($url);
1095            }
1096            $app->print(
1097                $app->translate(
1098                    "Changing file path for the asset '[_1]' (ID:[_2])...",
1099                    $asset->label, $asset->id
1100                )
1101            );
1102            $asset->save
1103              or $app->print( $app->translate("failed") . "\n" ), next;
1104            $app->print( $app->translate("ok") . "\n" );
1105            unless ( $q->param('redirect') ) {
1106                my $old_id   = delete $asset_ids{ $asset->id };
1107                my $filename = "$old_id-" . $asset->file_name;
1108                my $file     = File::Spec->catfile( $tmp_dir, $filename );
1109                MT::BackupRestore->restore_asset( $file, $asset, $old_id, $fmgr,
1110                    \%error_assets, sub { $app->print(@_); } );
1111            }
1112        }
1113    }
1114    unless ( $q->param('redirect') ) {
1115        _restore_non_blog_asset( $app, $tmp_dir, \%asset_ids, \%error_assets );
1116    }
1117    if (%error_assets) {
1118        my $data;
1119        while ( my ( $key, $value ) = each %error_assets ) {
1120            $data .=
1121              $app->translate( 'MT::Asset#[_1]: ', $key ) . $value . "\n";
1122        }
1123        my $message = $app->translate(
1124            'Some of the actual files for assets could not be restored.');
1125        $app->log(
1126            {
1127                message  => $message,
1128                level    => MT::Log::WARNING(),
1129                class    => 'system',
1130                category => 'restore',
1131                metadata => $data,
1132            }
1133        );
1134        $error .= $message;
1135    }
1136
1137    if ($tmp_dir) {
1138        require File::Path;
1139        File::Path::rmtree($tmp_dir);
1140    }
1141
1142    my $param = {};
1143    if ( scalar $q->param('redirect') ) {
1144        $param->{restore_end} = 0;  # redirect=1 means we are from multi-uploads
1145        require JSON;
1146        $param->{blogs_meta} = JSON::objToJson( \%blogs_meta );
1147        $param->{next_mode}  = 'dialog_restore_upload';
1148    }
1149    else {
1150        $param->{restore_end} = 1;
1151    }
1152    if ($error) {
1153        $param->{error}     = $error;
1154        $param->{error_url} = $app->uri( mode => 'view_log', args => {} );
1155    }
1156    for my $key (
1157        qw(files last redirect is_dirty is_asset objects_json deferred_json))
1158    {
1159        $param->{$key} = scalar $q->param($key);
1160    }
1161    $param->{name}   = $q->param('current_file');
1162    $param->{assets} = encode_html( $q->param('assets') );
1163    $app->print( $app->build_page( 'dialog/restore_end.tmpl', $param ) );
1164}
1165
1166sub dialog_restore_upload {
1167    my $app  = shift;
1168    my $user = $app->user;
1169    return $app->errtrans("Permission denied.") if !$user->is_superuser;
1170    $app->validate_magic() or return;
1171
1172    my $q = $app->param;
1173
1174    my $current        = $q->param('current_file');
1175    my $last           = $q->param('last');
1176    my $files          = $q->param('files');
1177    my $assets_json    = $q->param('assets');
1178    my $is_asset       = $q->param('is_asset') || 0;
1179    my $schema_version = $q->param('schema_version')
1180      || $app->config('SchemaVersion');
1181    my $overwrite_template = $q->param('overwrite_templates') ? 1 : 0;
1182
1183    my $objects  = {};
1184    my $deferred = {};
1185    require JSON;
1186    my $objects_json = $q->param('objects_json') if $q->param('objects_json');
1187    $deferred = JSON::jsonToObj( $q->param('deferred_json') )
1188      if $q->param('deferred_json');
1189
1190    my ($fh) = $app->upload_info('file');
1191
1192    my $param = {};
1193    $param->{start}          = $q->param('start') || 0;
1194    $param->{is_asset}       = $is_asset;
1195    $param->{name}           = $current;
1196    $param->{files}          = $files;
1197    $param->{assets}         = $assets_json;
1198    $param->{last}           = $last;
1199    $param->{redirect}       = 1;
1200    $param->{is_dirty}       = $q->param('is_dirty');
1201    $param->{objects_json}   = $objects_json if defined($objects_json);
1202    $param->{deferred_json}  = JSON::objToJson($deferred) if defined($deferred);
1203    $param->{blogs_meta}     = $q->param('blogs_meta');
1204    $param->{schema_version} = $schema_version;
1205    $param->{overwrite_templates} = $overwrite_template;
1206
1207    my $uploaded = $q->param('file');
1208    if ( defined($uploaded) ) {
1209        $uploaded =~ s!\\!/!g;    ## Change backslashes to forward slashes
1210        my ( $volume, $directories, $uploaded_filename ) =
1211          File::Spec->splitpath($uploaded);
1212        if ( $current ne $uploaded_filename ) {
1213            close $fh if $uploaded_filename;
1214            $param->{error} =
1215              $app->translate( 'Please upload [_1] in this page.', $current );
1216            return $app->load_tmpl( 'dialog/restore_upload.tmpl', $param );
1217        }
1218    }
1219
1220    if (!$fh) {
1221        $param->{error} = $app->translate('File was not uploaded.')
1222          if !( $q->param('redirect') );
1223        return $app->load_tmpl( 'dialog/restore_upload.tmpl', $param );
1224    }
1225
1226    $app->{no_print_body} = 1;
1227
1228    local $| = 1;
1229    $app->send_http_header('text/html');
1230
1231    $app->print( $app->build_page( 'dialog/restore_start.tmpl', $param ) );
1232
1233    if ( defined $objects_json ) {
1234        my $objects_tmp = JSON::jsonToObj($objects_json);
1235        my %class2ids;
1236
1237        # { MT::CLASS#OLD_ID => NEW_ID }
1238        for my $key ( keys %$objects_tmp ) {
1239            my ( $class, $old_id ) = split '#', $key;
1240            if ( exists $class2ids{$class} ) {
1241                my $newids = $class2ids{$class}->{newids};
1242                push @$newids, $objects_tmp->{$key};
1243                my $keymaps = $class2ids{$class}->{keymaps};
1244                push @$keymaps,
1245                  { newid => $objects_tmp->{$key}, oldid => $old_id };
1246            }
1247            else {
1248                $class2ids{$class} = {
1249                    newids => [ $objects_tmp->{$key} ],
1250                    keymaps =>
1251                      [ { newid => $objects_tmp->{$key}, oldid => $old_id } ]
1252                };
1253            }
1254        }
1255        for my $class ( keys %class2ids ) {
1256            eval "require $class;";
1257            next if $@;
1258            my $newids  = $class2ids{$class}->{newids};
1259            my $keymaps = $class2ids{$class}->{keymaps};
1260            my @objs    = $class->load( { id => $newids } );
1261            for my $obj (@objs) {
1262                my @old_ids = grep { $_->{newid} eq $obj->id } @$keymaps;
1263                my $old_id = $old_ids[0]->{oldid};
1264                $objects->{"$class#$old_id"} = $obj;
1265            }
1266        }
1267    }
1268
1269    my $assets = JSON::jsonToObj( decode_html($assets_json) )
1270      if ( defined($assets_json) && $assets_json );
1271    $assets = [] if !defined($assets);
1272    my $asset;
1273    my @errors;
1274    my $error_assets = {};
1275    require MT::BackupRestore;
1276    my $blog_ids;
1277    my $asset_ids;
1278
1279    if ($is_asset) {
1280        $asset = shift @$assets;
1281        $asset->{fh} = $fh;
1282        my $blogs_meta = JSON::jsonToObj( $q->param('blogs_meta') || '{}' );
1283        MT::BackupRestore->_restore_asset_multi( $asset, $objects,
1284            $error_assets, sub { $app->print(@_); }, $blogs_meta );
1285        if ( defined( $error_assets->{ $asset->{asset_id} } ) ) {
1286            $app->log(
1287                {
1288                    message => $app->translate('Restoring a file failed: ')
1289                      . $error_assets->{ $asset->{asset_id} },
1290                    level    => MT::Log::WARNING(),
1291                    class    => 'system',
1292                    category => 'restore',
1293                }
1294            );
1295        }
1296    }
1297    else {
1298        ( $blog_ids, $asset_ids ) = eval {
1299            MT::BackupRestore->restore_process_single_file( $fh, $objects,
1300                $deferred, \@errors, $schema_version, $overwrite_template,
1301                sub { _progress($app, @_) } );
1302        };
1303        if ($@) {
1304            $last = 1;
1305        }
1306    }
1307
1308    my @files = split( ',', $files );
1309    my $file_next = shift @files if scalar(@files);
1310    if ( !defined($file_next) ) {
1311        if ( scalar(@$assets) ) {
1312            $asset             = $assets->[0];
1313            $file_next         = $asset->{asset_id} . '-' . $asset->{name};
1314            $param->{is_asset} = 1;
1315        }
1316    }
1317    $param->{files}  = join( ',', @files );
1318    $param->{assets} = encode_html( JSON::objToJson($assets) );
1319    $param->{name}   = $file_next;
1320    if ( 0 < scalar(@files) ) {
1321        $param->{last} = 0;
1322    }
1323    elsif ( 0 >= scalar(@$assets) - 1 ) {
1324        $param->{last} = 1;
1325    }
1326    else {
1327        $param->{last} = 0;
1328    }
1329    $param->{is_dirty} = scalar( keys %$deferred );
1330    if ($last) {
1331        $param->{restore_end} = 1;
1332        if ( $param->{is_dirty} ) {
1333            _log_dirty_restore( $app, $deferred );
1334            my $log_url = $app->uri( mode => 'view_log', args => {} );
1335            $param->{error} =
1336              $app->translate(
1337'Some objects were not restored because their parent objects were not restored.'
1338              );
1339            $param->{error_url} = $log_url;
1340        }
1341        elsif ( scalar( keys %$error_assets ) ) {
1342            $param->{error} =
1343              $app->translate('Some of the files were not restored correctly.');
1344            my $log_url = $app->uri( mode => 'view_log', args => {} );
1345            $param->{error_url} = $log_url;
1346        }
1347        elsif ( scalar @errors ) {
1348            $param->{error} = join '; ', @errors;
1349            my $log_url = $app->uri( mode => 'view_log', args => {} );
1350            $param->{error_url} = $log_url;
1351        }
1352        else {
1353            $app->log(
1354                {
1355                    message => $app->translate(
1356"Successfully restored objects to Movable Type system by user '[_1]'",
1357                        $app->user->name
1358                    ),
1359                    level    => MT::Log::INFO(),
1360                    class    => 'system',
1361                    category => 'restore'
1362                }
1363            );
1364            $param->{ok_url} = $app->uri( mode => 'start_restore', args => {} );
1365        }
1366    }
1367    else {
1368        my %objects_json;
1369        %objects_json = map { $_ => $objects->{$_}->id } keys %$objects;
1370        $param->{objects_json}  = JSON::objToJson( \%objects_json );
1371        $param->{deferred_json} = JSON::objToJson($deferred);
1372
1373        $param->{error} = join( '; ', @errors );
1374        if ( defined($blog_ids) && scalar(@$blog_ids) ) {
1375            $param->{next_mode} = 'dialog_adjust_sitepath';
1376            $param->{blog_ids}  = join( ',', @$blog_ids );
1377            $param->{asset_ids} = join( ',', @$asset_ids )
1378              if defined($asset_ids);
1379        }
1380        else {
1381            $param->{next_mode} = 'dialog_restore_upload';
1382        }
1383    }
1384    MT->run_callbacks('restore', $objects, $deferred, \@errors, sub { _progress( $app, @_ ) });
1385
1386    $app->print( $app->build_page( 'dialog/restore_end.tmpl', $param ) );
1387    close $fh if $fh;
1388}
1389
1390sub dialog_adjust_sitepath {
1391    my $app  = shift;
1392    my $user = $app->user;
1393    return $app->errtrans("Permission denied.") if !$user->is_superuser;
1394    $app->validate_magic() or return;
1395
1396    my $q         = $app->param;
1397    my $tmp_dir   = $q->param('tmp_dir');
1398    my $error     = $q->param('error') || q();
1399    my $uploaded  = $q->param('restore_upload') || 0;
1400    my @blog_ids  = split ',', $q->param('blog_ids');
1401    my $asset_ids = $q->param('asset_ids');
1402    my @blogs     = $app->model('blog')->load( { id => \@blog_ids } );
1403    my @blogs_loop;
1404
1405    foreach my $blog (@blogs) {
1406        push @blogs_loop,
1407          {
1408            name          => $blog->name,
1409            id            => $blog->id,
1410            old_site_path => $blog->site_path,
1411            old_site_url  => $blog->site_url,
1412            $blog->column('archive_path')
1413            ? ( old_archive_path => $blog->archive_path )
1414            : (),
1415            $blog->column('archive_url')
1416            ? ( old_archive_url => $blog->archive_url )
1417            : (),
1418          };
1419    }
1420    my $param = { blogs_loop => \@blogs_loop, tmp_dir => $tmp_dir };
1421    $param->{error}          = $error     if $error;
1422    $param->{restore_upload} = $uploaded  if $uploaded;
1423    $param->{asset_ids}      = $asset_ids if $asset_ids;
1424    for my $key (
1425        qw(files assets last redirect is_dirty is_asset objects_json deferred_json)
1426      )
1427    {
1428        $param->{$key} = $q->param($key) if $q->param($key);
1429    }
1430    $param->{name} = $q->param('current_file');
1431    $param->{screen_id} = "adjust-sitepath";
1432    $app->load_tmpl( 'dialog/adjust_sitepath.tmpl', $param );
1433}
1434
1435sub convert_to_html {
1436    my $app    = shift;
1437    my $format = $app->param('format');
1438    my $text   = $app->param('text');
1439    # XMLHttpRequest always send text in UTF-8... right?
1440    if ( defined $text ) {
1441        $text = encode_text($text, 'utf-8', $app->config->PublishCharset);
1442    } 
1443    else {
1444        $text = '' ;
1445    }
1446    my $text_more = $app->param('text_more');
1447    if ( defined $text_more ) {
1448        $text_more = encode_text($text_more, 'utf-8', $app->config->PublishCharset);
1449    } 
1450    else {
1451        $text_more = '' ;
1452    }
1453    my $result = {
1454        text      => $app->apply_text_filters( $text,      [$format] ),
1455        text_more => $app->apply_text_filters( $text_more, [$format] ),
1456        format    => $format,
1457    };
1458    return $app->json_result($result);
1459}
1460
1461sub update_list_prefs {
1462    my $app   = shift;
1463    my $prefs = $app->list_pref( $app->param('_type') );
1464    $app->call_return;
1465}
1466
1467sub recover_passwords {
1468    my $app = shift;
1469    my @id  = $app->param('id');
1470
1471    return $app->errtrans("Permission denied.")
1472      unless $app->user->is_superuser();
1473
1474    my $class = ref $app eq 'MT::App::Upgrader' ? 'MT::BasicAuthor' : $app->model('author');
1475    eval "use $class;";
1476
1477    my @msg_loop;
1478    foreach (@id) {
1479        my $author = $class->load($_)
1480            or next;
1481        my ( $rc, $res ) = reset_password( $app, $author, $author->hint );
1482        push @msg_loop, { message => $res };
1483    }
1484
1485    $app->load_tmpl( 'recover_password_result.tmpl',
1486        { message_loop => \@msg_loop, return_url => $app->return_uri } );
1487}
1488
1489sub reset_password {
1490    my $app      = shift;
1491    my ($author) = $_[0];
1492    my $hint     = $_[1];
1493    my $name     = $_[2];
1494
1495    require MT::Auth;
1496    require MT::Log;
1497    if ( !MT::Auth->can_recover_password ) {
1498        $app->log(
1499            {
1500                message => $app->translate(
1501"Invalid password recovery attempt; can't recover password in this configuration"
1502                ),
1503                level    => MT::Log::SECURITY(),
1504                class    => 'system',
1505                category => 'recover_password',
1506            }
1507        );
1508        return ( 0,
1509            $app->translate("Can't recover password in this configuration") );
1510    }
1511
1512    $app->log(
1513        {
1514            message => $app->translate(
1515                "Invalid user name '[_1]' in password recovery attempt", $name
1516            ),
1517            level    => MT::Log::SECURITY(),
1518            class    => 'system',
1519            category => 'recover_password',
1520        }
1521      ),
1522      return ( 0, $app->translate("User name or password hint is incorrect.") )
1523      unless $author;
1524    return ( 0,
1525        $app->translate("User has not set pasword hint; cannot recover password")
1526    ) if ( $hint && !$author->hint );
1527
1528    $app->log(
1529        {
1530            message => $app->translate(
1531                "Invalid attempt to recover password (used hint '[_1]')",
1532                $hint
1533            ),
1534            level    => MT::Log::SECURITY(),
1535            class    => 'system',
1536            category => 'recover_password'
1537        }
1538      ),
1539      return ( 0, $app->translate("User name or password hint is incorrect.") )
1540      unless $author->hint eq $hint;
1541
1542    return ( 0, $app->translate("User does not have email address") )
1543      unless $author->email;
1544
1545    my @pool = ( 'a' .. 'z', 0 .. 9 );
1546    my $pass = '';
1547    for ( 1 .. 8 ) { $pass .= $pool[ rand @pool ] }
1548    $author->set_password($pass);
1549    $author->save;
1550    my $message =
1551      $app->translate(
1552"Password was reset for user '[_1]' (user #[_2]). Password was sent to the following address: [_3]",
1553        $author->name, $author->id, $author->email );
1554    $app->log(
1555        {
1556            message  => $message,
1557            level    => MT::Log::SECURITY(),
1558            class    => 'system',
1559            category => 'recover_password'
1560        }
1561    );
1562
1563    my $address =
1564      defined $author->nickname
1565      ? $author->nickname . ' <' . $author->email . '>'
1566      : $author->email;
1567    my %head = (
1568        id      => 'recover_password',
1569        To      => $address,
1570        From    => $app->config('EmailAddressMain') || $address,
1571        Subject => $app->translate("Password Recovery")
1572    );
1573    my $charset = $app->charset;
1574    my $mail_enc = uc( $app->config('MailEncoding') || $charset );
1575    $head{'Content-Type'} = qq(text/plain; charset="$mail_enc");
1576
1577    my $body = $app->build_email( 'recover-password.tmpl',
1578        { user_password => $pass, link_to_login => $app->base . $app->mt_uri } 
1579    );
1580    $body = wrap_text( $body, 72 );
1581    require MT::Mail;
1582    MT::Mail->send( \%head, $body )
1583      or return (
1584        0,
1585        $app->translate(
1586            "Error sending mail ([_1]); please fix the problem, then "
1587              . "try again to recover your password.",
1588            MT::Mail->errstr
1589        )
1590      );
1591    ( 1, $message );
1592}
1593
1594sub restore_file {
1595    my $app = shift;
1596    my ( $fh, $errormsg ) = @_;
1597    my $q = $app->param;
1598    my $schema_version = $app->config->SchemaVersion;
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 = $app->config->SchemaVersion;
1658    #my $schema_version =
1659    #  $q->param('ignore_schema_conflict')
1660    #  ? 'ignore'
1661    #  : $app->config('SchemaVersion');
1662
1663    my $overwrite_template = $q->param('overwrite_global_templates') ? 1 : 0;
1664
1665    my @errors;
1666    my %error_assets;
1667    require MT::BackupRestore;
1668    my ( $deferred, $blogs, $assets ) =
1669      MT::BackupRestore->restore_directory( $dir, \@errors, \%error_assets,
1670        $schema_version, $overwrite_template, sub { _progress( $app, @_ ); } );
1671
1672    if ( scalar @errors ) {
1673        $$error = $app->translate('Error occured during restore process.');
1674        $app->log(
1675            {
1676                message  => $$error,
1677                level    => MT::Log::WARNING(),
1678                class    => 'system',
1679                category => 'restore',
1680                metadata => join( '; ', @errors ),
1681            }
1682        );
1683    }
1684    return ( $blogs, $assets ) unless ( defined($deferred) && %$deferred );
1685
1686    if ( scalar( keys %error_assets ) ) {
1687        my $data;
1688        while ( my ( $key, $value ) = each %error_assets ) {
1689            $data .=
1690              $app->translate( 'MT::Asset#[_1]: ', $key ) . $value . "\n";
1691        }
1692        my $message = $app->translate('Some of files could not be restored.');
1693        $app->log(
1694            {
1695                message  => $message,
1696                level    => MT::Log::WARNING(),
1697                class    => 'system',
1698                category => 'restore',
1699                metadata => $data,
1700            }
1701        );
1702        $$error .= $message;
1703    }
1704
1705    if ( scalar( keys %$deferred ) ) {
1706        _log_dirty_restore( $app, $deferred );
1707        my $log_url = $app->uri( mode => 'view_log', args => {} );
1708        $$error = $app->translate(
1709'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>.',
1710            $log_url
1711        );
1712        return ( $blogs, $assets );
1713    }
1714
1715    return ( $blogs, $assets ) if $$error;
1716
1717    $app->log(
1718        {
1719            message => $app->translate(
1720"Successfully restored objects to Movable Type system by user '[_1]'",
1721                $app->user->name
1722            ),
1723            level    => MT::Log::INFO(),
1724            class    => 'system',
1725            category => 'restore'
1726        }
1727    );
1728    return ( $blogs, $assets );
1729}
1730
1731sub restore_upload_manifest {
1732    my $app  = shift;
1733    my ($fh) = @_;
1734    my $user = $app->user;
1735    return $app->errtrans("Permission denied.") if !$user->is_superuser;
1736    $app->validate_magic() or return;
1737
1738    my $q = $app->param;
1739
1740    require MT::BackupRestore;
1741    my $backups = MT::BackupRestore->process_manifest($fh);
1742    return $app->errtrans(
1743        "Uploaded file was not a valid Movable Type backup manifest file.")
1744      if !defined($backups);
1745
1746    my $files     = $backups->{files};
1747    my $assets    = $backups->{assets};
1748    my $file_next = shift @$files if defined($files) && scalar(@$files);
1749    my $assets_json;
1750    my $param = {};
1751
1752    if ( !defined($file_next) ) {
1753        if ( scalar(@$assets) > 0 ) {
1754            my $asset = shift @$assets;
1755            $file_next = $asset->{name};
1756            $param->{is_asset} = 1;
1757        }
1758    }
1759    require JSON;
1760    $assets_json = encode_url( JSON::objToJson($assets) )
1761      if scalar(@$assets) > 0;
1762    $param->{files}       = join( ',', @$files );
1763    $param->{assets}      = $assets_json;
1764    $param->{filename}    = $file_next;
1765    $param->{last}        = scalar(@$files) ? 0 : ( scalar(@$assets) ? 0 : 1 );
1766    $param->{open_dialog} = 1;
1767    $param->{schema_version} = $app->config->SchemaVersion;
1768    #$param->{schema_version} =
1769    #  $q->param('ignore_schema_conflict')
1770    #  ? 'ignore'
1771    #  : $app->config('SchemaVersion');
1772    $param->{overwrite_templates} = $q->param('overwrite_global_templates') ? 1 : 0;
1773
1774    $param->{dialog_mode} = 'dialog_restore_upload';
1775    $param->{dialog_params} =
1776        'start=1'
1777      . '&amp;magic_token='
1778      . $app->current_magic
1779      . '&amp;files='
1780      . $param->{files}
1781      . '&amp;assets='
1782      . $param->{assets}
1783      . '&amp;current_file='
1784      . $param->{filename}
1785      . '&amp;last='
1786      . $param->{'last'}
1787      . '&amp;schema_version='
1788      . $param->{schema_version}
1789      . '&amp;overwrite_templates='
1790      . $param->{overwrite_templates}
1791      . '&amp;redirect=1';
1792    $app->load_tmpl( 'restore.tmpl', $param );
1793
1794    #close $fh if $fh;
1795}
1796
1797sub _backup_finisher {
1798    my $app = shift;
1799    my ( $fnames, $param ) = @_;
1800    unless ( ref $fnames ) {
1801        $fnames = [$fnames];
1802    }
1803    $param->{filename}       = $fnames->[0];
1804    $param->{backup_success} = 1;
1805    require MT::Session;
1806    MT::Session->remove( { kind => 'BU' } );
1807    foreach my $fname (@$fnames) {
1808        my $sess = MT::Session->new;
1809        $sess->id( $app->make_magic_token() );
1810        $sess->kind('BU');    # BU == Backup
1811        $sess->name($fname);
1812        $sess->start(time);
1813        $sess->save;
1814    }
1815    my $message;
1816    if ( my $blog_id = $param->{blog_id} || $param->{blog_ids} ) {
1817        $message = $app->translate(
1818            "Blog(s) (ID:[_1]) was/were successfully backed up by user '[_2]'",
1819            $blog_id, $app->user->name
1820        );
1821    }
1822    else {
1823        $message =
1824          $app->translate(
1825            "Movable Type system was successfully backed up by user '[_1]'",
1826            $app->user->name );
1827    }
1828    $app->log(
1829        {
1830            message  => $message,
1831            level    => MT::Log::INFO(),
1832            class    => 'system',
1833            category => 'restore'
1834        }
1835    );
1836    $app->print( $app->build_page( 'include/backup_end.tmpl', $param ) );
1837}
1838
1839sub _progress {
1840    my $app = shift;
1841    my $ids = $app->request('progress_ids') || {};
1842
1843    my ( $str, $id ) = @_;
1844    if ( $id && $ids->{$id} ) {
1845        my $str_js = encode_js($str);
1846        $app->print(
1847qq{<script type="text/javascript">progress('$str_js', '$id');</script>}
1848        );
1849    }
1850    elsif ($id) {
1851        $ids->{$id} = 1;
1852        $app->print(qq{\n<span id="$id">$str</span>});
1853    }
1854    else {
1855        $app->print("<span>$str</span>");
1856    }
1857
1858    $app->request( 'progress_ids', $ids );
1859}
1860
1861sub _log_dirty_restore {
1862    my $app = shift;
1863    my ($deferred) = @_;
1864    my %deferred_by_class;
1865    for my $key ( keys %$deferred ) {
1866        my ( $class, $id ) = split( '#', $key );
1867        if ( exists $deferred_by_class{$class} ) {
1868            push @{ $deferred_by_class{$class} }, $id;
1869        }
1870        else {
1871            $deferred_by_class{$class} = [$id];
1872
1873        }
1874    }
1875    while ( my ( $class_name, $ids ) = each %deferred_by_class ) {
1876        my $message = $app->translate(
1877'Some [_1] were not restored because their parent objects were not restored.',
1878            $class_name
1879        );
1880        $app->log(
1881            {
1882                message  => $message,
1883                level    => MT::Log::WARNING(),
1884                class    => 'system',
1885                category => 'restore',
1886                metadata => join( ', ', @$ids ),
1887            }
1888        );
1889    }
1890    1;
1891}
1892
18931;
Note: See TracBrowser for help on using the browser.