root/branches/release-34/lib/MT/CMS/Tools.pm @ 1823

Revision 1823, 59.5 kB (checked in by takayama, 20 months ago)

Fixed BugId:67959
* Added check for result of object loading

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