root/branches/athena/lib/MT/BackupRestore.pm @ 1092

Revision 1092, 29.2 kB (checked in by hachi, 2 years ago)

Merging release-15 to athena branch. svn merge -r59987:60375 http://svn.sixapart.com/repos/eng/movabletype/branches/release-15 .

  • Property svn:keywords set to Id Author Date 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 base qw( MT::ErrorHandler );
12
13use constant NS_MOVABLETYPE => 'http://www.sixapart.com/ns/movabletype';
14
15use File::Spec;
16use File::Copy;
17
18sub _populate_obj_to_backup {
19    my $pkg = shift;
20    my ($blog_ids, $skip, $later) = @_;
21
22    my %populated;
23    if ( defined($blog_ids) && scalar(@$blog_ids) ) {
24        # author will be handled at last
25        $populated{MT->model('author')} = 1;
26    }
27
28    my @obj_to_backup;
29    my $types = MT->registry('object_types');
30    foreach my $key (keys %$types) {
31        next if $key =~ /\w+\.\w+/; # skip subclasses
32        my $class = MT->model($key);
33        next unless $class;
34        next if $class eq $key; # FIXME: to remove plugin object_classes
35        next if exists $skip->{$class};
36        next if exists $later->{$class};
37        next if exists $populated{$class};
38        $pkg->_create_obj_to_backup(
39            $class, $blog_ids, \@obj_to_backup, \%populated, $later);
40    }
41    foreach my $class (keys %$later) {
42        next if exists $skip->{$class};
43        $pkg->_create_obj_to_backup(
44            $class, $blog_ids, \@obj_to_backup, \%populated, {});
45    }
46
47    if ( defined($blog_ids) && scalar(@$blog_ids) ) {
48        # Author has two ways to be associated to a blog
49        my $class = MT->model('author');
50        unshift @obj_to_backup, {$class => { 
51            terms => undef, 
52            args => { 'join' => 
53                [ MT->model('association'), 'author_id', { blog_id => $blog_ids }, { unique => 1 } ] 
54            }}}; 
55        unshift @obj_to_backup, {$class => { 
56            terms => undef, 
57            args => { 'join' => 
58                [ MT->model('permission'), 'author_id', { blog_id => $blog_ids }, { unique => 1 } ] 
59            }}}; 
60    }
61    return \@obj_to_backup;
62}
63
64sub _create_obj_to_backup {
65    my $pkg = shift;
66    my ($class, $blog_ids, $obj_to_backup, $populated, $later) = @_;
67
68    my $columns = $class->column_names;
69    foreach my $column (@$columns) {
70        if ( $column =~ /^(\w+)_id$/ ) {
71            my $parent = $1;
72            my $p_class = MT->model($parent);
73            next unless $p_class;
74            next if exists $later->{$p_class};
75            next if exists $populated->{$p_class};
76            $pkg->_create_obj_to_backup(
77                $p_class, $blog_ids, $obj_to_backup, $populated, $later);
78        }
79    }
80   
81    push @$blog_ids, '0'
82        if defined($blog_ids) && scalar(@$blog_ids);
83    if ( $class->can('backup_terms_args') ) {
84        push @$obj_to_backup, { $class => $class->backup_terms_args($blog_ids) };
85    }
86    else {
87        push @$obj_to_backup, $pkg->_default_terms_args($class, $blog_ids);
88    }
89
90    $populated->{$class} = 1;
91}
92
93sub _default_terms_args {
94    my $pkg = shift;
95    my ($class, $blog_ids) = @_;
96
97    if (defined($blog_ids) && scalar(@$blog_ids)) {
98        return { $class =>
99          {
100            terms => { 'blog_id' => $blog_ids }, 
101            args => undef
102          }
103        };
104    }
105    else {
106        return
107          {
108            $class => { terms => undef, args => undef }
109          };
110    }
111}
112   
113
114sub backup {
115    my $class = shift;
116    my ($blog_ids, $printer, $splitter, $finisher, $progress, $size, $enc, $metadata) = @_;
117    my $obj_to_backup = $class->_populate_obj_to_backup(
118        $blog_ids,
119        {
120            MT->model('config') => 1, 
121            MT->model('session') => 1,
122            MT->model('ts_job') => 1,
123            MT->model('ts_error') => 1,
124            MT->model('ts_exitstatus') => 1,
125            MT->model('ts_funcmap') => 1,
126        },
127        {
128            MT->model('placement') => 1, 
129            MT->model('ping') => 1, 
130            MT->model('objecttag') => 1, 
131            MT->model('objectasset') => 1, 
132            MT->model('objectscore') => 1,
133        }
134    );
135
136    my $header .= "<movabletype xmlns='" . NS_MOVABLETYPE . "'\n";
137    $header .= join ' ', map { $_ . "='" . $metadata->{$_} . "'" } keys %$metadata;
138    $header .= ">\n";
139    $header = "<?xml version='1.0' encoding='$enc'?>\n$header" if $enc !~ m/utf-?8/i;
140    $printer->($header);
141
142    my $files = {};
143    _loop_through_objects(
144        $printer, $splitter, $finisher, $progress, $size, $obj_to_backup, $files);
145
146    my $else_xml = MT->run_callbacks('Backup', $blog_ids, $progress);
147    $printer->($else_xml) if $else_xml ne '1';
148
149    $printer->('</movabletype>');
150    $finisher->($files);
151}
152
153sub _loop_through_objects {
154    my ($printer, $splitter, $finisher, $progress, $size, $obj_to_backup, $files) = @_;
155
156    my $counter = 1;
157    my $bytes = 0;
158    my %author_ids_seen;
159    for my $class_hash (@$obj_to_backup) {
160        my ($class, $term_arg) = each(%$class_hash);
161        eval "require $class;";
162        my $children = $class->properties->{child_classes} || {};
163        for my $child_class (keys %$children) {
164            eval "require $child_class;";
165        }
166        if (my $err = $@) {
167            $progress->("$err\n", 'Error');
168            next;
169        }
170        my $records = 0;
171        my $state = MT->translate('Backing up [_1] records:', $class);
172        $progress->($state, $class->class_type || $class->datasource);
173        my $offset = 0;
174        my $terms = $term_arg->{terms} || {};
175        my $args = $term_arg->{args};
176        while (1) {
177            $args->{offset} = $offset;
178            $args->{limit} = 50;
179            my @objects;
180            eval {
181                @objects = $class->load($terms, $args);
182            };
183            if (my $err = $@) {
184                $progress->("$class:$err\n", 'Error');
185            }
186            last unless @objects;
187            $offset += scalar @objects;
188            for my $object (@objects) {
189                if ( ( $class eq MT->model('author') )
190                  && ( exists $author_ids_seen{$object->id} ) ) {
191                    next;
192                }
193                $bytes += $printer->($object->to_xml(undef, $args) . "\n");
194                $records++;
195                if ($size && ($bytes >= $size)) {
196                    $splitter->(++$counter);
197                    $bytes = 0;
198                }
199                if ( $class eq MT->model('author') ) {
200                    # Authors may be duplicated because of how terms and args are created.
201                    $author_ids_seen{$object->id} = 1;
202                } elsif ( $class->datasource eq 'asset' ) {
203                    $files->{$object->id} = [$object->url, $object->file_path, $object->file_name];
204                }
205            }
206            $progress->($state . " " . MT->translate("[_1] records backed up...", $records), $class->datasource)
207                if $records && ($records % 100 == 0);
208        }
209        if ($records) {
210            $progress->($state . " " . MT->translate("[_1] records backed up.", $records), $class->class_type || $class->datasource);
211        } else {
212            $progress->($state . " " . MT->translate("There were no [_1] records to be backed up.", $class), $class->class_type || $class->datasource);
213        }
214    }
215}
216
217sub restore_file {
218    my $class = shift;
219    my ($fh, $errormsg, $schema_version, $callback) = @_;
220
221    my $objects = {};
222    my $deferred = {};
223    my $errors = [];
224
225    my ($blog_ids, $asset_ids) = eval { $class->restore_process_single_file(
226        $fh, $objects, $deferred, $errors, $schema_version, $callback
227    ); };
228    $$errormsg = join('; ', @$errors);
229    #my @blog_ids;
230    #while (my ($key, $value) = each %$objects) {
231    #    if ($key =~ /MT::Blog#/) {
232    #        push @blog_ids, $value->id;
233    #    }
234    #}
235    ($deferred, $blog_ids);
236}
237 
238sub restore_process_single_file {
239    my $class = shift;
240    my ($fh, $objects, $deferred, $errors, $schema_version, $callback) = @_;
241   
242    my %restored_blogs = map { $objects->{$_}->id => 1; } grep { 'blog' eq $objects->{$_}->datasource } keys %$objects;
243
244    require XML::SAX;
245    require MT::BackupRestore::BackupFileHandler;
246    my $handler = MT::BackupRestore::BackupFileHandler->new(
247        callback => $callback,
248        objects => $objects,
249        deferred => $deferred,
250        errors => $errors,
251        schema_version => $schema_version,
252    );
253
254    require MT::Util;
255    my $parser = MT::Util::sax_parser();
256    $callback->(ref($parser) . "\n") if MT->config->DebugMode;
257    $parser->{Handler} = $handler;
258    eval { $parser->parse_file($fh); };
259    if (my $e = $@) {
260        push @$errors, $e;
261        $callback->($e);
262        die $e if $handler->{critical}; 
263    }
264
265    my @blog_ids;
266    my @asset_ids;
267
268    while (my ($key, $value) = each %$objects) {
269        if ('blog' eq $value->datasource) {
270            push @blog_ids, $value->id unless exists $restored_blogs{$value->id};
271        } elsif ('asset' eq $value->datasource) {
272            my ($old_id) = $key =~ /^.+#(\d+)$/;
273            push @asset_ids, $value->id, $old_id;
274        }
275    }
276    my $blog_ids = scalar(@blog_ids) ? \@blog_ids : undef;
277    my $asset_ids = scalar(@asset_ids) ? \@asset_ids : undef;
278    ($blog_ids, $asset_ids);
279}
280
281sub restore_directory {
282    my $class = shift;
283    my ($dir, $errors, $error_assets, $schema_version, $callback) = @_;
284
285    my $manifest;
286    my @files;
287    opendir my $dh, $dir or push(@$errors, MT->translate("Can't open directory '[_1]': [_2]", $dir, "$!")), return undef;
288    for my $f (readdir $dh) {
289        next if $f !~ /^.+\.manifest$/i;
290        $manifest = File::Spec->catfile($dir, $f);
291        last;
292    }
293    closedir $dh;
294    unless ($manifest) {
295        push @$errors, MT->translate("No manifest file could be found in your import directory [_1].", $dir);
296        return (undef, undef);
297    }
298
299    my $fh = gensym;
300    open $fh, "<$manifest" or push(@$errors, MT->translate("Can't open [_1].", $manifest)), return 0;
301    my $backups = __PACKAGE__->process_manifest($fh);
302    close $fh;
303    unless($backups) {
304        push @$errors, MT->translate("Manifest file [_1] was not a valid Movable Type backup manifest file.", $manifest);
305        return (undef, undef);
306    }
307
308    $callback->(MT->translate("Manifest file: [_1]", $manifest) . "\n");
309
310    my %objects;
311    my $deferred = {};
312
313    my $files = $backups->{files};
314    my @blog_ids;
315    my @asset_ids;
316    for my $file (@$files) {
317        my $fh = gensym;
318        my $filepath = File::Spec->catfile($dir, $file);
319        open $fh, "<$filepath" or push @$errors, MT->translate("Can't open [_1]."), next;
320
321        my ($tmp_blog_ids, $tmp_asset_ids) = eval { __PACKAGE__->restore_process_single_file(
322            $fh, \%objects, $deferred, $errors, $schema_version, $callback); };
323
324        close $fh;
325        last if $@;
326
327        push @blog_ids, @$tmp_blog_ids if defined $tmp_blog_ids;
328        push @asset_ids, @$tmp_asset_ids if defined $tmp_asset_ids;
329    }
330    my $blog_ids = scalar(@blog_ids) ? \@blog_ids : undef;
331    my $asset_ids = scalar(@asset_ids) ? \@asset_ids : undef;
332    ($deferred, $blog_ids, $asset_ids);
333}
334
335sub process_manifest {
336    my $class = shift;
337    my ($stream) = @_;
338
339    if ((ref($stream) eq 'Fh') || (ref($stream) eq 'GLOB')){
340        seek($stream, 0, 0) or return undef;
341        require XML::SAX;
342        require MT::BackupRestore::ManifestFileHandler;
343        my $handler = MT::BackupRestore::ManifestFileHandler->new();
344
345        require MT::Util;
346        my $parser = MT::Util::sax_parser();
347        $parser->{Handler} = $handler;
348        eval { $parser->parse_file($stream); };
349        if (my $e = $@) {
350            die $e;
351        }
352        return $handler->{backups};
353    }
354    return undef;
355}
356
357sub restore_asset {
358    my $class = shift;
359    my ($file, $asset, $old_id, $fmgr, $errors, $callback) = @_;
360
361    my $id = $asset->id;
362
363    my $path = $asset->file_path;
364    unless (defined($path)) {
365        $callback->(MT->translate('Path was not found for the file ([_1]).', $id));
366        return 0;
367    }
368    my ($vol, $dir, $fn) = File::Spec->splitpath($path);
369    my $voldir =  "$vol$dir";
370    if (!-w $voldir) {
371        unless (defined $fmgr) {
372            # we do need utf8_off here
373            $errors->{$id} = MT->translate('[_1] is not writable.', MT::I18N::utf8_off($voldir)) ;
374        } else {
375            $voldir =~ s|/$|| unless $voldir eq '/';  ## OS X doesn't like / at the end in mkdir().
376            unless ($fmgr->exists($voldir)) {
377                $fmgr->mkpath($voldir) or
378                    $errors->{$id} = MT->translate("Error making path '[_1]': [_2]", $path, $fmgr->errstr);
379            }
380        }
381    }
382    if (-w $voldir) {
383        my $filename = "$old_id-" . $asset->file_name;
384        $callback->(MT->translate("Copying [_1] to [_2]...", $filename, $path));
385        copy($file, $path)
386            or $errors->{$id} = $!;
387    }
388
389    $callback->(exists($errors->{$id}) ?
390        MT->translate('Failed: ') . $errors->{$id} :
391        MT->translate("Done.")
392    );
393    $callback->("\n");
394}
395
396sub _restore_asset_multi {
397    my $class = shift;
398    my ($asset_element, $objects, $errors, $callback, $blogs_meta) = @_;
399
400    my $old_id = $asset_element->{asset_id};
401    if (!defined($old_id)) {
402        $callback->(MT->translate('ID for the file was not set.'));
403        return 0;
404    }
405    my $asset_class = MT->model('asset');
406    my $asset = $objects->{"$asset_class#$old_id"};
407    unless (defined($asset)) {
408        $callback->(MT->translate('The file ([_1]) was not restored.', $old_id));
409        return 0;
410    }
411
412    my $fmgr;
413    if (exists $blogs_meta->{$asset->blog_id}) {
414        my $meta = $blogs_meta->{$asset->blog_id};
415        my $path = $asset->file_path;
416        my $url = $asset->url;
417        if (my $archive_path = $meta->{'archive_path'}) {
418            my $old_archive_path = $meta->{'old_archive_path'};
419            $path =~ s/\Q$old_archive_path\E/$archive_path/i;
420            $asset->file_path($path);
421        }
422        if (my $archive_url = $meta->{'archive_url'}) {
423            my $old_archive_url = $meta->{'old_archive_url'};
424            $url =~ s/\Q$old_archive_url\E/$archive_url/i;
425            $asset->url($url);
426        }
427        if (my $site_path = $meta->{'site_path'}) {
428            my $old_site_path = $meta->{'old_site_path'};
429            $path =~ s/\Q$old_site_path\E/$site_path/i;
430            $asset->file_path($path);
431        }
432        if (my $site_url = $meta->{'site_url'}) {
433            my $old_site_url = $meta->{'old_site_url'};
434            $url =~ s/\Q$old_site_url\E/$site_url/i;
435            $asset->url($url);
436        }
437        $callback->(MT->translate("Changing path for the file '[_1]' (ID:[_2])...", $asset->label, $asset->id));
438        $asset->save or $callback->(MT->translate("failed") . "\n");
439        $callback->(MT->translate("ok") . "\n");
440
441        my $blog = MT->model('blog')->load($asset->blog_id);
442        $fmgr = $blog->file_mgr;
443    }
444    my $file = $asset_element->{fh};
445    $class->restore_asset($file, $asset, $old_id, $fmgr, $errors, $callback);
446}
447
448package MT::Object;
449
450sub _is_element {
451    my $obj = shift;
452    my ($def) = @_;
453    return (('text' eq $def->{type}) || (('string' eq $def->{type}) && (255 < $def->{size}))) ? 1 : 0;
454}
455
456sub to_xml {
457    my $obj = shift;
458    my ($namespace, $args) = @_;
459
460    my $coldefs = $obj->column_defs;
461    my $colnames = $obj->column_names;
462    my $xml;
463
464    my $elem = $obj->datasource;
465    $xml = '<' . $elem;
466    $xml .= " xmlns='$namespace'" if defined($namespace) && $namespace;
467
468    my (@elements, @blobs, @meta);
469    for my $name (@$colnames) {
470        if ($obj->column($name) || (defined($obj->column($name)) && ('0' eq $obj->column($name)))) {
471            if ( ( $obj->properties->{meta_column} || '' ) eq $name ) {
472                push @meta, $name;
473                next;
474            }
475            elsif ($obj->_is_element($coldefs->{$name})) {
476                push @elements, $name;
477                next;
478            } elsif ('blob' eq $coldefs->{$name}->{type}) {
479                push @blobs, $name;
480                next;
481            }
482            $xml .= " $name='" . MT::Util::encode_xml($obj->column($name), 1) . "'";
483        }
484    }
485    $xml .= '>';
486    $xml .= "<$_>" . MT::Util::encode_xml($obj->column($_), 1) . "</$_>" foreach @elements;
487    require MIME::Base64;
488    $xml .= "<$_>" . MIME::Base64::encode_base64($obj->column($_), '') . "</$_>" foreach @blobs;
489    foreach my $meta_col (@meta) {
490        my $hashref = $obj->$meta_col;
491        $xml .= "<$meta_col>" . 
492                MIME::Base64::encode_base64(MT::Serialize->serialize(\$hashref), '') .
493                "</$meta_col>";
494    }
495    $xml .= '</' . $elem . '>';
496    $xml;
497}
498
499sub parents {
500    my $obj = shift;
501    {};
502}
503
504sub _restore_id {
505    my $obj = shift;
506    my ($key, $val, $data, $objects) = @_;
507
508    return 0 unless 'ARRAY' eq ref($val);
509    return 1 if 0 == $data->{$key}; 
510
511    my $new_obj;
512    my $old_id = $data->{$key};
513    foreach (@$val) {
514        $new_obj = $objects->{"$_#$old_id"};
515        last if $new_obj;
516    }
517    return 0 unless $new_obj;
518    $data->{$key} = $new_obj->id;
519    return 1;
520}
521
522sub restore_parent_ids {
523    my $obj = shift;
524    my ($data, $objects) = @_;
525
526    my $parents = $obj->parents;
527    my $count = scalar(keys %$parents);
528
529    my $done = 0;
530    while (my ($key, $val) = each(%$parents)) {
531        $val = [ $val ] unless (ref $val);
532        if ('ARRAY' eq ref($val)) {
533            $done += $obj->_restore_id($key, $val, $data, $objects);
534        }
535        elsif ('HASH' eq ref($val)) {
536            my $v = $val->{class};
537            $v = [ $v ] unless (ref $v);
538            my $result = 0;
539            if (my $relations = $val->{relations}) {
540                my $col = $relations->{key};
541                my $ds = $data->{$col};
542                my $ev = $relations->{$ds . '_id'};
543                $ev = MT->model($ds) unless $ev;
544                return 0 unless $ev;
545                $ev = [ $ev ] unless (ref $ev);
546                $done += $obj->_restore_id($key, $ev, $data, $objects);
547            }
548            else {
549                $result = $obj->_restore_id($key, $v, $data, $objects);
550                $result = 1 if exists($val->{optional}) && $val->{optional};
551                $data->{$key} = -1 
552                  if !$result && (exists($val->{orphanize}) && $val->{orphanize});
553                $done += $result;
554            }
555        }
556    }
557    ($count == $done) ? 1 : 0;   
558}
559
560package MT::Blog;
561
562sub backup_terms_args {
563    my $class = shift;
564    my ($blog_ids) = @_;
565
566    if ( defined($blog_ids) && scalar(@$blog_ids) ) {
567        return
568          {
569            terms => { id => $blog_ids }, 
570            args => undef,
571          };
572    }
573    else {
574        return { terms => undef, args => undef };
575    }
576}
577
578package MT::Tag;
579
580sub backup_terms_args {
581    my $class = shift;
582    my ($blog_ids) = @_;
583
584    if ( defined($blog_ids) && scalar(@$blog_ids) ) {
585        return
586          {
587            terms => undef, 
588            args =>
589              {
590                'join' =>
591                  [
592                    'MT::ObjectTag', 
593                    'tag_id',
594                    { blog_id => $blog_ids }, 
595                    { unique => 1 }
596                  ]
597              }
598          };
599    }
600    else {
601        return { terms => undef, args => undef };
602    }
603}
604
605package MT::Role;
606
607sub backup_terms_args {
608    my $class = shift;
609    my ($blog_ids) = @_;
610
611    if ( defined($blog_ids) && scalar(@$blog_ids) ) {
612        return
613          {
614            terms => undef, 
615            args =>
616              {
617                'join' =>
618                  [
619                    'MT::Association',
620                    'role_id',
621                    { blog_id => $blog_ids },
622                    { unique => 1 }
623                  ]
624              }
625          };
626    }
627    else {
628        return { terms => undef, args => undef };
629    }
630}
631
632package MT::Asset;
633
634sub backup_terms_args {
635    my $class = shift;
636    my ($blog_ids) = @_;
637
638    if ( defined($blog_ids) && scalar(@$blog_ids) ) {
639        return
640          {
641            terms => { 'blog_id' => $blog_ids, 'class' => '*' }, 
642            args => undef
643          }
644    }
645    else {
646        return { terms => { 'class' => '*' }, args => undef };
647    }
648}
649
650my $assets_seen = {};
651
652sub to_xml {
653    my $obj = shift;
654    my $xml = q();
655
656    return $xml if exists $assets_seen->{$obj->id};
657
658    if ($obj->parent) {
659        my $parent = MT->model('asset')->load($obj->parent);
660        $xml .= $parent->to_xml(@_);
661        $xml .= "\n";
662    }
663
664    $xml .= $obj->SUPER::to_xml(@_);
665    $assets_seen->{$obj->id} = 1;
666    $xml;
667}
668
669sub parents {
670    my $obj = shift;
671    {
672        blog_id => MT->model('blog'),
673        parent  => MT->model('asset')
674    };
675}
676
677package MT::PluginData;
678
679sub backup_terms_args {
680    my $class = shift;
681    my ($blog_ids) = @_;
682
683    return { terms => undef, args => undef };
684}
685
686sub restore_parent_ids {
687    my $obj = shift;
688    my ($data, $objects) = @_;
689
690    if ($data->{key} =~ /^configuration:blog:(\d+)$/i) {
691        my $new_blog = $objects->{'MT::Blog#' . $1};
692        if ($new_blog) {
693            $data->{key} = 'configuration:blog:' . $new_blog->id;
694        }
695    }
696    return 1;
697}
698
699package MT::Association;
700
701sub restore_parent_ids {
702    my $obj = shift;
703    my ($data, $objects) = @_;
704
705    my ($u, $b, $g, $r) = (0, 0, 0, 0);
706
707    my $processor = sub {
708        my ($elem) = @_;
709        my $class = MT->model($elem);
710        my $old_id = $data->{$elem . '_id'};
711        my $new_obj = $objects->{"$class#$old_id"};
712        return 0 unless defined($new_obj) && $new_obj;
713        $data->{$elem . '_id'} = $new_obj->id;
714        return 1;
715    };
716
717    $u = $processor->('author');
718    $g = $processor->('group');
719    $b = $processor->('blog');
720    $r = $processor->('role');
721
722    # Combination allowed are:
723    # USER_BLOG_ROLE  => 1;
724    # GROUP_BLOG_ROLE => 2;
725    # USER_GROUP      => 3;
726    # USER_ROLE       => 4;
727    # GROUP_ROLE      => 5;
728
729    ($u && $g) || ($u && $r) || ($g && $r) ? 1 : 0; # || ($u && $b && $r) || ($g && $b && $r)
730}
731
732package MT::Category;
733
734my $category_seen = {};
735
736sub to_xml {
737    my $obj = shift;
738    my $xml = q();
739
740    return $xml if exists $category_seen->{$obj->id};
741   
742    if ('0' ne $obj->parent) {
743        $xml .= $obj->parent_category->to_xml(@_);
744        $xml .= "\n";
745    }
746
747    $xml .= $obj->SUPER::to_xml(@_);
748    $category_seen->{$obj->id} = 1;
749    $xml;
750}
751
752sub parents {
753    my $obj = shift;
754    {
755        blog_id => MT->model('blog'),
756        parent  => [ MT->model('category'), MT->model('folder') ],
757    };
758}
759
760package MT::Comment;
761
762sub parents {
763    my $obj = shift;
764    {
765        entry_id => [ MT->model('entry'), MT->model('page') ],
766        blog_id => MT->model('blog'),
767        commenter_id => { class => MT->model('author'), optional => 1 },
768    };
769}
770
771package MT::Entry;
772
773sub parents {
774    my $obj = shift;
775    {
776        blog_id => MT->model('blog'),
777        author_id => { class => MT->model('author'), optional => 1, orphanize => 1 },
778    };
779}
780
781package MT::Notification;
782
783sub parents {
784    my $obj = shift;
785    {
786        blog_id => MT->model('blog'),
787    };
788}
789
790package MT::ObjectTag;
791
792sub parents {
793    my $obj = shift;
794    {
795        blog_id => MT->model('blog'),
796        tag_id => MT->model('tag'),
797        object_id => { relations => {
798            key => 'object_datasource',
799            entry_id => [ MT->model('entry'), MT->model('page') ],
800        }}
801    };
802}
803
804package MT::Permission;
805
806sub parents {
807    my $obj = shift;
808    {
809        blog_id => { class => MT->model('blog'), optional => 1 },
810        author_id => { class => MT->model('author'), optional => 1 },
811    };
812}
813
814package MT::Placement;
815
816sub parents {
817    my $obj = shift;
818    {
819        category_id => [ MT->model('category'), MT->model('folder') ],
820        blog_id => MT->model('blog'),
821        entry_id => [ MT->model('entry'), MT->model('page') ],
822    };
823}
824
825package MT::TBPing;
826
827sub parents {
828    my $obj = shift;
829    {
830        blog_id => MT->model('blog'),
831        tb_id => MT->model('trackback'),
832    };
833}
834
835package MT::Template;
836
837sub parents {
838    my $obj = shift;
839    {
840        blog_id => MT->model('blog'),
841    };
842}
843
844package MT::TemplateMap;
845
846sub parents {
847    my $obj = shift;
848    {
849        blog_id => MT->model('blog'),
850        template_id  => MT->model('template')
851    };
852}
853
854package MT::Trackback;
855
856sub restore_parent_ids {
857    my $obj = shift;
858    my ($data, $objects) = @_;
859
860    my $result = 0;
861    my $blog_class = MT->model('blog');
862    my $new_blog = $objects->{$blog_class . '#' . $data->{blog_id}};
863    if ($new_blog) {
864        $data->{blog_id} = $new_blog->id;
865    } else {
866        return 0;
867    }                           
868    if (my $cid = $data->{category_id}) {
869        my $cat_class = MT->model('category');
870        my $new_obj = $objects->{$cat_class . '#' . $cid};
871        unless ($new_obj) {
872            $cat_class = MT->model('folder');
873            $new_obj = $objects->{$cat_class . '#' . $cid};
874        }
875        if ($new_obj) {
876            $data->{category_id} = $new_obj->id;
877            $result = 1;
878        }
879    } elsif (my $eid = $data->{entry_id}) {
880        my $entry_class = MT->model('entry');
881        my $new_obj = $objects->{$entry_class . '#' . $eid};
882        unless ($new_obj) {
883            $entry_class = MT->model('page');
884            $new_obj = $objects->{$entry_class . '#' . $eid};
885        }
886        if ($new_obj) {
887            $data->{entry_id} = $new_obj->id;
888            $result = 1;
889        }
890    }
891    $result;
892}
893
894package MT::ObjectAsset;
895
896sub parents {
897    my $obj = shift;
898    {
899        blog_id => MT->model('blog'),
900        asset_id => MT->model('asset'),
901        object_id => { relations => {
902            key => 'object_ds',
903            entry_id => [ MT->model('entry'), MT->model('page') ],
904        }}
905    };
906}
907
908package MT::ObjectScore;
909
910sub backup_terms_args {
911    my $class = shift;
912    my ($blog_ids) = @_;
913
914    return { terms => undef, args => undef };
915}
916
917sub parents {
918    my $obj = shift;
919    {
920        author_id => MT->model('author'),
921        object_id => { relations => {
922            key => 'object_ds',
923            entry_id => [ MT->model('entry'), MT->model('page') ],
924        }}
925    };
926}
927
9281;
929__END__
930
931=head1 NAME
932
933MT::BackupRestore
934
935=head1 METHODS
936
937=head2 backup
938
939TODO Backup I<MT::Tag>, I<MT::Author>, I<MT::Blog>, I<MT::Role>,
940I<MT::Category>, I<MT::Asset>, and I<MT::Entry>.  Each object will
941be back up by MT::Object#to_xml call, which will do the actual
942Object ==>> XML serialization.
943
944=head2 restore_file
945
946TODO Restore MT system from an XML file which contains MT backup
947information (created by backup subroutine).
948
949=head2 restore_process_single_file
950
951TODO A method which will do the actual heavy lifting of the
952process to restore objects from an XML file.  Returns array of blog_ids
953which are restored in the very session, and hash of asset_ids.
954
955=head2 restore_directory
956
957TODO A method which reads specified directory, find a manifest file,
958and do the multi-file restore operation directed by the manifest file.
959
960=head2 restore_asset
961
962TODO A method which restores the assets' actual files to the
963specified directory.  It also creates subdirectory by request.
964
965=head2 process_manifest
966
967TODO A method which is called from MT::App::CMS to process an uploaded
968manifest file which is to be the source of the multi-file restore
969operation in the MT::App::CMS.
970
971=head1 Callbacks
972
973For plugins which uses MT::Object-derived types, backup and restore
974operation call callbacks for plugins to inject XMLs so they are
975also backup, and read XML to restore objects so they are also restored.
976
977Callbacks called by the package are as follows:
978
979=over 4
980
981=item Backup
982   
983Calling convention is:
984
985    callback($cb, $blog_ids, $progress)
986
987The callback is used for MT::Object-derived types used by plugins
988to be backup.  The callback must return the object's XML representation
989in a string, or 1 for nothing.  $blog_ids has an ARRAY reference to
990blog_ids which indicates what weblog a user chose to backup.  It may
991be an empty array if a user chose Everything.  $progress is a CODEREF
992used to report progress to the user.
993
994If a plugin has an MT::Object derived type, the plugin will register
995a callback to Backup callback, and Backup process will call the callbacks
996to give plugins a chance to add their own data to the backup file.
997Otherwise, plugin's object classes is likely be ignored in backup operation.
998
999=item Restore
1000
1001Restore callbacks are called in convention like below:
1002
1003    callback($cb, $data, $objects, $deferred, $callback);
1004
1005Where $data is a parameter which was passed to XML::SAX::Base's
1006start_element callback method.
1007
1008$objects is an hash reference which contains all the restored objects
1009in the restore session.  The hash keys are stored in the format
1010MT::ObjectClassName#old_id, and hash values are object reference
1011of the actually restored objects (with new id).  Old ids are ids
1012which are stored in the XML files, while new ids are ids which
1013are restored.
1014
1015$deferred is an hash reference which contains information about
1016restore-deferred objects.  Deferred objects are those objects
1017which appeared in the XMl file but could not be restored because
1018any parent objects are missing.  The hash keys are stored in
1019the format MT::ObjectClassName#old_id and hash values are 1.
1020
1021$callback is a code reference which will print out the passed paramter.
1022Callback method can use this to communicate with users.
1023
1024If a plugin has an MT::Object derived type, the plugin will register
1025a callback to Restore.<element_name>:<xmlnamespace> callback,
1026so later the restore operation will call the callback function with
1027parameters described above.  XML Namespace is required to be registered,
1028so an xml node can be resolved into what plugins to be called back,
1029and can be distinguished the same element name with each other.
1030
1031=head1 AUTHOR & COPYRIGHT
1032
1033Please see L<MT/AUTHOR & COPYRIGHT>.
1034
1035=cut
Note: See TracBrowser for help on using the browser.