root/branches/release-35/lib/MT/BackupRestore/BackupFileHandler.pm @ 1948

Revision 1948, 13.8 kB (checked in by fumiakiy, 20 months ago)

New metadata structure is now backup- and restore-able. BugId:79317

  • Property svn:keywords set to Id Author Date Revision
Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT::BackupRestore::BackupFileHandler;
8
9use strict;
10use XML::SAX::Base;
11use MIME::Base64;
12
13@MT::BackupRestore::BackupFileHandler::ISA = qw(XML::SAX::Base);
14
15sub new {
16    my $class = shift;
17    my (%param) = @_;
18    my $self = bless \%param, $class;
19    return $self;
20}
21
22sub start_document {
23    my $self = shift;
24    my $data = shift;
25
26    $self->{start} = 1;
27
28    1;
29}
30
31sub start_element {
32    my $self = shift;
33    my $data = shift;
34
35    return if $self->{skip};
36
37    my $name = $data->{LocalName};
38    my $attrs = $data->{Attributes};
39    my $ns = $data->{NamespaceURI};
40
41    if ($self->{start}) {
42        die MT->translate('Uploaded file was not a valid Movable Type backup manifest file.')
43            if !(('movabletype' eq $name) && (MT::BackupRestore::NS_MOVABLETYPE() eq $ns));
44        unless ($self->{ignore_schema_conflicts}) {
45            my $schema = $attrs->{'{}schema_version'}->{Value};
46            if (('ignore' ne $self->{schema_version}) && ($schema > $self->{schema_version})) {
47                $self->{critical} = 1;
48                my $message = MT->translate('Uploaded file was backed up from Movable Type with the newer schema version ([_1]) than the one in this system ([_2]).  It is not safe to restore the file to this version of Movable Type.', MT::I18N::encode_text(MT::I18N::utf8_off($schema), 'utf-8'), $self->{schema_version});
49                MT->log({ 
50                    message => $message,
51                    level => MT::Log::ERROR(),
52                    class => 'system',
53                    category => 'restore',
54                });
55                die $message;
56            }
57        }
58        $self->{start} = 0;
59        return 1;
60    }
61
62    my $objects = $self->{objects};
63    my $deferred = $self->{deferred};
64    my $callback = $self->{callback};
65
66    if (my $current = $self->{current}) {
67        # this is an element for a text column of the object
68        $self->{current_text} = [ $name ];
69    } else {
70        if (MT::BackupRestore::NS_MOVABLETYPE() eq $ns) {
71            my $class = MT->model($name);
72            unless ($class) {
73                push @{$self->{errors}}, MT->translate('[_1] is not a subject to be restored by Movable Type.', $name);
74            } else {
75                if ($self->{current_class} ne $class) {
76                    if (my $c = $self->{current_class}) {
77                        my $state = $self->{state};
78                        my $records = $self->{records};
79                        $callback->($state . " " . MT->translate("[_1] records restored.", $records), $c->class_type || $c->datasource);
80                    }
81                    $self->{records} = 0;
82                    $self->{current_class} = $class;
83                    my $state = MT->translate('Restoring [_1] records:', $class);
84                    $callback->($state, $name);
85                    $self->{state} = $state;
86                }
87                my %column_data = map { $attrs->{$_}->{LocalName} => 
88                        MT::I18N::encode_text(MT::I18N::utf8_off($attrs->{$_}->{Value}), 'utf-8')
89                    } keys(%$attrs);
90                my $obj;
91                if ( 'author' eq $name ) {
92                    $obj = $class->load({ name => $column_data{name} });
93                    if ($obj) {
94                        if ( UNIVERSAL::isa('MT::App', MT->instance)
95                          && ( $obj->id == MT->instance->user->id ) ) {
96                            MT->log({ message => MT->translate(
97                                "User with the same name as the name of the currently logged in ([_1]) found.  Skipped the record.", $obj->name),
98                                level => MT::Log::INFO(),
99                                metadata => 'Permissions and Associations have been restored.',
100                                class => 'system',
101                                category => 'restore',
102                            });
103                            $objects->{"$class#" . $column_data{id}} = $obj;
104                            $self->{skip} += 1;
105                        }
106                        else {
107                            $self->{callback}->("\n");
108                            MT->log({ message => MT->translate(
109                                "User with the same name '[_1]' found (ID:[_2]).  Restore replaced this user with the data backed up.",
110                                                  $obj->name, $obj->id),
111                                level => MT::Log::INFO(),
112                                metadata => 'Permissions and Associations have been restored as well.',
113                                class => 'system',
114                                category => 'restore',
115                            });
116                            my $old_id = delete $column_data{id};
117                            $objects->{"$class#$old_id"} = $obj;
118                            my $child_classes = $obj->properties->{child_classes} || {};
119                            for my $class (keys %$child_classes) {
120                                eval "use $class;";
121                                $class->remove({ author_id => $obj->id, blog_id => '0' });
122                            }
123                            my $success = $obj->restore_parent_ids(\%column_data, $objects);
124                            if ($success) {
125                                $obj->set_values(\%column_data);
126                                $self->{current} = $obj;
127                            } else {
128                                $deferred->{$class . '#' . $column_data{id}} = 1;
129                                $self->{deferred} = $deferred;
130                                $self->{skip} += 1;
131                            }
132                            $self->{loaded} = 1;
133                        }
134                    }
135                } elsif ('template' eq $name) {
136                    if (!$column_data{blog_id}) {
137                        $obj = $class->load({ blog_id => 0, identifier => $column_data{identifier} });
138                        if ($obj) {
139                            my $old_id = delete $column_data{id};
140                            $objects->{"$class#$old_id"} = $obj;
141                            if ($self->{overwrite_template}) {
142                                $obj->set_values(\%column_data);
143                                $self->{current} = $obj;
144                                $self->{loaded} = 1;
145                            } else {
146                                $self->{skip} += 1;
147                            }
148                        }
149                    }
150                }
151                unless ($obj) {
152                    $obj = $class->new;
153                }
154                unless ($obj->id) {
155                    my $success = $obj->restore_parent_ids(\%column_data, $objects);
156                    if ($success) {
157                        require MT::Meta;
158                        my @metacolumns = MT::Meta->metadata_by_class( ref($obj) );
159                        my %metacolumns = map { $_->{name} => $_->{type} } @metacolumns;
160                        $self->{metacolumns}{ref($obj)} = \%metacolumns;
161                        my %realcolumn_data = map { $_ => $column_data{$_} }
162                            grep { !exists($metacolumns{$_}) }
163                                keys %column_data;
164                        $obj->set_values(\%realcolumn_data);
165                        foreach my $metacol ( keys %metacolumns ) {
166                            next if ( 'vclob' eq $metacolumns{$metacol} )
167                                 || ( 'vblob' eq $metacolumns{$metacol} );
168                            $obj->$metacol( $column_data{$metacol} );
169                        }
170                        $self->{current} = $obj;
171                    } else {
172                        $deferred->{$class . '#' . $column_data{id}} = 1;
173                        $self->{deferred} = $deferred;
174                        $self->{skip} += 1;
175                    }
176                }
177            }
178        } else {
179            my $obj = MT->run_callbacks("Restore.$name:$ns", $data, $objects, $deferred, $callback);
180            $self->{current} = $obj if defined($obj) && ('1' ne $obj);
181        }
182    }
183    1;
184}
185
186sub characters {
187    my $self = shift;
188    my $data = shift;
189
190    return if $self->{skip};
191    return if !exists($self->{current});
192    if (my $text_data = $self->{current_text}) {
193        push @$text_data, MT::I18N::utf8_off($data->{Data});
194        $self->{current_text} = $text_data;
195    }
196    1;
197}
198
199sub end_element {
200    my $self = shift;
201    my $data = shift;
202
203    if ($self->{skip}) {
204        $self->{skip} -= 1;
205        return;
206    }
207
208    my $name = $data->{LocalName};
209    my $class = MT->model($name);
210
211    if (my $obj = $self->{current}) {
212        if (my $text_data = delete $self->{current_text}) {
213            my $column_name = shift @$text_data;
214            my $text;
215            $text .= $_ foreach @$text_data;
216           
217            my $defs = $obj->column_defs;
218            if ( exists( $defs->{$column_name} ) ) {
219                if ('blob' eq $defs->{$column_name}->{type}) {
220                    $obj->column($column_name, MIME::Base64::decode_base64($text));
221                } else {
222                    $text = MT::I18N::encode_text($text, 'utf-8');
223                    $obj->column($column_name, $text);
224                }
225            }
226            elsif ( my $metacolumns = $self->{metacolumns}{ref($obj)} ) {
227                if ( my $type = $metacolumns->{$column_name} ) {
228                    if ( 'vblob' eq $type ) {
229                        $self->{callback}->($text);
230                        $text = MIME::Base64::decode_base64($text);
231                        $self->{callback}->($text);
232                        $obj->$column_name( $text );
233                    }
234                    else {
235                        $text = MT::I18N::encode_text($text, 'utf-8');
236                        $obj->$column_name( $text );
237                    }
238                }
239            }
240        } else {
241            my $old_id = $obj->id;
242            unless ((('author' eq $name) || ('template' eq $name)) && (exists $self->{loaded})) {
243                delete $obj->{column_values}->{id};
244                delete $obj->{changed_cols}->{id};
245            } else {
246                delete $self->{loaded};
247            }
248            my $exists = 0;
249            if ('tag' eq $name) {
250                if (my $tag = MT::Tag->load({ name => $obj->name }, { binary => { name => 1 } } )) {
251                    $exists = 1;
252                    $self->{objects}->{"$class#$old_id"} = $tag;
253                    $self->{callback}->("\n");
254                    $self->{callback}->(
255                        MT->translate("Tag '[_1]' exists in the system.",
256                            $obj->name)
257                    );
258                }
259            } elsif ('trackback' eq $name) {
260                my $term;
261                my $message;
262                if ($obj->entry_id) {
263                    $term = { entry_id => $obj->entry_id };
264                } elsif ($obj->category_id) {
265                    $term = { category_id => $obj->category_id };
266                }
267                if (my $tb = $class->load($term)) {
268                    $exists = 1;
269                    my $changed = 0;
270                    if ($obj->passphrase) {
271                        $tb->passphrase($obj->passphrase);
272                        $changed = 1;
273                    }
274                    if ($obj->is_disabled) {
275                        $tb->is_disabled($obj->is_disabled);
276                        $changed = 1;
277                    }
278                    $tb->save if $changed;
279                    $self->{objects}->{"$class#$old_id"} = $tb;
280                    my $records = $self->{records};
281                    $self->{callback}->($self->{state} . " " . MT->translate("[_1] records restored...", $records), $data->{LocalName})
282                        if $records && ($records % 10 == 0);
283                    $self->{records} = $records + 1;
284                }
285            }
286            elsif ('permission' eq $name) {
287                my $perm = $class->exist( {
288                    author_id => $obj->author_id,
289                    blog_id   => $obj->blog_id
290                });
291                $exists = 1 if $perm;
292            }
293            elsif ('objectscore' eq $name) {
294                my $score = $class->exist( {
295                    author_id => $obj->author_id,
296                    object_id => $obj->object_id,
297                    object_ds => $obj->object_ds,
298                });
299                $exists = 1 if $score;
300            }
301            unless ($exists) {
302                my $result;
303                if ( $obj->id ) {
304                    $result = $obj->update();
305                }
306                else {
307                    $result = $obj->insert();
308                }
309                if ( $result ) {
310                    if ($class =~ /MT::Asset(::.+)*/) {
311                        $class = 'MT::Asset';
312                    }
313                    $self->{objects}->{"$class#$old_id"} = $obj;
314                    my $records = $self->{records};
315                    $self->{callback}->($self->{state} . " " . MT->translate("[_1] records restored...", $records), $data->{LocalName})
316                        if $records && ($records % 10 == 0);
317                    $self->{records} = $records + 1;
318                } else {
319                    push @{$self->{errors}}, $obj->errstr;
320                    $self->{callback}->($obj->errstr);
321                }
322            }
323            delete $self->{current};
324        }
325    }
326}
327
328sub end_document {
329    my $self = shift;
330    my $data = shift;
331
332    if (my $c = $self->{current_class}) {
333        my $state = $self->{state};
334        my $records = $self->{records};
335        $self->{callback}->($state . " " . MT->translate("[_1] records restored.", $records), $c->class_type || $c->datasource);
336    }
337
338    1;
339}
340
3411;
Note: See TracBrowser for help on using the browser.