| | 14 | |
|---|
| | 15 | { |
|---|
| | 16 | my $driver; |
|---|
| | 17 | sub _driver { |
|---|
| | 18 | my $driver_name = 'MT::Revisable::' . MT->config->RevisioningDriver; |
|---|
| | 19 | eval 'require ' . $driver_name; |
|---|
| | 20 | if (my $err = $@) { |
|---|
| | 21 | die (MT->translate("Bad RevisioningDriver config '[_1]': [_2]", $driver_name, $err)); |
|---|
| | 22 | } |
|---|
| | 23 | my $driver = $driver_name->new; |
|---|
| | 24 | die $driver_name->errstr |
|---|
| | 25 | if (!$driver || (ref(\$driver) eq 'SCALAR')); |
|---|
| | 26 | return $driver; |
|---|
| | 27 | } |
|---|
| | 28 | |
|---|
| | 29 | sub _handle { |
|---|
| | 30 | my $method = ( caller(1) )[3]; |
|---|
| | 31 | $method =~ s/.*:://; |
|---|
| | 32 | my $driver = $driver ||= _driver(); |
|---|
| | 33 | return undef unless $driver->can($method); |
|---|
| | 34 | $driver->$method(@_); |
|---|
| | 35 | } |
|---|
| | 36 | |
|---|
| | 37 | sub release { |
|---|
| | 38 | undef $driver; |
|---|
| | 39 | } |
|---|
| | 40 | } |
|---|
| 48 | | sub revision_pkg { |
|---|
| 49 | | my $class = shift; |
|---|
| 50 | | my $props = $class->properties; |
|---|
| 51 | | |
|---|
| 52 | | return $props->{revision_pkg} if $props->{revision_pkg}; |
|---|
| 53 | | |
|---|
| 54 | | my $rev = ref $class || $class; |
|---|
| 55 | | $rev .= '::Revision'; |
|---|
| 56 | | |
|---|
| 57 | | return $props->{revision_pkg} = $rev; |
|---|
| 58 | | } |
|---|
| 59 | | |
|---|
| 60 | | sub revision_props { |
|---|
| 61 | | my $class = shift; |
|---|
| 62 | | my $obj_ds = $class->datasource; |
|---|
| 63 | | my $obj_id = $obj_ds . '_id'; |
|---|
| 64 | | return { |
|---|
| 65 | | key => $class->datasource, |
|---|
| 66 | | column_defs => { |
|---|
| 67 | | id => 'integer not null auto_increment', |
|---|
| 68 | | $obj_id => 'integer not null', |
|---|
| 69 | | $obj_ds => 'blob not null', |
|---|
| 70 | | rev_number => 'integer not null', |
|---|
| 71 | | changed => 'string(255) not null' |
|---|
| 72 | | }, |
|---|
| 73 | | indexes => { |
|---|
| 74 | | $obj_id => 1 |
|---|
| 75 | | }, |
|---|
| 76 | | defaults => { |
|---|
| 77 | | rev_number => 0 |
|---|
| 78 | | }, |
|---|
| 79 | | audit => 1, |
|---|
| 80 | | primary_key => 'id', |
|---|
| 81 | | datasource => $class->datasource . '_rev' |
|---|
| 82 | | }; |
|---|
| 83 | | } |
|---|
| 84 | | |
|---|
| 85 | | sub install_revisioning { |
|---|
| 86 | | my $class = shift; |
|---|
| 87 | | my $datasource = $class->datasource; |
|---|
| 88 | | |
|---|
| 89 | | my $subclass = $class->revision_pkg; |
|---|
| 90 | | return unless $subclass; |
|---|
| 91 | | |
|---|
| 92 | | my $rev_props = $class->revision_props; |
|---|
| 93 | | |
|---|
| 94 | | no strict 'refs'; ## no critic |
|---|
| 95 | | return if defined ${"${subclass}::VERSION"}; |
|---|
| 96 | | |
|---|
| 97 | | ## Try to use this subclass first to see if it exists |
|---|
| 98 | | my $subclass_file = $subclass . '.pm'; |
|---|
| 99 | | $subclass_file =~ s{::}{/}g; |
|---|
| 100 | | eval "# line " . __LINE__ . " " . __FILE__ . "\nno warnings 'all';require '$subclass_file';$subclass->import();"; |
|---|
| 101 | | if ($@) { |
|---|
| 102 | | ## Die if we get an unexpected error |
|---|
| 103 | | die $@ unless $@ =~ /Can't locate /; |
|---|
| 104 | | } else { |
|---|
| 105 | | ## This class exists. We don't need to do anything. |
|---|
| 106 | | return 1; |
|---|
| 107 | | } |
|---|
| 108 | | |
|---|
| 109 | | my $base_class = 'MT::Object'; |
|---|
| 110 | | |
|---|
| 111 | | my $subclass_src = " |
|---|
| 112 | | # line " . __LINE__ . " " . __FILE__ . " |
|---|
| 113 | | package $subclass; |
|---|
| 114 | | our \$VERSION = 1.0; |
|---|
| 115 | | use base qw($base_class); |
|---|
| 116 | | |
|---|
| 117 | | 1; |
|---|
| 118 | | "; |
|---|
| 119 | | |
|---|
| 120 | | ## no critic ProhibitStringyEval |
|---|
| 121 | | eval $subclass_src or print STDERR "Could not create package $subclass!\n"; |
|---|
| 122 | | |
|---|
| 123 | | $subclass->install_properties($rev_props); |
|---|
| 124 | | } |
|---|
| | 75 | sub revision_pkg { _handle(@_); } |
|---|
| | 76 | sub revision_props { _handle(@_); } |
|---|
| | 77 | sub init_revisioning { _handle(@_); } |
|---|
| 232 | | sub save_revision { |
|---|
| 233 | | my $obj = shift; |
|---|
| 234 | | return 1 unless $obj->id; |
|---|
| 235 | | |
|---|
| 236 | | my $changed_cols = $obj->{changed_revisioned_cols}; |
|---|
| 237 | | return 1 unless scalar @$changed_cols > 0; |
|---|
| 238 | | |
|---|
| 239 | | my $datasource = $obj->datasource; |
|---|
| 240 | | my $obj_id = $datasource . '_id'; |
|---|
| 241 | | my $packed_obj = $obj->pack_revision(); |
|---|
| 242 | | |
|---|
| 243 | | require MT::Serialize; |
|---|
| 244 | | my $rev_class = MT->model($datasource . ':revision'); |
|---|
| 245 | | my $revision = $rev_class->new; |
|---|
| 246 | | $revision->set_values({ |
|---|
| 247 | | $obj_id => $obj->id, |
|---|
| 248 | | $datasource => MT::Serialize->serialize(\$packed_obj), |
|---|
| 249 | | changed => join ',', @$changed_cols |
|---|
| 250 | | }); |
|---|
| 251 | | $revision->rev_number($obj->current_revision); |
|---|
| 252 | | $revision->save or return; |
|---|
| 253 | | |
|---|
| 254 | | return 1; |
|---|
| 255 | | } |
|---|
| 256 | | |
|---|
| 257 | | sub object_from_revision { |
|---|
| 258 | | my $obj = shift; |
|---|
| 259 | | my ($rev) = @_; |
|---|
| 260 | | my $datasource = $obj->datasource; |
|---|
| 261 | | |
|---|
| 262 | | my $rev_obj = $obj->clone; |
|---|
| 263 | | my $serialized_obj = $rev->$datasource; |
|---|
| 264 | | require MT::Serialize; |
|---|
| 265 | | my $packed_obj = MT::Serialize->unserialize($serialized_obj); |
|---|
| 266 | | $rev_obj->unpack_revision($$packed_obj); |
|---|
| 267 | | |
|---|
| 268 | | # Here we cheat since audit columns aren't revisioned |
|---|
| 269 | | $rev_obj->modified_by($rev->created_by); |
|---|
| 270 | | $rev_obj->modified_on($rev->modified_on); |
|---|
| 271 | | |
|---|
| 272 | | my @changed = split ',', $rev->changed; |
|---|
| 273 | | |
|---|
| 274 | | return [ $rev_obj, \@changed, $rev->rev_number]; |
|---|
| 275 | | } |
|---|
| 276 | | |
|---|
| 277 | | sub load_revision { |
|---|
| | 185 | sub save_revision { _handle(@_); } |
|---|
| | 186 | sub object_from_revision { _handle(@_); } |
|---|
| | 187 | sub load_revision { _handle(@_); } |
|---|
| | 188 | |
|---|
| | 189 | sub apply_revision { |
|---|
| 280 | | my $datasource = $obj->datasource; |
|---|
| 281 | | my $rev_class = MT->model($datasource . ':revision'); |
|---|
| 282 | | |
|---|
| 283 | | # Only specified a rev_number |
|---|
| 284 | | if(defined $terms && ref $terms ne 'HASH') { |
|---|
| 285 | | $terms = { rev_number => $_[0] }; |
|---|
| 286 | | } |
|---|
| 287 | | $terms->{$datasource . '_id'} ||= $obj->id; |
|---|
| 288 | | |
|---|
| 289 | | if ( wantarray ) { |
|---|
| 290 | | my @rev = map { $obj->object_from_revision($_); } |
|---|
| 291 | | $rev_class->load( $terms, $args ); |
|---|
| 292 | | unless (@rev) { |
|---|
| 293 | | return $obj->error( $rev_class->errstr ); |
|---|
| 294 | | } |
|---|
| 295 | | return @rev; |
|---|
| 296 | | } |
|---|
| 297 | | else { |
|---|
| 298 | | my $rev = $rev_class->load( $terms, $args ) |
|---|
| 299 | | or return $obj->error( $rev_class->errstr ); |
|---|
| 300 | | my $o = $obj->object_from_revision($rev); |
|---|
| 301 | | return $o; |
|---|
| 302 | | } |
|---|
| 303 | | } |
|---|
| 304 | | |
|---|
| 305 | | sub apply_revision { |
|---|
| 306 | | my $obj = shift; |
|---|
| 307 | | my ( $rev_id ) = @_; |
|---|
| 308 | | |
|---|
| 309 | | my $rev = $obj->load_revision( $rev_id ) |
|---|
| | 192 | |
|---|
| | 193 | my $rev = $obj->load_revision($terms, $args) |
|---|