root/branches/release-35/lib/MT/CMS/Tools.pm @ 1902

Revision 1902, 59.5 kB (checked in by fumiakiy, 20 months ago)

Restore userpic association. BugId:75025

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