root/branches/release-40/lib/MT/BackupRestore/BackupFileHandler.pm @ 2564

Revision 2564, 15.0 kB (checked in by fumiakiy, 18 months ago)

Do not proceed restoring from the backup which was from *different* version of MT (not just newer, but different version would not be allowed). BugId:80102

  • 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            if ( $schema != $self->{schema_version} ) {
48                $self->{critical} = 1;
49                my $message = MT->translate('Uploaded file was backed up from Movable Type but the different schema version ([_1]) from 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});
50                MT->log({ 
51                    message => $message,
52                    level => MT::Log::ERROR(),
53                    class => 'system',
54                    category => 'restore',
55                });
56                die $message;
57            }
58        #}
59        $self->{start} = 0;
60        return 1;
61    }
62
63    my $objects = $self->{objects};
64    my $deferred = $self->{deferred};
65    my $callback = $self->{callback};
66
67    if (my $current = $self->{current}) {
68        # this is an element for a text column of the object
69        $self->{current_text} = [ $name ];
70    } else {
71        if (MT::BackupRestore::NS_MOVABLETYPE() eq $ns) {
72            my $class = MT->model($name);
73            unless ($class) {
74                push @{$self->{errors}}, MT->translate('[_1] is not a subject to be restored by Movable Type.', $name);
75            } else {
76                if ($self->{current_class} ne $class) {
77                    if (my $c = $self->{current_class}) {
78                        my $state = $self->{state};
79                        my $records = $self->{records};
80                        $callback->($state . " " . MT->translate("[_1] records restored.", $records), $c->class_type || $c->datasource);
81                    }
82                    $self->{records} = 0;
83                    $self->{current_class} = $class;
84                    my $state = MT->translate('Restoring [_1] records:', $class);
85                    $callback->($state, $name);
86                    $self->{state} = $state;
87                }
88                my %column_data = map { $attrs->{$_}->{LocalName} => 
89                        MT::I18N::encode_text(MT::I18N::utf8_off($attrs->{$_}->{Value}), 'utf-8')
90                    } keys(%$attrs);
91                my $obj;
92                if ( 'author' eq $name ) {
93                    $obj = $class->load({ name => $column_data{name} });
94                    if ($obj) {
95                        if ( UNIVERSAL::isa( MT->instance, 'MT::App' )
96                          && ( $obj->id == MT->instance->user->id ) ) {
97                            MT->log({ message => MT->translate(
98                                "User with the same name as the name of the currently logged in ([_1]) found.  Skipped the record.", $obj->name),
99                                level => MT::Log::INFO(),
100                                metadata => 'Permissions and Associations have been restored.',
101                                class => 'system',
102                                category => 'restore',
103                            });
104                            $objects->{"$class#" . $column_data{id}} = $obj;
105                            $self->{current} = $obj;
106                            $self->{loaded} = 1;
107                            $self->{skip} += 1;
108                        }
109                        else {
110                            $self->{callback}->("\n");
111                            MT->log({ message => MT->translate(
112                                "User with the same name '[_1]' found (ID:[_2]).  Restore replaced this user with the data backed up.",
113                                                  $obj->name, $obj->id),
114                                level => MT::Log::INFO(),
115                                metadata => 'Permissions and Associations have been restored as well.',
116                                class => 'system',
117                                category => 'restore',
118                            });
119                            my $old_id = delete $column_data{id};
120                            $objects->{"$class#$old_id"} = $obj;
121                            my $child_classes = $obj->properties->{child_classes} || {};
122                            for my $class (keys %$child_classes) {
123                                eval "use $class;";
124                                $class->remove({ author_id => $obj->id, blog_id => '0' });
125                            }
126                            my $success = $obj->restore_parent_ids(\%column_data, $objects);
127                            if ($success) {
128                                my %realcolumns = map { $_ => delete($column_data{$_}) }
129                                    @{ $obj->column_names };
130                                $obj->set_values(\%realcolumns);
131                                $obj->$_($column_data{$_}) foreach keys( %column_data );
132                                $self->{current} = $obj;
133                            } else {
134                                $deferred->{$class . '#' . $column_data{id}} = 1;
135                                $self->{deferred} = $deferred;
136                                $self->{skip} += 1;
137                            }
138                            $self->{loaded} = 1;
139                        }
140                    }
141                } elsif ('template' eq $name) {
142                    if (!$column_data{blog_id}) {
143                        $obj = $class->load({ blog_id => 0, identifier => $column_data{identifier} });
144                        if ($obj) {
145                            my $old_id = delete $column_data{id};
146                            $objects->{"$class#$old_id"} = $obj;
147                            if ($self->{overwrite_template}) {
148                                my %realcolumns = map { $_ => delete($column_data{$_}) }
149                                    @{ $obj->column_names };
150                                $obj->set_values(\%realcolumns);
151                                $obj->$_($column_data{$_}) foreach keys( %column_data );
152                                $self->{current} = $obj;
153                                $self->{loaded} = 1;
154                            } else {
155                                $self->{skip} += 1;
156                            }
157                        }
158                    }
159                }
160                unless ($obj) {
161                    $obj = $class->new;
162                }
163                unless ($obj->id) {
164                    my $success = $obj->restore_parent_ids(\%column_data, $objects);
165                    if ($success) {
166                        require MT::Meta;
167                        my @metacolumns = MT::Meta->metadata_by_class( ref($obj) );
168                        my %metacolumns = map { $_->{name} => $_->{type} } @metacolumns;
169                        $self->{metacolumns}{ref($obj)} = \%metacolumns;
170                        my %realcolumn_data = map { $_ => $column_data{$_} }
171                            grep { !exists($metacolumns{$_}) }
172                                keys %column_data;
173                        $obj->set_values(\%realcolumn_data);
174                        foreach my $metacol ( keys %metacolumns ) {
175                            next if ( 'vclob' eq $metacolumns{$metacol} )
176                                 || ( 'vblob' eq $metacolumns{$metacol} );
177                            $obj->$metacol( $column_data{$metacol} );
178                        }
179                        $self->{current} = $obj;
180                    } else {
181                        $deferred->{$class . '#' . $column_data{id}} = 1;
182                        $self->{deferred} = $deferred;
183                        $self->{skip} += 1;
184                    }
185                }
186            }
187        } else {
188            my $obj = MT->run_callbacks("Restore.$name:$ns", $data, $objects, $deferred, $callback);
189            $self->{current} = $obj if defined($obj) && ('1' ne $obj);
190        }
191    }
192    1;
193}
194
195sub characters {
196    my $self = shift;
197    my $data = shift;
198
199    return if $self->{skip};
200    return if !exists($self->{current});
201    if (my $text_data = $self->{current_text}) {
202        push @$text_data, MT::I18N::utf8_off($data->{Data});
203        $self->{current_text} = $text_data;
204    }
205    1;
206}
207
208sub end_element {
209    my $self = shift;
210    my $data = shift;
211
212    if ($self->{skip}) {
213        $self->{skip} -= 1;
214        return;
215    }
216
217    my $name  = $data->{LocalName};
218    my $ns    = $data->{NamespaceURI};
219    my $class = MT->model($name);
220
221    if (my $obj = $self->{current}) {
222        if (my $text_data = delete $self->{current_text}) {
223            my $column_name = shift @$text_data;
224            my $text;
225            $text .= $_ foreach @$text_data;
226           
227            my $defs = $obj->column_defs;
228            if ( exists( $defs->{$column_name} ) ) {
229                if ('blob' eq $defs->{$column_name}->{type}) {
230                    $obj->column($column_name, MIME::Base64::decode_base64($text));
231                } else {
232                    $text = MT::I18N::encode_text($text, 'utf-8');
233                    $obj->column($column_name, $text);
234                }
235            }
236            elsif ( my $metacolumns = $self->{metacolumns}{ref($obj)} ) {
237                if ( my $type = $metacolumns->{$column_name} ) {
238                    if ( 'vblob' eq $type ) {
239                        $text = MIME::Base64::decode_base64($text);
240                        $text = MT::Serialize->unserialize( $text );
241                        $obj->$column_name( $$text );
242                    }
243                    else {
244                        $text = MT::I18N::encode_text($text, 'utf-8');
245                        $obj->$column_name( $text );
246                    }
247                }
248            }
249        } else {
250            my $old_id = $obj->id;
251            unless ((('author' eq $name) || ('template' eq $name)) && (exists $self->{loaded})) {
252                delete $obj->{column_values}->{id};
253                delete $obj->{changed_cols}->{id};
254            } else {
255                delete $self->{loaded};
256            }
257            my $exists = 0;
258            if ('tag' eq $name) {
259                if (my $tag = MT::Tag->load({ name => $obj->name }, { binary => { name => 1 } } )) {
260                    $exists = 1;
261                    $self->{objects}->{"$class#$old_id"} = $tag;
262                    $self->{callback}->("\n");
263                    $self->{callback}->(
264                        MT->translate("Tag '[_1]' exists in the system.",
265                            $obj->name)
266                    );
267                }
268            } elsif ('trackback' eq $name) {
269                my $term;
270                my $message;
271                if ($obj->entry_id) {
272                    $term = { entry_id => $obj->entry_id };
273                } elsif ($obj->category_id) {
274                    $term = { category_id => $obj->category_id };
275                }
276                if (my $tb = $class->load($term)) {
277                    $exists = 1;
278                    my $changed = 0;
279                    if ($obj->passphrase) {
280                        $tb->passphrase($obj->passphrase);
281                        $changed = 1;
282                    }
283                    if ($obj->is_disabled) {
284                        $tb->is_disabled($obj->is_disabled);
285                        $changed = 1;
286                    }
287                    $tb->save if $changed;
288                    $self->{objects}->{"$class#$old_id"} = $tb;
289                    my $records = $self->{records};
290                    $self->{callback}->($self->{state} . " " . MT->translate("[_1] records restored...", $records), $data->{LocalName})
291                        if $records && ($records % 10 == 0);
292                    $self->{records} = $records + 1;
293                }
294            }
295            elsif ('permission' eq $name) {
296                my $perm = $class->exist( {
297                    author_id => $obj->author_id,
298                    blog_id   => $obj->blog_id
299                });
300                $exists = 1 if $perm;
301            }
302            elsif ('objectscore' eq $name) {
303                my $score = $class->exist( {
304                    author_id => $obj->author_id,
305                    object_id => $obj->object_id,
306                    object_ds => $obj->object_ds,
307                });
308                $exists = 1 if $score;
309            }
310            elsif ('field' eq $name) {
311                # Available in propack only
312                if ( $obj->blog_id == 0 ) {
313                    my $field = $class->exist( {
314                        blog_id => 0,
315                        basename => $obj->basename,
316                    } );
317                    $exists = 1 if $field;
318                }
319            }
320            unless ($exists) {
321                my $result;
322                if ( $obj->id ) {
323                    $result = $obj->update();
324                }
325                else {
326                    $result = $obj->insert();
327                }
328                if ( $result ) {
329                    if ($class =~ /MT::Asset(::.+)*/) {
330                        $class = 'MT::Asset';
331                    }
332                    $self->{objects}->{"$class#$old_id"} = $obj;
333                    my $records = $self->{records};
334                    $self->{callback}->($self->{state} . " " . MT->translate("[_1] records restored...", $records), $data->{LocalName})
335                        if $records && ($records % 10 == 0);
336                    $self->{records} = $records + 1;
337                    my $cb = "restored.$name";
338                    $cb .= ":$ns" if MT::BackupRestore::NS_MOVABLETYPE() ne $ns;
339                    MT->run_callbacks( $cb, $obj, $self->{callback} );
340                    $obj->call_trigger('post_save', $obj);
341                } else {
342                    push @{$self->{errors}}, $obj->errstr;
343                    $self->{callback}->($obj->errstr);
344                }
345            }
346            delete $self->{current};
347        }
348    }
349}
350
351sub end_document {
352    my $self = shift;
353    my $data = shift;
354
355    if (my $c = $self->{current_class}) {
356        my $state = $self->{state};
357        my $records = $self->{records};
358        $self->{callback}->($state . " " . MT->translate("[_1] records restored.", $records), $c->class_type || $c->datasource);
359    }
360
361    1;
362}
363
3641;
Note: See TracBrowser for help on using the browser.