Changeset 951
- Timestamp:
- 12/19/06 11:42:50 (2 years ago)
- Files:
-
- branches/wheeljack/extras/examples/plugins/BackupRestoreSample/BackupRestoreSample.pl (modified) (2 diffs)
- branches/wheeljack/lib/MT/App/CMS.pm (modified) (21 diffs)
- branches/wheeljack/lib/MT/App/Wizard.pm (modified) (1 diff)
- branches/wheeljack/lib/MT/BackupRestore (added)
- branches/wheeljack/lib/MT/BackupRestore.pm (modified) (18 diffs)
- branches/wheeljack/lib/MT/BackupRestore/BackupFileHandler.pm (added)
- branches/wheeljack/lib/MT/BackupRestore/ManifestFileHandler.pm (added)
- branches/wheeljack/lib/MT/Object.pm (modified) (2 diffs)
- branches/wheeljack/mt-check.cgi.pre (modified) (1 diff)
- branches/wheeljack/t/61-to_from_xml.t (modified) (4 diffs)
- branches/wheeljack/tmpl/cms/backup_end.tmpl (modified) (2 diffs)
- branches/wheeljack/tmpl/cms/backup_restore.tmpl (modified) (9 diffs)
- branches/wheeljack/tmpl/cms/blog-left-nav.tmpl (modified) (1 diff)
- branches/wheeljack/tmpl/cms/dialog_restore_end.tmpl (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
branches/wheeljack/extras/examples/plugins/BackupRestoreSample/BackupRestoreSample.pl
r822 r951 28 28 }); 29 29 MT->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); 30 MT->add_callback('Backup', 9, $plugin, \&backup); 31 MT->add_callback("Restore.backup_restore_sample_object:$ns", 9, $plugin, \&restore); 36 32 37 33 sub backup { 38 my ($cb, $obj) =@_; 39 my $class = ref $obj; 34 my ($cb, $blog_ids) = @_; 40 35 my $xml; 41 36 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)); 45 38 my @objects = BackupRestoreSample::Object->load($terms); 46 39 for my $object (@objects) { … … 50 43 } 51 44 52 sub backup_else { 53 my ($cb) = @_; 54 return "<foo xmlns='$ns'>barbaz</foo>"; 45 sub 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; 55 54 } 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 10823 10823 my $app = shift; 10824 10824 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 } 10826 10831 10827 10832 my %param = (); 10833 $param{blog_id} = $blog_id if $blog_id; 10828 10834 $app->add_breadcrumb($app->translate('Backup & Restore')); 10829 $param{system_overview_nav} = 1 ;10835 $param{system_overview_nav} = 1 unless $blog_id; 10830 10836 $param{nav_backup} = 1; 10831 10837 my $missing_tgz = 0; … … 10840 10846 $param{zip} = !$missing_zip; 10841 10847 10842 eval "require XML:: XPath";10843 $param{missing_ xpath} = 1 if $@;10848 eval "require XML::SAX"; 10849 $param{missing_sax} = 1 if $@; 10844 10850 10845 10851 my $limit = $app->config('CGIMaxUpload') || 2048; … … 10869 10875 my $app = shift; 10870 10876 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 } 10872 10883 $app->validate_magic() or return; 10873 10884 10874 my $q = $app->param;10875 10876 10885 my $what = $q->param('backup_what'); 10877 10886 return $app->errtrans("You must select what you want to backup.") if !$what; … … 10907 10916 $app->add_breadcrumb($app->translate('Backup & Restore'), $app->uri(mode => 'backup_restore')); 10908 10917 $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; 10910 10920 $param->{nav_backup} = 1; 10911 10921 … … 10981 10991 $fh = gensym(); 10982 10992 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); 10983 10995 push @files, { 10984 url => $ app->uri . "?__mode=backup_download&name=$file-1.xml&magic_token=" . $app->current_magic,10996 url => $url, 10985 10997 filename => $file . "-1.xml" 10986 10998 }; … … 10993 11005 $fh = gensym(); 10994 11006 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); 10995 11009 push @files, { 10996 url => $ app->uri . "?__mode=backup_download&name=$file-$findex.xml&magic_token=" . $app->current_magic,11010 url => $url, 10997 11011 filename => $file . "-$findex.xml" 10998 11012 }; … … 11023 11037 print $fh "</manifest>\n"; 11024 11038 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); 11025 11041 push @files, { 11026 url => $ app->uri . "?__mode=backup_download&name=$file.manifest&magic_token=" . $app->current_magic,11042 url => $url, 11027 11043 filename => "$file.manifest" 11028 11044 }; … … 11067 11083 $arc->writeToFileHandle($fh_arc); 11068 11084 close $fh_arc; 11085 unlink File::Spec->catfile($temp_dir, $_->{filename}) foreach 11086 grep { !defined($_->{path}) } @files; 11069 11087 $app->_backup_finisher($fname, $param); 11070 11088 } … … 11079 11097 my $app = shift; 11080 11098 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 } 11082 11104 $app->validate_magic() or return; 11083 11105 my $filename = $app->param('filename'); … … 11180 11202 $result = $app->restore_directory($dir, \$error); 11181 11203 } else { 11204 $param->{restore_upload} = 1; 11182 11205 my $uploaded = $q->param('file'); 11183 11206 my ($volume, $directories, $uploaded_filename) = File::Spec->splitpath($uploaded) if defined($uploaded); 11184 11207 if ($uploaded_filename =~ /^.+\.xml$/i) { 11185 $param->{restore_upload} = 1;11186 11208 $result = $app->restore_file($fh, \$error); 11187 11209 } elsif ($uploaded_filename =~ /^.+\.tar(\.gz)?$/i) { … … 11257 11279 $result = 0; 11258 11280 $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.'); 11259 11284 } 11260 11285 } … … 11292 11317 return 0; 11293 11318 } 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 } 11294 11328 11295 11329 $app->log({ … … 11312 11346 } 11313 11347 11348 my @errors; 11314 11349 my %error_assets; 11315 11350 require MT::BackupRestore; 11316 11351 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); 11320 11356 return 0; 11321 11357 } … … 11389 11425 return $app->errtrans("No manifest file was uploaded.") if $no_upload; 11390 11426 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; 11391 11435 my $param = {}; 11392 11436 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); 11397 11451 $param->{open_dialog} = 1; 11398 11452 $app->build_page('backup_restore.tmpl', $param); … … 11446 11500 11447 11501 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 } 11454 11510 } 11455 11511 … … 11458 11514 return $app->build_page('dialog_restore_upload.tmpl', $param); 11459 11515 } 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 );11474 11516 11475 11517 $app->{no_print_body} = 1; … … 11511 11553 } 11512 11554 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); 11514 11557 my $asset; 11558 my @errors; 11515 11559 require MT::BackupRestore; 11516 11560 if ($is_asset) { … … 11529 11573 } else { 11530 11574 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(@_) }); 11532 11576 } 11533 11577 … … 11545 11589 $param->{assets} = MT::Util::encode_html(JSON::objToJson($assets)); 11546 11590 $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 } 11548 11598 $param->{is_dirty} = scalar(keys %$deferred); 11549 11599 if ($last) { … … 11572 11622 category => 'restore' 11573 11623 }); 11624 $param->{ok_url} = $app->uri(mode => 'backup_restore', args => {}); 11574 11625 } 11575 11626 } else { … … 11579 11630 $param->{deferred_json} = JSON::objToJson($deferred); 11580 11631 11581 $param->{error} = $error if $error;11632 $param->{error} = join('; ', @errors); 11582 11633 $param->{next_mode} = 'dialog_restore_upload'; 11583 11634 } branches/wheeljack/lib/MT/App/Wizard.pm
r903 r951 39 39 [ 'IO::Uncompress::Gunzip', 0, 0, 'IO::Uncompress::Gunzip is required in order to decompress files in backup/restore operation.', 'IO Uncompress Gunzip'], 40 40 [ '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'], 42 42 ); 43 43 branches/wheeljack/lib/MT/BackupRestore.pm
r910 r951 43 43 args => undef 44 44 }}; 45 push @$obj_to_backup, {'MT::Notification' => { 46 term => { 'blog_id' => $blog_ids }, 47 args => undef 48 }}; 45 49 push @$obj_to_backup, {'MT::Template' => { 46 50 term => { 'id' => $blog_ids }, 51 args => undef 52 }}; 53 push @$obj_to_backup, {'MT::TemplateMap' => { 54 term => { 'blog_id' => $blog_ids }, 47 55 args => undef 48 56 }}; … … 52 60 [ 'MT::Association', 'role_id', { blog_id => $blog_ids }, undef ] 53 61 }}}; 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 }}; 54 70 push @$obj_to_backup, {'MT::Category' => { 55 71 term => { 'blog_id' => $blog_ids }, … … 64 80 args => undef 65 81 }}; 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 }}; 66 94 push @$obj_to_backup, {'MT::Trackback' => { 67 95 term => { 'blog_id' => $blog_ids }, 68 96 args => undef 69 97 }}; 98 push @$obj_to_backup, {'MT::TBPing' => { 99 term => { 'blog_id' => $blog_ids }, 100 args => undef 101 }}; 70 102 push @$obj_to_backup, {'MT::Comment' => { 71 103 term => { 'blog_id' => $blog_ids }, 72 104 args => undef 73 105 }}; 106 push @$obj_to_backup, {'MT::PluginData' => { term => undef, args => undef }}; 74 107 } else { 75 108 push @$obj_to_backup, {'MT::Tag' => { term => undef, args => undef }}; 76 109 push @$obj_to_backup, {'MT::Author' => { term => undef, args => undef }}; 77 110 push @$obj_to_backup, {'MT::Blog' => { term => undef, args => undef }}; 111 push @$obj_to_backup, {'MT::Notification' => { term => undef, args => undef }}; 78 112 push @$obj_to_backup, {'MT::Template' => { term => undef, args => undef }}; 113 push @$obj_to_backup, {'MT::TemplateMap' => { term => undef, args => undef }}; 79 114 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 }}; 80 117 push @$obj_to_backup, {'MT::Category' => { term => undef, args => undef }}; 81 118 push @$obj_to_backup, {'MT::Asset' => { term => undef, args => undef }}; 82 119 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 }}; 83 123 push @$obj_to_backup, {'MT::Trackback' => { term => undef, args => undef }}; 124 push @$obj_to_backup, {'MT::TBPing' => { term => undef, args => undef }}; 84 125 push @$obj_to_backup, {'MT::Comment' => { term => undef, args => undef }}; 126 push @$obj_to_backup, {'MT::PluginData' => { term => undef, args => undef }}; 127 $blog_ids = []; 85 128 } 86 129 … … 93 136 $printer, $splitter, $finisher, $size, $obj_to_backup, $files); 94 137 95 my $else_xml = MT->run_callbacks('Backup .else');138 my $else_xml = MT->run_callbacks('Backup', $blog_ids); 96 139 $printer->($else_xml, q()) if $else_xml ne '1'; 97 140 … … 151 194 my ($fh, $errormsg, $callback) = @_; 152 195 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 170 202 ); 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); 178 204 $deferred; 179 205 } 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 201 207 sub restore_process_single_file { 202 208 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; 246 231 } 247 232 248 233 sub restore_directory { 249 234 my $class = shift; 250 my ($dir, $error , $error_assets, $callback) = @_;235 my ($dir, $errors, $error_assets, $callback) = @_; 251 236 252 237 my $manifest; 253 238 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; 255 240 for my $f (readdir $dh) { 256 241 next if $f !~ /^.+\.manifest$/i; … … 260 245 closedir $dh; 261 246 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); 263 248 return undef; 264 249 } 265 250 266 251 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); 269 254 close $fh; 270 255 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); 272 257 return undef; 273 258 } … … 275 260 $callback->(MT->translate("Manifest file: [_1]\n", $manifest)); 276 261 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 );289 262 my %objects; 290 263 my $deferred = {}; … … 294 267 my $fh = gensym; 295 268 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; 302 270 303 271 my $result = __PACKAGE__->restore_process_single_file( 304 $fh, \ @obj_to_restore, \%objects, $deferred, $error, $callback);272 $fh, \%objects, $deferred, $errors, $callback); 305 273 306 274 close $fh; … … 311 279 } 312 280 313 sub _process_manifest { 281 sub process_manifest { 282 my $class = shift; 314 283 my ($stream) = @_; 315 284 316 285 if ((ref($stream) eq 'Fh') || (ref($stream) eq 'GLOB')){ 317 286 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}; 346 299 } 347 300 return undef; … … 353 306 354 307 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 } 357 312 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 } 358 320 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 } 360 325 my ($vol, $dir, $fn) = File::Spec->splitpath($path); 361 326 if (!-w "$vol$dir") { … … 392 357 } 393 358 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();422 359 } 423 360 … … 489 426 push @elements, $name; 490 427 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;496 428 } 497 429 $xml .= " $name='" . MT::Util::encode_xml($obj->column($name), 1) . "'"; … … 500 432 $xml .= '>'; 501 433 $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';505 434 $xml .= '</' . $obj->datasource . '>'; 506 435 $xml; … … 537 466 } 538 467 (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 or614 $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;658 468 } 659 469 … … 747 557 package MT::Category; 748 558 749 sub to_backup {750 $_[0]->parent ? 0 : 1;751 }559 #sub to_backup { 560 # $_[0]->parent ? 0 : 1; 561 #} 752 562 753 563 sub children_to_xml { … … 1265 1075 specified directory. 1266 1076 1267 =head2 restore_upload_manifest1077 =head2 process_manifest 1268 1078 1269 1079 TODO A method which is called from MT::App::CMS to process an uploaded … … 1273 1083 =head1 Callbacks 1274 1084 1085 For plugins which uses MT::Object-derived types, backup and restore 1086 operation call callbacks for plugins to inject XMLs so they are 1087 also backup, and read XML to restore objects so they are also restored. 1088 1275 1089 Callbacks called by the package are as follows: 1276 1090 1277 1091 =over 4 1278 1092 1279 =item Backup .else1093 =item Backup 1280 1094 1281 1095 Calling convention is: 1282 1096 1283 callback($cb )1097 callback($cb, $blog_ids) 1284 1098 1285 1099 The 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. 1100 to be backup. The callback must return the object's XML representation 1101 in a string, or 1 for nothing. $blog_ids has an ARRAY reference to 1102 blog_ids which indicates what weblog a user chose to backup. It may 1103 be an empty array if a user chose Everything.
