root/branches/wheeljack/lib/MT/BackupRestore.pm @ 972

Revision 972, 31.7 kB (checked in by fumiakiy, 3 years ago)

Added code to handle MT::Asset::* in restore operation. BugId: 46134

  • Property svn:keywords set to Author Date Id Revision
Line 
1# Copyright 2001-2006 Six Apart. This code cannot be redistributed without
2# permission from www.sixapart.com.  For more information, consult your
3# Movable Type license.
4#
5# $Id$
6
7package MT::BackupRestore;
8use strict;
9
10use Symbol;
11use MT::ErrorHandler;
12@MT::BackupRestore::ISA = qw( MT::ErrorHandler );
13
14use constant NS_MOVABLETYPE => 'http://www.sixapart.com/ns/movabletype';
15
16use File::Spec;
17use File::Copy;
18
19sub backup {
20    my $class = shift;
21    my ($blog_ids, $printer, $splitter, $finisher, $size, $enc) = @_;
22    my $obj_to_backup = [];
23
24    if (defined($blog_ids) && scalar(@$blog_ids)) {
25        push @$obj_to_backup, {'MT::Tag' => {
26            term => undef, 
27            args => { 'join' =>
28                [ 'MT::ObjectTag', 'tag_id', { blog_id => $blog_ids }, undef ]
29            }}};
30        push @$obj_to_backup, {'MT::Author' => {
31            term => undef, 
32            args => { 'join' => 
33                [ 'MT::Association', 'author_id', { blog_id => $blog_ids }, undef ]
34            }}};
35        ## Author has two different ways to associate to a weblog...
36        push @$obj_to_backup, {'MT::Author' => {
37            term => undef, 
38            args => { 'join' => 
39                [ 'MT::Permission', 'author_id', { blog_id => $blog_ids }, undef ]
40            }}};
41        push @$obj_to_backup, {'MT::Blog' => { 
42            term => { 'id' => $blog_ids }, 
43            args => undef
44            }};
45        push @$obj_to_backup, {'MT::Notification' => {
46            term => { 'blog_id' => $blog_ids }, 
47            args => undef
48            }};
49        push @$obj_to_backup, {'MT::Template' => { 
50            term => { 'blog_id' => $blog_ids }, 
51            args => undef
52            }};
53        push @$obj_to_backup, {'MT::TemplateMap' => {
54            term => { 'blog_id' => $blog_ids }, 
55            args => undef
56            }};
57        push @$obj_to_backup, {'MT::Role' => {
58            term => undef,
59            args => { 'join' => 
60                [ 'MT::Association', 'role_id', { blog_id => $blog_ids }, undef ]
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            }};
70        push @$obj_to_backup, {'MT::Category' => {
71            term => { 'blog_id' => $blog_ids }, 
72            args => undef
73            }};
74        push @$obj_to_backup, {'MT::Asset' => {
75            term => { 'blog_id' => $blog_ids }, 
76            args => undef
77            }};
78        push @$obj_to_backup, {'MT::Entry' => {
79            term => { 'blog_id' => $blog_ids }, 
80            args => undef
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            }};
94        push @$obj_to_backup, {'MT::Trackback' => {
95            term => { 'blog_id' => $blog_ids }, 
96            args => undef
97            }};
98        push @$obj_to_backup, {'MT::TBPing' => {
99            term => { 'blog_id' => $blog_ids }, 
100            args => undef
101            }};
102        push @$obj_to_backup, {'MT::Comment' => {
103            term => { 'blog_id' => $blog_ids }, 
104            args => undef
105            }};
106        push @$obj_to_backup, {'MT::PluginData' => { term => undef, args => undef }};
107    } else {
108        push @$obj_to_backup, {'MT::Tag' => { term => undef, args => undef }};
109        push @$obj_to_backup, {'MT::Author' => { term => undef, args => undef }};
110        push @$obj_to_backup, {'MT::Blog' => { term => undef, args => undef }};
111        push @$obj_to_backup, {'MT::Notification' => { term => undef, args => undef }};
112        push @$obj_to_backup, {'MT::Template' => { term => undef, args => undef }};
113        push @$obj_to_backup, {'MT::TemplateMap' => { term => undef, args => undef }};
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 }};
117        push @$obj_to_backup, {'MT::Category' => { term => undef, args => undef }};
118        push @$obj_to_backup, {'MT::Asset' => { term => undef, args => undef }};
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 }};
123        push @$obj_to_backup, {'MT::Trackback' => { term => undef, args => undef }};
124        push @$obj_to_backup, {'MT::TBPing' => { term => undef, args => undef }};
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 = [];
128    }
129
130    my $header .= "<movabletype xmlns='" . NS_MOVABLETYPE . "'>\n";
131    $header = "<?xml version='1.0' encoding='$enc'?>\n$header" if $enc !~ m/utf-?8/i;
132    $printer->($header, q());
133
134    my $files = {};
135    _loop_through_objects(
136        $printer, $splitter, $finisher, $size, $obj_to_backup, $files);
137
138    my $else_xml = MT->run_callbacks('Backup', $blog_ids);
139    $printer->($else_xml, q()) if $else_xml ne '1';
140
141    $printer->('</movabletype>', q());
142    $finisher->($files);
143}
144
145sub _loop_through_objects {
146    my ($printer, $splitter, $finisher, $size, $obj_to_backup, $files) = @_;
147
148    my $counter = 1;
149    my $bytes = 0;
150    my %author_ids_seen;
151    for my $class_hash (@$obj_to_backup) {
152        my ($class, $term_arg) = each(%$class_hash);
153        eval "require $class;";
154        my $children = $class->children_names || {};
155        for my $child_class (values %$children) {
156            eval "require $child_class;";
157        }
158        my $err = $@;
159        if ($err) {
160            $printer->("$err\n", "$err\n");
161            next;
162        }
163        my $offset = 0;
164        my $term = $term_arg->{term} || {};
165        my $args = $term_arg->{args};
166        while (1) {
167            $args->{offset} = $offset;
168            $args->{limit} = 50;
169            my @objects = $class->load($term, $args);
170            last unless @objects;
171            $offset += scalar @objects;
172            for my $object (@objects) {
173                next if ($class eq 'MT::Author') && exists($author_ids_seen{$object->id});
174                $bytes += $printer->($object->to_xml(undef, $args) . "\n", 
175                    MT->translate('[_1]#[_2] has been backed up.', $class, $object->id) . "\n")
176                        if $object->to_backup;
177                if ($size && ($bytes >= $size)) {
178                    $splitter->(++$counter);
179                    $bytes = 0;
180                }
181                if ($class eq 'MT::Author') {
182                    # MT::Author may be duplicated because of how terms and args are created.
183                    $author_ids_seen{$object->id} = 1;
184                } elsif ($class eq 'MT::Asset') {
185                    $files->{$object->id} = [$object->url, $object->file_path, $object->file_name];
186                }
187            }
188        }
189    }
190}
191
192sub restore_file {
193    my $class = shift;
194    my ($fh, $errormsg, $callback) = @_;
195
196    my $objects = {};
197    my $deferred = {};
198    my $errors = [];
199
200    $class->restore_process_single_file(
201        $fh, $objects, $deferred, $errors, $callback
202    );
203    $$errormsg = join('; ', @$errors);
204    $deferred;
205}
206 
207sub restore_process_single_file {
208    my $class = shift;
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;
231}
232
233sub restore_directory {
234    my $class = shift;
235    my ($dir, $errors, $error_assets, $callback) = @_;
236
237    my $manifest;
238    my @files;
239    opendir my $dh, $dir or push(@$errors, MT->translate("Can't open directory '[_1]': [_2]", $dir, "$!")), return undef;
240    for my $f (readdir $dh) {
241        next if $f !~ /^.+\.manifest$/i;
242        $manifest = File::Spec->catfile($dir, $f);
243        last;
244    }
245    closedir $dh;
246    unless ($manifest) {
247        push @$errors, MT->translate("No manifest file could be found in your import directory [_1].", $dir);
248        return undef;
249    }
250
251    my $fh = gensym;
252    open $fh, "<$manifest" or push(@$errors, MT->translate("Can't open [_1].", $manifest)), return 0;
253    my $backups = __PACKAGE__->process_manifest($fh);
254    close $fh;
255    unless($backups) {
256        push @$errors, MT->translate("Manifest file [_1] was not a valid Movable Type backup manifest file.", $manifest);
257        return undef;
258    }
259
260    $callback->(MT->translate("Manifest file: [_1]\n", $manifest));
261
262    my %objects;
263    my $deferred = {};
264
265    my $files = $backups->{files};
266    for my $file (@$files) {
267        my $fh = gensym;
268        my $filepath = File::Spec->catfile($dir, $file);
269        open $fh, "<$filepath" or push @$errors, MT->translate('[_1] cannot open.'), next;
270
271        my $result = __PACKAGE__->restore_process_single_file(
272            $fh, \%objects, $deferred, $errors, $callback);
273
274        close $fh;
275    }
276   
277     _restore_assets($dir, $backups, \%objects, $error_assets, $callback) if defined($backups->{assets});
278    $deferred;
279}
280
281sub process_manifest {
282    my $class = shift;
283    my ($stream) = @_;
284
285    if ((ref($stream) eq 'Fh') || (ref($stream) eq 'GLOB')){
286        seek($stream, 0, 0) or 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};
299    }
300    return undef;
301}
302
303sub restore_asset {
304    my $class = shift;
305    my ($tmp, $asset_element, $objects, $errors, $callback) = @_;
306
307    my $id = $asset_element->{asset_id};
308    if (!defined($id)) {
309        $callback->(MT->translate('ID for the asset was not set.'));
310        return 0;
311    }
312    my $asset = $objects->{"MT::Asset#$id"};
313    unless (defined($asset)) {
314        $callback->(MT->translate('The asset ([_1]) was not restored.', $id));
315        return 0;
316    }
317    my $path = $asset->file_path;
318    unless (defined($path)) {
319        $callback->(MT->translate('Path was not found for the asset ([_1]).', $id));
320        return 0;
321    }
322    my ($vol, $dir, $fn) = File::Spec->splitpath($path);
323    if (!-w "$vol$dir") {
324        my $voldir =  "$vol$dir";
325        # we do need decode_utf8 here
326        $errors->{$id} = MT->translate('[_1] is not writable.', MT::I18N::utf8_off($voldir));
327    } else {
328        my $filename = "$id-" . $asset_element->{name};
329        $callback->(MT->translate("Copying [_1] to [_2]...", $filename, $path));
330        my $file;
331        if (defined($tmp)) {
332            $file = File::Spec->catfile($tmp, $filename) if defined($tmp);
333        } else {
334            $file = $asset_element->{fh};
335        }
336        copy($file, $path)
337            or $errors->{$id} = $!;
338    }
339    $callback->(exists($errors->{$id}) ?
340        MT->translate('Failed: ') . $errors->{$id} :
341        MT->translate("Done.")
342    );
343    $callback->("\n");
344}
345
346sub _restore_assets {
347    my ($tmp, $backups, $objects, $errors, $callback) = @_;
348    my $assets = $backups->{assets};
349    return 0 if !defined($assets);
350    use File::Copy;
351    require File::Spec;
352    for my $asset_element (@$assets) {
353        __PACKAGE__->restore_asset($tmp, $asset_element, $objects, $errors, $callback);
354    }
355    1;
356}
357
358package MT::Object;
359
360sub _is_element {
361    my $obj = shift;
362    my ($def) = @_;
363    return (('text' eq $def->{type}) || (('string' eq $def->{type}) && (255 < $def->{size}))) ? 1 : 0;
364}
365
366sub to_backup { 1; }
367
368sub children_to_xml {
369    my $obj = shift;
370    my ($namespace, $args) = @_;
371
372    my $t = {};
373    if (defined($args)) {
374        my $j = $args->{'join'};
375        $t = $j->[2] if defined($j);
376    }
377
378    my $children = $obj->children_names;
379    my @children_classes = values %$children;
380    my $xml = '';
381
382    for my $child_class (@children_classes) {
383        eval "require $child_class";
384        my $err = $@;
385        return $err if defined($err) && $err;
386
387        my $terms = { 
388            $obj->datasource . '_id' => $obj->id,
389            %$t,
390        };
391       
392        my $offset = 0;
393        while (1) {
394            my @objects = $child_class->load(
395                $terms,
396                { offset => $offset, limit => 50, }
397            );
398            last unless @objects;
399            $offset += scalar @objects;
400            for my $object (@objects) {
401                $xml .= $object->to_xml($namespace) . "\n" if $object->to_backup;
402            }
403        }
404    }
405    $xml;
406}
407
408sub to_xml {
409    my $obj = shift;
410    my ($namespace, $args) = @_;
411
412    my $coldefs = $obj->column_defs;
413    my $colnames = $obj->column_names;
414    my $xml;
415
416    $xml = '<' . $obj->datasource;
417    $xml .= " xmlns='$namespace'" if defined($namespace) && $namespace;
418
419    my @elements;
420    for my $name (@$colnames) {
421        if ($obj->column($name) || (defined($obj->column($name)) && ('0' eq $obj->column($name)))) {
422            if ($obj->_is_element($coldefs->{$name})) {
423                push @elements, $name;
424                next;
425            }
426            $xml .= " $name='" . MT::Util::encode_xml($obj->column($name), 1) . "'";
427        }
428    }
429    $xml .= '>';
430    $xml .= "<$_>" . MT::Util::encode_xml($obj->column($_), 1) . "</$_>" foreach @elements;
431    $xml .= '</' . $obj->datasource . '>';
432    $xml;
433}
434
435sub children_names {
436    my $obj = shift;
437    {};
438}
439
440sub parent_names {
441    my $obj = shift;
442    {};
443}
444
445sub restore_parent_ids {
446    my $obj = shift;
447    my ($data, $objects) = @_;
448
449    my $parent_names = $obj->parent_names;
450
451    my $done = 0;
452    for my $parent_element_name (keys %$parent_names) {
453        if (!exists($data->{$parent_element_name . '_id'})) {
454            $done++;
455            next;
456        }
457        my $parent_class_name = $parent_names->{$parent_element_name};
458        my $old_id = $data->{$parent_element_name . '_id'};
459        my $new_obj = $objects->{"$parent_class_name#$old_id"};
460        next if !(defined($new_obj) && $new_obj);
461        $data->{$parent_element_name . '_id'} = $new_obj->id;
462        $done++;
463    }
464    (scalar(keys(%$parent_names)) == $done) ? 1 : 0;   
465}
466
467package MT::Asset;
468
469sub children_to_xml {
470    my $obj = shift;
471    my ($namespace, $args) = @_;
472    my $xml = '';
473
474    require MT::ObjectTag;
475    my $offset = 0;
476    while (1) {
477        my @objecttags = MT::ObjectTag->load(
478            { object_id => $obj->id, object_datasource => $obj->datasource },
479            { offset => $offset, limit => 50, }
480        );
481        last unless @objecttags;
482        $offset += scalar @objecttags;
483        for my $objecttag (@objecttags) {
484            $xml .= $objecttag->to_xml($namespace, $args) . "\n" if $objecttag->to_backup;
485        }
486    }
487   
488    $xml;
489}
490
491sub children_names {
492    my $obj = shift;
493    my $children = {
494        objecttag => 'MT::ObjectTag',
495    };
496    $children;
497}
498
499sub parent_names {
500    my $obj = shift;
501    my $parents = {
502        blog => 'MT::Blog',
503    };
504    $parents;
505}
506
507package MT::Association;
508
509sub parent_names {
510    my $obj = shift;
511    my $parents = {
512        blog => 'MT::Blog',
513        author => 'MT::Author',
514        role => 'MT::Role',
515    };
516    $parents;
517}
518
519package MT::Author;
520
521sub children_names {
522    my $obj = shift;
523    my $children = {
524        permission => 'MT::Permission',
525        association => 'MT::Association',
526        #entry => 'MT::Entry',       ## An author is a parent of a category/entry
527        #category => 'MT::Category', ## but a category/entry is not a child of an author
528                                     ## otherwise they duplicate in restore operation.
529                                     ## Also, <author> must come before <category> and
530                                     ## <entry> in the backup file.
531    };
532    $children;
533}
534
535package MT::Blog;
536
537sub children_names {
538    my $obj = shift;
539    my $children = {
540        placement => 'MT::Placement',
541        permission => 'MT::Permission',
542        notification => 'MT::Notification',
543        association => 'MT::Association',
544        fileinfo => 'MT::FileInfo',
545        #template => 'MT::Template',
546        #entry => 'MT::Entry',  ## A blog is a parent of an entry/a template but
547                                ## they are not a child of a blog
548                                ## otherwise they duplicate in restore operation.
549                                ## Also, <blog> must come before <entry> and <template>.
550    };
551    $children;
552}
553
554package MT::Category;
555
556#sub to_backup {
557#    $_[0]->parent ? 0 : 1;
558#}
559
560sub children_to_xml {
561    my $obj = shift;
562    my ($namespace, $args) = @_;
563    my $xml = '';
564
565    #require MT::Trackback;
566    #my $tb = MT::Trackback->load({ category_id => $obj->id });
567    #if ($tb) {
568    #    require MT::TBPing;
569    #    my $offset = 0;
570    #    while (1) {
571    #        my @pings = MT::TBPing->load(
572    #            { tb_id => $tb->id, },
573    #            { offset => $offset, limit => 50, }
574    #        );
575    #        last unless @pings;
576    #        $offset += scalar @pings;
577    #        for my $ping (@pings) {
578    #            $xml .= $ping->to_xml($namespace, $args) . "\n" if $ping->to_backup;
579    #        }
580    #    }
581    #}
582
583    my $offset = 0;
584    while (1) {
585        my @placements = MT::Placement->load(
586            { 'category_id' => $obj->id, },
587            { offset => $offset, limit => 50, }
588        );
589        last unless @placements;
590        $offset += scalar @placements;
591        for my $placement (@placements) {
592            $xml .= $placement->to_xml($namespace, $args) . "\n" if $placement->to_backup;
593        }
594    }
595
596    my @children = $obj->children_categories;
597    return $xml unless @children;
598    for my $child (@children) {
599        $xml .= $child->to_xml($namespace, $args) . "\n";
600    }
601    $xml;
602}
603
604sub children_names {
605    my $obj = shift;
606    my $children = {
607        #tbping => 'MT::TBPing',
608        category => 'MT::Category',
609        placement => 'MT::Placement',
610        fileinfo => 'MT::FileInfo',
611    };
612    $children;
613}
614
615sub parent_names {
616    my $obj = shift;
617    my $parents = {
618        blog => 'MT::Blog',
619        author => 'MT::Author',
620    };
621    $parents;
622}
623
624sub restore_parent_ids {
625    my $obj = shift;
626    my ($data, $objects) = @_;
627
628    my $parent_names = $obj->parent_names;
629
630    my $done = -1;
631    for my $parent_element_name (keys %$parent_names) {
632        my $parent_class_name = $parent_names->{$parent_element_name};
633        my $old_id = $data->{$parent_element_name . '_id'};
634        my $new_obj = $objects->{"$parent_class_name#$old_id"};
635        next if !(defined($new_obj) && $new_obj);
636        $data->{$parent_element_name . '_id'} = $new_obj->id;
637        $done++;
638    }
639    my $old_id = $data->{'parent'};
640    if (defined($old_id) && ($old_id > 0)) {
641        my $new_obj = $objects->{"MT::Category#$old_id"};
642        if (defined($new_obj) && $new_obj) {
643            $data->{'parent'} = $new_obj->id;
644            $done++;
645        }
646    } else {
647        $done++;
648    }
649    (scalar(keys(%$parent_names)) == $done) ? 1 : 0;   
650}
651
652package MT::Comment;
653
654## To avoid duplicates...
655#sub children_names {
656#    my $obj = shift;
657#    my $children = {
658#        author -> 'MT::Author',
659#    };
660#    $children;
661#}
662
663sub parent_names {
664    my $obj = shift;
665    my $parents = {
666        entry => 'MT::Entry',
667        blog => 'MT::Blog',
668    };
669    $parents;
670}
671
672sub restore_parent_ids {
673    my $obj = shift;
674    my ($data, $objects) = @_;
675
676    my $parent_names = $obj->parent_names;
677
678    my $done = -1;
679    for my $parent_element_name (keys %$parent_names) {
680        my $parent_class_name = $parent_names->{$parent_element_name};
681        my $old_id = $data->{$parent_element_name . '_id'};
682        my $new_obj = $objects->{"$parent_class_name#$old_id"};
683        next if !(defined($new_obj) && $new_obj);
684        $data->{$parent_element_name . '_id'} = $new_obj->id;
685        $done++;
686    }
687    my $old_id = $data->{'commenter_id'};
688    if (defined($old_id) && ($old_id > 0)) {
689        my $new_obj = $objects->{"MT::Author#$old_id"};
690        if (defined($new_obj) && $new_obj) {
691            $data->{'commenter_id'} = $new_obj->id;
692            $done++;
693        }
694    } else {
695        $done++;
696    }
697    (scalar(keys(%$parent_names)) == $done) ? 1 : 0;   
698}
699
700package MT::Entry;
701
702sub children_to_xml {
703    my $obj = shift;
704    my ($namespace, $args) = @_;
705
706    my $xml = '';
707
708    $xml .= $obj->_entry_child_to_xml('MT::Placement');
709
710    require MT::ObjectTag;
711    my $offset = 0;
712    while (1) {
713        my @objecttags = MT::ObjectTag->load(
714            { object_id => $obj->id, object_datasource => $obj->datasource },
715            { offset => $offset, limit => 50, }
716        );
717        last unless @objecttags;
718        $offset += scalar @objecttags;
719        for my $objecttag (@objecttags) {
720            $xml .= $objecttag->to_xml($namespace, $args) . "\n" if $objecttag->to_backup;
721        }
722    }
723   
724    #require MT::Trackback;
725    #my $tb = MT::Trackback->load({ entry_id => $obj->id });
726    #if ($tb) {
727    #    require MT::TBPing;
728    #    my $offset = 0;
729    #    while (1) {
730    #        my @pings = MT::TBPing->load(
731    #            { tb_id => $tb->id, },
732    #            { offset => $offset, limit => 50, }
733    #        );
734    #        last unless @pings;
735    #        $offset += scalar @pings;
736    #        for my $ping (@pings) {
737    #            $xml .= $ping->to_xml($namespace, $args) . "\n" if $ping->to_backup;
738    #        }
739    #    }
740    #}
741   
742    $xml .= $obj->_entry_child_to_xml('MT::FileInfo', $namespace, $args);
743   
744    $xml;
745}
746
747sub _entry_child_to_xml {
748    my $obj = shift;
749    my ($child_class, $namespace, $args) = @_;
750    my $xml = '';
751   
752    eval "require $child_class";
753    my $err = $@;
754    return $err if defined($err) && $err;
755
756    my $offset = 0;
757    while (1) {
758        my @objects = $child_class->load(
759            { entry_id => $obj->id, },
760            { offset => $offset, limit => 50, }
761        );
762        last unless @objects;
763        $offset += scalar @objects;
764        for my $object (@objects) {
765            $xml .= $object->to_xml($namespace, $args) . "\n" if $object->to_backup;
766        }
767    }
768    $xml;
769}
770
771sub children_names {
772    my $obj = shift;
773    my $children = {
774        objecttag => 'MT::ObjectTag',
775        placement => 'MT::Placement',
776        fileinfo => 'MT::FileInfo',
777    };
778    $children;
779}
780
781sub parent_names {
782    my $obj = shift;
783    my $parents = {
784        blog => 'MT::Blog',
785        author => 'MT::Author',
786    };
787    $parents;
788}
789
790package MT::Notification;
791
792sub parent_names {
793    my $obj = shift;
794    my $parents = {
795        blog => 'MT::Blog',
796    };
797    $parents;
798}
799
800package MT::ObjectTag;
801
802sub parent_names {
803    my $obj = shift;
804    my $parents = {
805        blog => 'MT::Blog',
806        tag => 'MT::Tag',
807        entry => 'MT::Entry',
808        asset => 'MT::Asset',
809    };
810    $parents;
811}
812
813sub restore_parent_ids {
814    my $obj = shift;
815    my ($data, $objects) = @_;
816
817    my $parent_names = $obj->parent_names;
818
819    my $done = 1;
820    for my $parent_element_name (keys %$parent_names) {
821        my $parent_class_name = $parent_names->{$parent_element_name};
822        my $old_id = $data->{$parent_element_name . '_id'};
823        my $new_obj = $objects->{"$parent_class_name#$old_id"};
824        next if !(defined($new_obj) && $new_obj);
825        $data->{$parent_element_name . '_id'} = $new_obj->id;
826        $done++;
827    }
828    my $old_id = $data->{'object_id'};
829    if (defined($old_id) && ($old_id > 0)) {
830        my $class = $parent_names->{$data->{'object_datasource'}};
831        my $new_obj = $objects->{"$class#$old_id"};
832        if (defined($new_obj) && $new_obj) {
833            $data->{'object_id'} = $new_obj->id;
834            $done++;
835        }
836    } else {
837        $done++;
838    }
839    (scalar(keys(%$parent_names)) == $done) ? 1 : 0;   
840}
841
842package MT::Permission;
843
844sub parent_names {
845    my $obj = shift;
846    my $parents = {
847        author => 'MT::Author',
848        blog => 'MT::Blog'
849    };
850    $parents;
851}
852
853package MT::Placement;
854
855sub parent_names {
856    my $obj = shift;
857    my $parents = {
858        category => 'MT::Category',
859        blog => 'MT::Blog',
860        entry => 'MT::Entry',
861    };
862    $parents;
863}
864
865package MT::Role;
866
867sub children_names {
868    my $obj = shift;
869    my $children = {
870        association => 'MT::Association',
871    };
872    $children;
873}
874
875package MT::Tag;
876
877sub children_names {
878    my $obj = shift;
879    my $children = {
880        objecttag => 'MT::ObjectTag',
881    };
882    $children;
883}
884
885package MT::TBPing;
886
887sub parent_names {
888    my $obj = shift;
889    my $parents = {
890        blog => 'MT::Blog',
891    };
892    $parents;
893}
894
895sub restore_parent_ids {
896    my $obj = shift;
897    my ($data, $objects) = @_;
898
899    my $parent_names = $obj->parent_names;
900
901    my $done = -1;
902    for my $parent_element_name (keys %$parent_names) {
903        my $parent_class_name = $parent_names->{$parent_element_name};
904        my $old_id = $data->{$parent_element_name . '_id'};
905        my $new_obj = $objects->{"$parent_class_name#$old_id"};
906        next if !(defined($new_obj) && $new_obj);
907        $data->{$parent_element_name . '_id'} = $new_obj->id;
908        $done++;
909    }
910    my $old_tb_id = $data->{'tb_id'};
911    my $new_tb;
912    require MT::Trackback;
913    my $tb = MT::Trackback->load($old_tb_id);
914    if (my $cid = $tb->category_id) {
915        my $new_obj = $objects->{"MT::Category#" . $cid};
916        if (defined($new_obj) && $new_obj) {
917            $new_tb = MT::Trackback->load({ category_id => $new_obj->id });
918            return 0 if (!defined($new_tb) || !$new_tb);
919        }   
920    } elsif (my $eid = $tb->entry_id) {
921        my $new_obj = $objects->{"MT::Entry#" . $eid};
922        if (defined($new_obj) && $new_obj) {
923            $new_tb = MT::Trackback->load({ entry_id => $new_obj->id });
924            return 0 if (!defined($new_tb) || !$new_tb);
925        }
926    }
927    if (defined($new_tb) && $new_tb) {
928        $data->{'tb_id'} = $new_tb->id;
929        $done++;
930    }
931    (scalar(keys(%$parent_names)) == $done) ? 1 : 0;   
932}
933
934package MT::Template;
935
936sub children_names {
937    my $obj = shift;
938    my $children = {
939        fileinfo => 'MT::FileInfo',
940        templatemap => 'MT::TemplateMap',
941    };
942    $children;
943}
944
945sub parent_names {
946    my $obj = shift;
947    my $parents = {
948        blog => 'MT::Blog',
949    };
950    $parents;
951}
952
953package MT::TemplateMap;
954
955sub parent_names {
956    my $obj = shift;
957    my $parents = {
958        blog => 'MT::Blog',
959        template => 'MT::Template',
960    };
961    $parents;
962}
963
964package MT::Trackback;
965
966sub children_names {
967    my $obj = shift;
968    my $children = {
969        tbping => 'MT::TBPing',
970    };
971    $children;
972}
973
974sub children_to_xml {
975    my $obj = shift;
976    my ($namespace, $args) = @_;
977
978    my $t = {};
979    if (defined($args)) {
980        my $j = $args->{'join'};
981        $t = $j->[2] if defined($j);
982    }
983
984    my $xml = '';
985
986    my $terms = { 
987        'tb_id' => $obj->id,
988        %$t,
989    };
990   
991    my $offset = 0;
992    while (1) {
993        my @objects = MT::TBPing->load(
994            $terms,
995            { offset => $offset, limit => 50, }
996        );
997        last unless @objects;
998        $offset += scalar @objects;
999        for my $object (@objects) {
1000            $xml .= $object->to_xml($namespace) . "\n" if $object->to_backup;
1001        }
1002    }
1003    $xml;
1004}
1005
1006sub parent_names {
1007    my $obj = shift;
1008    my $parents = {
1009        entry => 'MT::Entry',
1010        category => 'MT::Category',
1011    };
1012    $parents;
1013}
1014
1015sub restore_parent_ids {
1016    my $obj = shift;
1017    my ($data, $objects) = @_;
1018
1019    my $result = 0;
1020    my $new_blog = $objects->{'MT::Blog#' . $data->{blog_id}};
1021    if ($new_blog) {
1022        $data->{blog_id} = $new_blog->id;
1023    } else {
1024        return 0;
1025    }                           
1026    if ($data->{category_id}) {
1027        my $new_obj = $objects->{'MT::Category#' . $data->{category_id}};
1028        if ($new_obj) {
1029            $data->{category_id} = $new_obj->id;
1030            $result = 1;
1031        }
1032    } elsif ($data->{entry_id}) {
1033        my $new_obj = $objects->{'MT::Entry#' . $data->{entry_id}};
1034        if ($new_obj) {
1035            $data->{entry_id} = $new_obj->id;
1036            $result = 1;
1037        }
1038    }
1039    $result;
1040}
1041
10421;
1043__END__
1044
1045=head1 NAME
1046
1047MT::BackupRestore
1048
1049=head1 METHODS
1050
1051=head2 backup
1052
1053TODO Backup I<MT::Tag>, I<MT::Author>, I<MT::Blog>, I<MT::Role>,
1054I<MT::Category>, I<MT::Asset>, and I<MT::Entry>.  Each object will
1055be back up by MT::Object#to_xml call, which will do the actual
1056Object ==>> XML serialization.
1057
1058=head2 restore_file
1059
1060TODO Restore MT system from an XML file which contains MT backup
1061information (created by backup subroutine).
1062
1063=head2 restore_process_single_file
1064
1065TODO A method which will do the actual heavy lifting of the
1066process to restore objects from an XML file.
1067
1068=head2 restore_directory
1069
1070TODO A method which reads specified directory, find a manifest file,
1071and do the multi-file restore operation directed by the manifest file.
1072
1073=head2 restore_asset
1074
1075TODO A method which restores the assets' actual files to the
1076specified directory.
1077
1078=head2 process_manifest
1079
1080TODO A method which is called from MT::App::CMS to process an uploaded
1081manifest file which is to be the source of the multi-file restore
1082operation in the MT::App::CMS.
1083
1084=head1 Callbacks
1085
1086For plugins which uses MT::Object-derived types, backup and restore
1087operation call callbacks for plugins to inject XMLs so they are
1088also backup, and read XML to restore objects so they are also restored.
1089
1090Callbacks called by the package are as follows:
1091
1092=over 4
1093
1094=item Backup
1095   
1096Calling convention is:
1097
1098    callback($cb, $blog_ids)
1099
1100The callback is used for MT::Object-derived types used by plugins
1101to be backup.  The callback must return the object's XML representation
1102in a string, or 1 for nothing.  $blog_ids has an ARRAY reference to
1103blog_ids which indicates what weblog a user chose to backup.  It may
1104be an empty array if a user chose Everything.
1105
1106=item Restore
1107
1108Restore callbacks are called in convention like below:
1109
1110    MT->run_callbacks("Restore.<xml_namespace>",
1111        $data, $objects, $deferred, $callback);
1112
1113Where $data is a parameter which was passed to XML::SAX::Base's
1114start_element callback method.
1115
1116$objects is an hash reference which contains all the restored objects
1117in the restore session.  The hash keys are stored in the format
1118MT::ObjectClassName#old_id, and hash values are object reference
1119of the actually restored objects (with new id).  Old ids are ids
1120which are stored in the XML files, while new ids are ids which
1121are restored.
1122
1123$deferred is an hash reference which contains information about
1124restore-deferred objects.  Deferred objects are those objects
1125which appeared in the XMl file but could not be restored because
1126any parent objects are missing.  The hash keys are stored in
1127the format MT::ObjectClassName#old_id and hash values are 1.
1128
1129$callback is a code reference which will print out the passed paramter.
1130Callback method can use this to communicate with users.
1131
1132If a plugin has an MT::Object derived type, the plugin will register
1133a callback to Restore.<xmlnamespace> callback, so later the restore
1134operation will call the callback function with parameters described above.
1135XML Namespace is required to be registered, so an xml node can be resolved
1136into what plugins to be called back, and can be distinguished the same
1137element name with each other.
1138
1139=head1 AUTHOR & COPYRIGHT
1140
1141Please see L<MT/AUTHOR & COPYRIGHT>.
1142
1143=cut
Note: See TracBrowser for help on using the browser.