Changeset 951

Show
Ignore:
Timestamp:
12/19/06 11:42:50 (2 years ago)
Author:
fumiakiy
Message:

* Restore now uses XML::SAX. mt-check and mt-wizard will now see XML::SAX. BugId: 45877
** Backup format has been changed so it will go better with SAX callbacks.
* Backup/Restore specific code has been removed from Object.pm and added to BackupRestore.pm for delay-loading. BugId: 45876
* Implemented weblog-level backup. Note a weblog administrator can backup weblog but can not restore if s/he is not a sysadmin. BugId: 45788

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/wheeljack/extras/examples/plugins/BackupRestoreSample/BackupRestoreSample.pl

    r822 r951  
    2828}); 
    2929MT->add_plugin($plugin); 
    30 MT->add_callback('Backup.role', 9, $plugin, \&backup); 
    31 MT->add_callback('Backup.blog', 9, $plugin, \&backup); 
    32 MT->add_callback('Backup.else', 9, $plugin, \&backup_else); 
    33 MT->add_callback("Restore.role:$ns", 9, $plugin, \&restore); 
    34 MT->add_callback("Restore.blog:$ns", 9, $plugin, \&restore); 
    35 MT->add_callback("Restore.else:$ns", 9, $plugin, \&restore_else); 
     30MT->add_callback('Backup', 9, $plugin, \&backup); 
     31MT->add_callback("Restore.backup_restore_sample_object:$ns", 9, $plugin, \&restore); 
    3632 
    3733sub backup { 
    38     my ($cb, $obj) =@_; 
    39     my $class = ref $obj; 
     34    my ($cb, $blog_ids) = @_; 
    4035    my $xml; 
    4136    my $terms = {}; 
    42      
    43     $terms->{blog_id} = $obj->id if 'MT::Blog' eq ref($obj); 
    44     $terms->{role_id} = $obj->id if 'MT::Role' eq ref($obj); 
     37    $terms->{blog_id} = $blog_ids if defined($blog_ids) && (0 < scalar(@$blog_ids)); 
    4538    my @objects = BackupRestoreSample::Object->load($terms); 
    4639    for my $object (@objects) { 
     
    5043} 
    5144 
    52 sub backup_else { 
    53     my ($cb) = @_; 
    54     return "<foo xmlns='$ns'>barbaz</foo>"; 
     45sub restore { 
     46    my ($cb, $data, $objects, $deferred, $printer) = @_; 
     47    return 0 if $ns ne $data->{NamespaceURI}; 
     48     
     49    my $attrs = $data->{Attributes}; 
     50    my %column_data = map { $attrs->{$_}->{LocalName} => $attrs->{$_}->{Value} } keys(%$attrs); 
     51    my $obj = BackupRestoreSample::Object->new; 
     52    $obj->set_values(\%column_data);  
     53    return $obj; 
    5554} 
    56  
    57 sub restore { 
    58     my ($cb, $xp, $xml_node, $parent, $objects, $deferred, $printer) = @_; 
    59     return 0 if !($xml_node->isa('XML::XPath::Node::Element')); 
    60     my $namespace_node = $xml_node->getNamespace($xml_node->getPrefix); 
    61     return 0 if $ns ne $namespace_node->getExpanded; 
    62     my $error = ''; 
    63     my $obj = BackupRestoreSample::Object->from_xml( 
    64         XPath => $xp, 
    65         XmlNode => $xml_node, 
    66         Objects => $objects, 
    67         Deferred => $deferred, 
    68         Callback => $printer, 
    69         Namespace => $ns, 
    70     ); 
    71     1; 
    72 } 
    73  
    74 sub restore_else { 
    75     my ($cb, $xp, $xml_node, $objects, $deferred, $printer) = @_; 
    76     return 0 if !($xml_node->isa('XML::XPath::Node::Element')); 
    77     my $namespace_node = $xml_node->getNamespace($xml_node->getPrefix); 
    78     return 0 if $ns ne $namespace_node->getExpanded; 
    79     $printer->('restore_else has been called: ' . $xml_node->toString); 
    80     1; 
    81 } 
  • branches/wheeljack/lib/MT/App/CMS.pm

    r947 r951  
    1082310823    my $app = shift; 
    1082410824    my $user = $app->user; 
    10825     return $app->errtrans("Permission denied.") if !$user->is_superuser; 
     10825    my $blog_id = $app->param('blog_id'); 
     10826 
     10827    unless ($user->is_superuser) { 
     10828        return $app->errtrans("Permission denied.") 
     10829            unless defined($blog_id) && $app->{perms}->can_administer_blog; 
     10830    } 
    1082610831 
    1082710832    my %param = (); 
     10833    $param{blog_id} = $blog_id if $blog_id; 
    1082810834    $app->add_breadcrumb($app->translate('Backup & Restore')); 
    10829     $param{system_overview_nav} = 1
     10835    $param{system_overview_nav} = 1 unless $blog_id
    1083010836    $param{nav_backup} = 1; 
    1083110837    my $missing_tgz = 0; 
     
    1084010846    $param{zip} = !$missing_zip; 
    1084110847 
    10842     eval "require XML::XPath"; 
    10843     $param{missing_xpath} = 1 if $@; 
     10848    eval "require XML::SAX"; 
     10849    $param{missing_sax} = 1 if $@; 
    1084410850 
    1084510851    my $limit = $app->config('CGIMaxUpload') || 2048; 
     
    1086910875    my $app = shift; 
    1087010876    my $user = $app->user; 
    10871     return $app->errtrans("Permission denied.") if !$user->is_superuser; 
     10877    my $q = $app->param; 
     10878    my $blog_id = $q->param('blog_id'); 
     10879    unless ($user->is_superuser) { 
     10880        return $app->errtrans("Permission denied.") 
     10881            unless defined($blog_id) && $app->{perms}->can_administer_blog; 
     10882    } 
    1087210883    $app->validate_magic() or return; 
    1087310884     
    10874     my $q = $app->param; 
    10875  
    1087610885    my $what = $q->param('backup_what'); 
    1087710886    return $app->errtrans("You must select what you want to backup.") if !$what; 
     
    1090710916    $app->add_breadcrumb($app->translate('Backup & Restore'), $app->uri(mode => 'backup_restore')); 
    1090810917    $app->add_breadcrumb($app->translate('Backup')); 
    10909     $param->{system_overview_nav} = 1; 
     10918    $param->{system_overview_nav} = 1 if defined($blog_ids) && $blog_ids; 
     10919    $param->{blog_id} = $blog_id if $blog_id; 
    1091010920    $param->{nav_backup} = 1; 
    1091110921 
     
    1098110991        $fh = gensym(); 
    1098210992        open $fh, ">$filename"; 
     10993        my $url = $app->uri . "?__mode=backup_download&name=$file-1.xml&magic_token=" . $app->current_magic; 
     10994        $url .= "&blog_id=$blog_id" if defined($blog_id); 
    1098310995        push @files, {  
    10984             url => $app->uri . "?__mode=backup_download&name=$file-1.xml&magic_token=" . $app->current_magic
     10996            url => $url
    1098510997            filename => $file . "-1.xml" 
    1098610998        }; 
     
    1099311005            $fh = gensym(); 
    1099411006            open $fh, ">$filename"; 
     11007            my $url = $app->uri . "?__mode=backup_download&name=$file-$findex.xml&magic_token=" . $app->current_magic; 
     11008            $url .= "&blog_id=$blog_id" if defined($blog_id); 
    1099511009            push @files, { 
    10996                 url => $app->uri . "?__mode=backup_download&name=$file-$findex.xml&magic_token=" . $app->current_magic
     11010                url => $url
    1099711011                filename => $file . "-$findex.xml" 
    1099811012            }; 
     
    1102311037            print $fh "</manifest>\n"; 
    1102411038            close $fh; 
     11039            my $url = $app->uri . "?__mode=backup_download&name=$file.manifest&magic_token=" . $app->current_magic; 
     11040            $url .= "&blog_id=$blog_id" if defined($blog_id); 
    1102511041            push @files, { 
    11026                 url => $app->uri . "?__mode=backup_download&name=$file.manifest&magic_token=" . $app->current_magic
     11042                url => $url
    1102711043                filename => "$file.manifest" 
    1102811044            }; 
     
    1106711083                $arc->writeToFileHandle($fh_arc); 
    1106811084                close $fh_arc; 
     11085                unlink File::Spec->catfile($temp_dir, $_->{filename}) foreach 
     11086                    grep { !defined($_->{path}) } @files; 
    1106911087                $app->_backup_finisher($fname, $param); 
    1107011088            } 
     
    1107911097    my $app = shift; 
    1108011098    my $user = $app->user; 
    11081     return $app->errtrans("Permission denied.") if !$user->is_superuser; 
     11099    my $blog_id = $app->param('blog_id'); 
     11100    unless ($user->is_superuser) { 
     11101        return $app->errtrans("Permission denied.") 
     11102            unless defined($blog_id) && $app->{perms}->can_administer_blog; 
     11103    } 
    1108211104    $app->validate_magic() or return; 
    1108311105    my $filename = $app->param('filename'); 
     
    1118011202        $result = $app->restore_directory($dir, \$error); 
    1118111203    } else { 
     11204        $param->{restore_upload} = 1; 
    1118211205        my $uploaded = $q->param('file'); 
    1118311206        my ($volume, $directories, $uploaded_filename) = File::Spec->splitpath($uploaded) if defined($uploaded); 
    1118411207        if ($uploaded_filename =~ /^.+\.xml$/i) { 
    11185             $param->{restore_upload} = 1; 
    1118611208            $result = $app->restore_file($fh, \$error); 
    1118711209        } elsif ($uploaded_filename =~ /^.+\.tar(\.gz)?$/i) { 
     
    1125711279            $result = 0; 
    1125811280            $error = $app->translate('Upload manifest file via the other form.'); 
     11281        } else { 
     11282            $result = 0; 
     11283            $error = $app->translate('Please use xml, tar.gz, zip, or manifest as a file extension.'); 
    1125911284        } 
    1126011285    } 
     
    1129211317        return 0; 
    1129311318    } 
     11319    if ($errormsg) { 
     11320        $app->log({ 
     11321            message => $errormsg, 
     11322            level => MT::Log::ERROR(), 
     11323            class => 'system', 
     11324            category => 'restore', 
     11325        }); 
     11326       return 0; 
     11327    } 
    1129411328 
    1129511329    $app->log({ 
     
    1131211346    } 
    1131311347 
     11348    my @errors; 
    1131411349    my %error_assets; 
    1131511350    require MT::BackupRestore; 
    1131611351    my $deferred = MT::BackupRestore->restore_directory( 
    11317         $dir, $error, \%error_assets, sub { $app->print(@_); }); 
    11318  
    11319     if (!defined($deferred) && $$error) { 
     11352        $dir, \@errors, \%error_assets, sub { $app->print(@_); }); 
     11353 
     11354    if (!defined($deferred) && scalar(@errors)) { 
     11355        $$error = join('; ', @errors); 
    1132011356        return 0; 
    1132111357    } 
     
    1138911425    return $app->errtrans("No manifest file was uploaded.") if $no_upload; 
    1139011426 
     11427    require MT::BackupRestore; 
     11428    my $backups = MT::BackupRestore->process_manifest($fh); 
     11429    return $app->errtrans("Uploaded file was not a valid Movable Type backup manifest file.") if !defined($backups); 
     11430 
     11431    my $files = $backups->{files}; 
     11432    my $assets = $backups->{assets}; 
     11433    my $file_next = shift @$files if defined($files) && scalar(@$files); 
     11434    my $assets_json; 
    1139111435    my $param = {}; 
    1139211436 
    11393     require MT::BackupRestore; 
    11394     my $error = MT::BackupRestore->restore_upload_manifest($fh, $param); 
    11395     return $app->error($error) if $error; 
    11396      
     11437    if (!defined($file_next)) { 
     11438        if (scalar(@$assets) > 0) { 
     11439            my $asset = shift @$assets; 
     11440            $file_next = $asset->{name}; 
     11441            $param->{is_asset} = 1; 
     11442        } 
     11443    } 
     11444    require JSON; 
     11445    require MT::Util; 
     11446    $assets_json = MT::Util::encode_html(JSON::objToJson($assets)) if scalar(@$assets) > 0; 
     11447    $param->{files} = join(',', @$files); 
     11448    $param->{assets} = $assets_json; 
     11449    $param->{filename} = $file_next; 
     11450    $param->{last} = scalar(@$files) ? 0 : (scalar(@$assets) ? 0 : 1); 
    1139711451    $param->{open_dialog} = 1; 
    1139811452    $app->build_page('backup_restore.tmpl', $param); 
     
    1144611500 
    1144711501    my $uploaded = $q->param('file'); 
    11448     $uploaded =~ s!\\!/!g;   ## Change backslashes to forward slashes 
    11449     my ($volume, $directories, $uploaded_filename) = File::Spec->splitpath($uploaded) if defined($uploaded); 
    11450     if (defined($uploaded) && ($current ne $uploaded_filename)) { 
    11451         close $fh; 
    11452         $param->{error} = $app->translate('Please upload [_1] in this page.', $current); 
    11453         return $app->build_page('dialog_restore_upload.tmpl', $param); 
     11502    if (defined($uploaded)) { 
     11503        $uploaded =~ s!\\!/!g;   ## Change backslashes to forward slashes 
     11504        my ($volume, $directories, $uploaded_filename) = File::Spec->splitpath($uploaded); 
     11505        if ($current ne $uploaded_filename) { 
     11506            close $fh; 
     11507            $param->{error} = $app->translate('Please upload [_1] in this page.', $current); 
     11508            return $app->build_page('dialog_restore_upload.tmpl', $param); 
     11509        } 
    1145411510    } 
    1145511511 
     
    1145811514        return $app->build_page('dialog_restore_upload.tmpl', $param); 
    1145911515    } 
    11460  
    11461     my $error; 
    11462     my @obj_to_restore = (    ## Beware the order of keys is important. 
    11463         {tag => 'MT::Tag'}, 
    11464         {author => 'MT::Author'}, 
    11465         {blog => 'MT::Blog'}, 
    11466         {template => 'MT::Template'}, 
    11467         {role => 'MT::Role'}, 
    11468         {category => 'MT::Category'}, 
    11469         {asset => 'MT::Asset'}, 
    11470         {entry => 'MT::Entry'}, 
    11471         {trackback => 'MT::Trackback'}, 
    11472         {comment => 'MT::Comment'}, 
    11473     ); 
    1147411516  
    1147511517    $app->{no_print_body} = 1; 
     
    1151111553    } 
    1151211554 
    11513     my $assets = JSON::jsonToObj(MT::Util::decode_html($assets_json)) if defined($assets_json); 
     11555    my $assets = JSON::jsonToObj(MT::Util::decode_html($assets_json)) if (defined($assets_json) && $assets_json); 
     11556    $assets = [] if !defined($assets); 
    1151411557    my $asset; 
     11558    my @errors; 
    1151511559    require MT::BackupRestore; 
    1151611560    if ($is_asset) { 
     
    1152911573    } else { 
    1153011574        MT::BackupRestore->restore_process_single_file( 
    11531             $fh, \@obj_to_restore, $objects, $deferred, \$error, sub { $app->print(@_) }); 
     11575            $fh, $objects, $deferred, \@errors, sub { $app->print(@_) }); 
    1153211576    } 
    1153311577 
     
    1154511589    $param->{assets} = MT::Util::encode_html(JSON::objToJson($assets)); 
    1154611590    $param->{name} = $file_next; 
    11547     $param->{last} = (scalar(@files) || (scalar(@$assets) - 1)) ? 0 : 1; 
     11591    if (0 < scalar(@files)) { 
     11592        $param->{last} = 0; 
     11593    } elsif (0 >= scalar(@$assets) - 1) { 
     11594        $param->{last} = 1; 
     11595    } else { 
     11596        $param->{last} = 0; 
     11597    } 
    1154811598    $param->{is_dirty} = scalar(keys %$deferred); 
    1154911599    if ($last) { 
     
    1157211622                category => 'restore' 
    1157311623            }); 
     11624            $param->{ok_url} = $app->uri(mode => 'backup_restore', args => {}); 
    1157411625        } 
    1157511626    } else { 
     
    1157911630        $param->{deferred_json} = JSON::objToJson($deferred); 
    1158011631     
    11581         $param->{error} = $error if $error
     11632        $param->{error} = join('; ', @errors)
    1158211633        $param->{next_mode} = 'dialog_restore_upload'; 
    1158311634    } 
  • branches/wheeljack/lib/MT/App/Wizard.pm

    r903 r951  
    3939    [ 'IO::Uncompress::Gunzip', 0, 0, 'IO::Uncompress::Gunzip is required in order to decompress files in backup/restore operation.', 'IO Uncompress Gunzip'], 
    4040    [ 'Archive::Zip', 0, 0, 'Archive::Zip is required in order to archive files in backup/restore operation.', 'Archive Zip'], 
    41     [ 'XML::XPath', 0, 0, 'XML::XPath and/or its dependencies is required in order to restore from multiple files.', 'XML XPath'], 
     41    [ 'XML::SAX', 0, 0, 'XML::SAX and/or its dependencies is required in order to restore.', 'XML SAX'], 
    4242); 
    4343 
  • branches/wheeljack/lib/MT/BackupRestore.pm

    r910 r951  
    4343            args => undef 
    4444            }}; 
     45        push @$obj_to_backup, {'MT::Notification' => { 
     46            term => { 'blog_id' => $blog_ids },  
     47            args => undef 
     48            }}; 
    4549        push @$obj_to_backup, {'MT::Template' => {  
    4650            term => { 'id' => $blog_ids },  
     51            args => undef 
     52            }}; 
     53        push @$obj_to_backup, {'MT::TemplateMap' => { 
     54            term => { 'blog_id' => $blog_ids },  
    4755            args => undef 
    4856            }}; 
     
    5260                [ 'MT::Association', 'role_id', { blog_id => $blog_ids }, undef ] 
    5361            }}}; 
     62        push @$obj_to_backup, {'MT::Association' => { 
     63            term => { 'blog_id' => $blog_ids },  
     64            args => undef 
     65            }}; 
     66        push @$obj_to_backup, {'MT::Permission' => { 
     67            term => { 'blog_id' => $blog_ids },  
     68            args => undef 
     69            }}; 
    5470        push @$obj_to_backup, {'MT::Category' => { 
    5571            term => { 'blog_id' => $blog_ids },  
     
    6480            args => undef 
    6581            }}; 
     82        push @$obj_to_backup, {'MT::FileInfo' => { 
     83            term => { 'blog_id' => $blog_ids },  
     84            args => undef 
     85            }}; 
     86        push @$obj_to_backup, {'MT::ObjectTag' => { 
     87            term => { 'blog_id' => $blog_ids },  
     88            args => undef 
     89            }}; 
     90        push @$obj_to_backup, {'MT::Placement' => { 
     91            term => { 'blog_id' => $blog_ids },  
     92            args => undef 
     93            }}; 
    6694        push @$obj_to_backup, {'MT::Trackback' => { 
    6795            term => { 'blog_id' => $blog_ids },  
    6896            args => undef 
    6997            }}; 
     98        push @$obj_to_backup, {'MT::TBPing' => { 
     99            term => { 'blog_id' => $blog_ids },  
     100            args => undef 
     101            }}; 
    70102        push @$obj_to_backup, {'MT::Comment' => { 
    71103            term => { 'blog_id' => $blog_ids },  
    72104            args => undef 
    73105            }}; 
     106        push @$obj_to_backup, {'MT::PluginData' => { term => undef, args => undef }}; 
    74107    } else { 
    75108        push @$obj_to_backup, {'MT::Tag' => { term => undef, args => undef }}; 
    76109        push @$obj_to_backup, {'MT::Author' => { term => undef, args => undef }}; 
    77110        push @$obj_to_backup, {'MT::Blog' => { term => undef, args => undef }}; 
     111        push @$obj_to_backup, {'MT::Notification' => { term => undef, args => undef }}; 
    78112        push @$obj_to_backup, {'MT::Template' => { term => undef, args => undef }}; 
     113        push @$obj_to_backup, {'MT::TemplateMap' => { term => undef, args => undef }}; 
    79114        push @$obj_to_backup, {'MT::Role' => { term => undef, args => undef }}; 
     115        push @$obj_to_backup, {'MT::Association' => { term => undef, args => undef }}; 
     116        push @$obj_to_backup, {'MT::Permission' => { term => undef, args => undef }}; 
    80117        push @$obj_to_backup, {'MT::Category' => { term => undef, args => undef }}; 
    81118        push @$obj_to_backup, {'MT::Asset' => { term => undef, args => undef }}; 
    82119        push @$obj_to_backup, {'MT::Entry' => { term => undef, args => undef }}; 
     120        push @$obj_to_backup, {'MT::FileInfo' => { term => undef, args => undef }}; 
     121        push @$obj_to_backup, {'MT::ObjectTag' => { term => undef, args => undef }}; 
     122        push @$obj_to_backup, {'MT::Placement' => { term => undef, args => undef }}; 
    83123        push @$obj_to_backup, {'MT::Trackback' => { term => undef, args => undef }}; 
     124        push @$obj_to_backup, {'MT::TBPing' => { term => undef, args => undef }}; 
    84125        push @$obj_to_backup, {'MT::Comment' => { term => undef, args => undef }}; 
     126        push @$obj_to_backup, {'MT::PluginData' => { term => undef, args => undef }}; 
     127        $blog_ids = []; 
    85128    } 
    86129 
     
    93136        $printer, $splitter, $finisher, $size, $obj_to_backup, $files); 
    94137 
    95     my $else_xml = MT->run_callbacks('Backup.else'); 
     138    my $else_xml = MT->run_callbacks('Backup', $blog_ids); 
    96139    $printer->($else_xml, q()) if $else_xml ne '1'; 
    97140 
     
    151194    my ($fh, $errormsg, $callback) = @_; 
    152195 
    153     my $xp = _process_backup_header($fh); 
    154     if (!defined($xp)) { 
    155         $$errormsg = MT->translate('Uploaded file was not a valid Movable Type backup file.'); 
    156         return undef; 
    157     } 
    158  
    159     my @obj_to_restore = (    ## Beware the order of keys is important. 
    160         {tag => 'MT::Tag'}, 
    161         {author => 'MT::Author'}, 
    162         {blog => 'MT::Blog'}, 
    163         {template => 'MT::Template'}, 
    164         {role => 'MT::Role'}, 
    165         {category => 'MT::Category'}, 
    166         {asset => 'MT::Asset'}, 
    167         {entry => 'MT::Entry'}, 
    168         {trackback => 'MT::Trackback'}, 
    169         {comment => 'MT::Comment'}, 
     196    my $objects = {}; 
     197    my $deferred = {}; 
     198    my $errors = []; 
     199 
     200    $class->restore_process_single_file( 
     201        $fh, $objects, $deferred, $errors, $callback 
    170202    ); 
    171     my %objects; 
    172     my $deferred = {}; 
    173     my $error; 
    174  
    175     my $result = __PACKAGE__->restore_process_single_file( 
    176         $fh, \@obj_to_restore, \%objects, $deferred, \$error, $callback); 
    177  
     203    $$errormsg = join('; ', @$errors); 
    178204    $deferred; 
    179205} 
    180  
    181 sub _process_backup_header { 
    182     my ($fh) = @_; 
    183  
    184     if ((ref($fh) eq 'Fh') || (ref($fh) eq 'GLOB')){ 
    185         seek($fh, 0, 0) or return undef; 
    186         require XML::XPath; 
    187         my $xp = XML::XPath->new($fh) or return undef; 
    188         my $root; 
    189         eval { 
    190             $root = $xp->find("/*[local-name()='movabletype'][1]"); 
    191         }; 
    192         return undef if $@; 
    193         if ($root && ('movabletype' eq $root->get_node(1)->getLocalName)) { 
    194             return $xp; 
    195         } 
    196     } 
    197     return undef; 
    198  
    199 
    200  
     206  
    201207sub restore_process_single_file { 
    202208    my $class = shift; 
    203     my ($fh, $obj_to_restore, $objects, $deferred, $error, $callback) = @_; 
    204      
    205     my $xp = _process_backup_header($fh); 
    206     if (!defined($xp)) { 
    207         $$error = MT->translate('Uploaded file was not a valid Movable Type backup file.'); 
    208         return 0; 
    209     } 
    210      
    211     my $root_path = "/*[local-name()='movabletype']"; 
    212     for my $name_hash (@$obj_to_restore) { 
    213         my @keys = keys %$name_hash; 
    214         my $name = $keys[0]; 
    215         my $nodeset = $xp->find("$root_path/*[local-name()='$name']"); 
    216         for my $index (1..$nodeset->size()) { 
    217             my $node = $nodeset->get_node($index); 
    218             next if !($node->isa('XML::XPath::Node::Element')); 
    219  
    220             my $ns = $node->getNamespace($node->getPrefix); 
    221             next if ($ns && (NS_MOVABLETYPE ne $ns->getExpanded)); 
    222  
    223             my $class = $name_hash->{$node->getLocalName}; 
    224             eval "require $class;"; 
    225             $class->from_xml( 
    226                 XPath => $xp, 
    227                 XmlNode => $node,  
    228                 Objects => $objects,  
    229                 Deferred => $deferred, 
    230                 Error => $error,  
    231                 Callback => $callback, 
    232                 Namespace => NS_MOVABLETYPE, 
    233             ); 
    234         } 
    235     } 
    236      
    237     my $extension_set = $xp->find("$root_path/*"); 
    238     for my $index2 (1..$extension_set->size()) { 
    239         my $ext_node = $extension_set->get_node($index2); 
    240         my $ext_ns = $ext_node->getNamespace($ext_node->getPrefix); 
    241         next if ($ext_ns && (NS_MOVABLETYPE eq $ext_ns->getExpanded)); 
    242          
    243         MT->run_callbacks('Restore.else:' . $ext_ns->getExpanded,  
    244             $xp, $ext_node, $objects, $deferred, $callback); 
    245     } 
     209    my ($fh, $objects, $deferred, $errors, $callback) = @_; 
     210 
     211    require XML::SAX; 
     212    require MT::BackupRestore::BackupFileHandler; 
     213    my $handler = MT::BackupRestore::BackupFileHandler->new( 
     214        callback => $callback, 
     215        objects => $objects, 
     216        deferred => $deferred, 
     217        errors => $errors, 
     218    ); 
     219 
     220    my $parser = XML::SAX::ParserFactory->parser( 
     221        Handler => $handler, 
     222    ); 
     223    my $e; 
     224    eval { $parser->parse_file($fh); }; 
     225    $e = $@ if $@; 
     226    if ($e) { 
     227        push @$errors, $e; 
     228        $callback->($e); 
     229    } 
     230    1; 
    246231} 
    247232 
    248233sub restore_directory { 
    249234    my $class = shift; 
    250     my ($dir, $error, $error_assets, $callback) = @_; 
     235    my ($dir, $errors, $error_assets, $callback) = @_; 
    251236 
    252237    my $manifest; 
    253238    my @files; 
    254     opendir my $dh, $dir or $$error = MT->translate("Can't open directory '[_1]': [_2]", $dir, "$!"), return undef; 
     239    opendir my $dh, $dir or push(@$errors, MT->translate("Can't open directory '[_1]': [_2]", $dir, "$!")), return undef; 
    255240    for my $f (readdir $dh) { 
    256241        next if $f !~ /^.+\.manifest$/i; 
     
    260245    closedir $dh; 
    261246    unless ($manifest) { 
    262         $$error = MT->translate("No manifest file could be found in your import directory [_1].", $dir); 
     247        push @$errors, MT->translate("No manifest file could be found in your import directory [_1].", $dir); 
    263248        return undef; 
    264249    } 
    265250 
    266251    my $fh = gensym; 
    267     open $fh, "<$manifest" or $$error = MT->translate('[_1] cannot open.'), return 0; 
    268     my $backups = _process_manifest($fh); 
     252    open $fh, "<$manifest" or push(@$errors, MT->translate("Can't open [_1].", $manifest)), return 0; 
     253    my $backups = __PACKAGE__->process_manifest($fh); 
    269254    close $fh; 
    270255    unless($backups) { 
    271         $$error = MT->translate("Manifest file [_1] was not a valid Movable Type backup manifest file.", $manifest); 
     256        push @$errors, MT->translate("Manifest file [_1] was not a valid Movable Type backup manifest file.", $manifest); 
    272257        return undef; 
    273258    } 
     
    275260    $callback->(MT->translate("Manifest file: [_1]\n", $manifest)); 
    276261 
    277     my @obj_to_restore = (    ## Beware the order of keys is important. 
    278         {tag => 'MT::Tag'}, 
    279         {author => 'MT::Author'}, 
    280         {blog => 'MT::Blog'}, 
    281         {template => 'MT::Template'}, 
    282         {role => 'MT::Role'}, 
    283         {category => 'MT::Category'}, 
    284         {asset => 'MT::Asset'}, 
    285         {entry => 'MT::Entry'}, 
    286         {trackback => 'MT::Trackback'}, 
    287         {comment => 'MT::Comment'}, 
    288     ); 
    289262    my %objects; 
    290263    my $deferred = {}; 
     
    294267        my $fh = gensym; 
    295268        my $filepath = File::Spec->catfile($dir, $file); 
    296         open $fh, "<$filepath" or $$error = MT->translate('[_1] cannot open.'), return undef; 
    297         my $xp = _process_backup_header($fh); 
    298         if (!defined($xp)) { 
    299             $$error = MT->translate('The file [_1] was not a valid Movable Type backup file.', $filepath); 
    300             return undef; 
    301         } 
     269        open $fh, "<$filepath" or push @$errors, MT->translate('[_1] cannot open.'), next; 
    302270 
    303271        my $result = __PACKAGE__->restore_process_single_file( 
    304             $fh, \@obj_to_restore, \%objects, $deferred, $error, $callback); 
     272            $fh, \%objects, $deferred, $errors, $callback); 
    305273 
    306274        close $fh; 
     
    311279} 
    312280 
    313 sub _process_manifest { 
     281sub process_manifest { 
     282    my $class = shift; 
    314283    my ($stream) = @_; 
    315284 
    316285    if ((ref($stream) eq 'Fh') || (ref($stream) eq 'GLOB')){ 
    317286        seek($stream, 0, 0) or return undef; 
    318         require XML::XPath; 
    319         my $xp = XML::XPath->new($stream) or return undef; 
    320         my $root; 
    321         eval { 
    322             $root = $xp->find("/*[local-name()='manifest'][1]"); 
    323         }; 
    324         return undef if $@; 
    325         if ($root && ('manifest' eq $root->get_node(1)->getLocalName)) { 
    326             my $backups = { 
    327                 files => [], 
    328                 assets => [], 
    329             }; 
    330             my $nodeset = $xp->find("*", $root->get_node(1)); 
    331             for my $index (1..$nodeset->size()) { 
    332                 my $node = $nodeset->get_node($index); 
    333                 next if !($node->isa('XML::XPath::Node::Element')); 
    334                 if ('backup' eq $node->getAttribute('type')) { 
    335                     push @{$backups->{files}}, $node->getAttribute('name'); 
    336                 } elsif ('asset' eq $node->getAttribute('type')) { 
    337                     push @{$backups->{assets}}, {  
    338                         name => $node->getAttribute('name'), 
    339                         asset_id => $node->getAttribute('asset_id'), 
    340                     }; 
    341                 } 
    342             } 
    343             return $backups; 
    344         } 
    345         return undef; 
     287        require XML::SAX; 
     288        require MT::BackupRestore::ManifestFileHandler; 
     289        my $handler = MT::BackupRestore::ManifestFileHandler->new(); 
     290 
     291        my $parser = XML::SAX::ParserFactory->parser( 
     292            Handler => $handler, 
     293        ); 
     294        eval { $parser->parse_file($stream); }; 
     295        if (my $e = $@) { 
     296            die $e; 
     297        } 
     298        return $handler->{backups}; 
    346299    } 
    347300    return undef; 
     
    353306 
    354307    my $id = $asset_element->{asset_id}; 
    355     next if !defined($id); 
    356     next if !(exists $objects->{"MT::Asset#$id"}); 
     308    if (!defined($id)) { 
     309        $callback->(MT->translate('ID for the asset was not set.')); 
     310        return 0; 
     311    } 
    357312    my $asset = $objects->{"MT::Asset#$id"}; 
     313    unless (defined($asset)) { 
     314        $asset = $objects->{"MT::Asset::Image#$id"}; 
     315        unless (defined($asset)) { 
     316            $callback->(MT->translate('The asset ([_1]) was not restored.', $id)); 
     317            return 0; 
     318        } 
     319    } 
    358320    my $path = $asset->file_path; 
    359     next if !defined($path); 
     321    unless (defined($path)) { 
     322        $callback->(MT->translate('Path was not found for the asset ([_1]).', $id)); 
     323        return 0; 
     324    } 
    360325    my ($vol, $dir, $fn) = File::Spec->splitpath($path); 
    361326    if (!-w "$vol$dir") { 
     
    392357    } 
    393358    1; 
    394 } 
    395  
    396 sub restore_upload_manifest { 
    397     my $class = shift; 
    398     my ($fh, $param) = @_; 
    399  
    400     my $backups = _process_manifest($fh); 
    401     return MT->translate("Uploaded file was not a valid Movable Type backup manifest file.") if !defined($backups); 
    402  
    403     my $files = $backups->{files}; 
    404     my $assets = $backups->{assets}; 
    405     my $file_next = shift @$files if defined($files) && scalar(@$files); 
    406     my $assets_json; 
    407  
    408     if (!defined($file_next)) { 
    409         if (scalar(@$assets) > 0) { 
    410             my $asset = shift @$assets; 
    411             $file_next = $asset->{name}; 
    412             $param->{is_asset} = 1; 
    413         } 
    414     } 
    415     require JSON; 
    416     require MT::Util; 
    417     $assets_json = MT::Util::encode_html(JSON::objToJson($assets)) if scalar(@$assets) > 0; 
    418     $param->{files} = join(',', @$files); 
    419     $param->{assets} = $assets_json; 
    420     $param->{filename} = $file_next; 
    421     return q(); 
    422359} 
    423360 
     
    489426                push @elements, $name; 
    490427                next; 
    491                 #} elsif (('datetime' eq $coldefs->{$name}{type}) || ('timestamp' eq $coldefs->{$name}{type})) { 
    492                 #    my $ts_iso = MT::Util::ts2iso(undef, $obj->column($name)); 
    493                 #    $ts_iso =~ s/ /T/; 
    494                 #    $xml .= " $name='" . $ts_iso . "'"; 
    495                 #    next; 
    496428            } 
    497429            $xml .= " $name='" . MT::Util::encode_xml($obj->column($name), 1) . "'"; 
     
    500432    $xml .= '>'; 
    501433    $xml .= "<$_>" . MT::Util::encode_xml($obj->column($_), 1) . "</$_>" foreach @elements; 
    502     $xml .= $obj->children_to_xml($namespace, $args); 
    503     my $ext_xml = MT->run_callbacks('Backup.' . $obj->datasource, $obj); 
    504     $xml .= $ext_xml if $ext_xml ne '1'; 
    505434    $xml .= '</' . $obj->datasource . '>'; 
    506435    $xml; 
     
    537466    } 
    538467    (scalar(keys(%$parent_names)) == $done) ? 1 : 0;    
    539 } 
    540  
    541 sub from_xml { 
    542     my $class = shift; 
    543     my (%param) = @_; 
    544     my $xp = $param{XPath}; 
    545     my $element = $param{XmlNode}; 
    546     my $objects = $param{Objects}; 
    547     my $deferred = $param{Deferred}; 
    548     my $error = $param{Error}; 
    549     my $cb = $param{Callback}; 
    550  
    551     require MT::BackupRestore; 
    552     my $namespace = $param{Namespace} || MT::BackupRestore::NS_MOVABLETYPE(); 
    553  
    554     if (ref($class)) { 
    555         $class = ref($class); 
    556     } 
    557  
    558     my $err = $@; 
    559     if (defined($err) && $err) { 
    560         $cb->($err . "\n"); 
    561         return undef; 
    562     } 
    563  
    564     my $obj = $class->new; 
    565     if (!$obj || !($obj->isa('MT::Object'))) { 
    566         $cb->(MT->translate("Invalid XML element to restore: [_1]\n", $class)); 
    567         return undef; 
    568     } 
    569  
    570     my %data; 
    571     my $coldefs = $obj->column_defs; 
    572     my $attributes = $element->getAttributeNodes; 
    573     for my $attribute (@$attributes) { 
    574         my $colname = $attribute->getLocalName; 
    575         #if (('datetime' eq $coldefs->{$colname}{type}) || ('timestamp' eq $coldefs->{$colname}{type})) { 
    576         #    $data{$colname} = MT::Util::iso2ts(undef, $attribute->getNodeValue); 
    577         #} else { 
    578             $data{$colname} = $attribute->getNodeValue; 
    579         #} 
    580     } 
    581  
    582     my $success = 1; 
    583     my $parent_names = $obj->parent_names; 
    584     $success = $obj->restore_parent_ids(\%data, $objects) if scalar(keys %$parent_names); 
    585     if (!$success) { 
    586         $cb->(MT->translate("Restoring [_1] (ID: [_2]) was deferred because its parents objects have not been restored yet.\n", $class, $data{id})); 
    587         $deferred->{$class . '#' . $data{id}} = 1; 
    588         return undef; 
    589     } 
    590  
    591     $cb->(MT->translate("Restoring [_1]...\n", $class)); 
    592  
    593     my @extension_names; 
    594     my $child_element_names = $obj->children_names; 
    595     my $nodeset = $xp->find("*", $element); 
    596     for my $index (1..$nodeset->size()) { 
    597         my $node = $nodeset->get_node($index); 
    598         next if !($node->isa('XML::XPath::Node::Element')); 
    599  
    600         my $ns = $node->getNamespace($node->getPrefix); 
    601         if ($ns && ($namespace eq $ns->getExpanded)) { 
    602             if (!exists($child_element_names->{$node->getLocalName})) { 
    603                 $data{$node->getLocalName} = MT::Util::decode_xml($node->string_value); 
    604             } 
    605         } elsif ($ns) { 
    606             push @extension_names, $node->getLocalName; 
    607         } 
    608     } 
    609  
    610     my $old_id = $data{id}; 
    611     delete $data{id}; 
    612     $obj->set_values(\%data); 
    613     $obj->save or 
    614         $cb->($obj->errstr . "\n"), return undef; 
    615     $cb->(MT->translate("[_1] [_2] (ID: [_3]) has been restored successfully with new ID: [_4]\n", 
    616             $element->getLocalName =~ m/^[aeiou]/i ? 'An' : 'A', 
    617             $element->getLocalName, 
    618             $old_id, 
    619             $obj->id) 
    620     ); 
    621     my $key = "$class#$old_id"; 
    622     delete $deferred->{$key} if exists $deferred->{$key}; 
    623     $objects->{$key} = $obj; 
    624  
    625     for my $name (keys %$child_element_names) { 
    626         my $children_set = $xp->find("*[local-name()='$name']", $element); 
    627         for my $index2 (1..$children_set->size()) { 
    628             my $node = $children_set->get_node($index2); 
    629             my $ns = $node->getNamespace($node->getPrefix); 
    630             next if !$ns || $namespace ne $ns->getExpanded; 
    631              
    632             $param{XmlNode} = $node; 
    633             my $class = $child_element_names->{$name}; 
    634             eval "require $class;"; 
    635             my $child = $class->from_xml(%param); 
    636             next if !defined($child); 
    637             my $child_old_id = $node->getAttribute('id'); 
    638             my $grand_children_names = $child->children_names; 
    639             if (scalar(keys %$grand_children_names)) { 
    640                 my $child_key = "$class#$child_old_id"; 
    641                 $objects->{$child_key} = $child; 
    642             } 
    643         } 
    644     } 
    645  
    646     for my $ext_name (@extension_names) { 
    647         my $extension_set = $xp->find("*[local-name()='$ext_name']", $element); 
    648         for my $index3 (1..$extension_set->size()) { 
    649             my $ext_node = $extension_set->get_node($index3); 
    650             my $ns = $ext_node->getNamespace($ext_node->getPrefix); 
    651             next if !$ns || $namespace eq $ns->getExpanded; 
    652  
    653             MT->run_callbacks('Restore.' . $obj->datasource . ':' . $ns->getExpanded, 
    654                 $xp, $ext_node, $obj, $objects, $deferred, $cb); 
    655         } 
    656     } 
    657     $obj; 
    658468} 
    659469 
     
    747557package MT::Category; 
    748558 
    749 sub to_backup { 
    750     $_[0]->parent ? 0 : 1; 
    751 
     559#sub to_backup { 
     560#    $_[0]->parent ? 0 : 1; 
     561#
    752562 
    753563sub children_to_xml { 
     
    12651075specified directory. 
    12661076 
    1267 =head2 restore_upload_manifest 
     1077=head2 process_manifest 
    12681078 
    12691079TODO A method which is called from MT::App::CMS to process an uploaded 
     
    12731083=head1 Callbacks 
    12741084 
     1085For plugins which uses MT::Object-derived types, backup and restore 
     1086operation call callbacks for plugins to inject XMLs so they are 
     1087also backup, and read XML to restore objects so they are also restored. 
     1088 
    12751089Callbacks called by the package are as follows: 
    12761090 
    12771091=over 4 
    12781092 
    1279 =item Backup.else 
     1093=item Backup 
    12801094     
    12811095Calling convention is: 
    12821096 
    1283     callback($cb
     1097    callback($cb, $blog_ids
    12841098 
    12851099The callback is used for MT::Object-derived types used by plugins 
    1286 to be backup.  This callback is for those objects which has no 
    1287 relationship with any other MT::Objects.  Refer to MT::Object 
    1288 documentation for how to backup objects with relationships. 
    1289 The callback must return the object's XML representation in a 
    1290 string, or 1 for nothing. 
    1291  
    1292 =item Restore.else.<namespace> 
    1293  
    1294 Calling convention is: 
    1295  
    1296     callback($cb, $xp, $node, $objects, $deferred, $callback) 
    1297  
    1298 Where $xp is an XML::XPath object to be used to search for xml nodes. 
    1299  
    1300 $node is an XML::XPath::Element object to be restored. 
     1100to be backup.  The callback must return the object's XML representation 
     1101in a string, or 1 for nothing.  $blog_ids has an ARRAY reference to 
     1102blog_ids which indicates what weblog a user chose to backup.  It may 
     1103be an empty array if a user chose Everything.