root/branches/release-26/lib/MT/Object.pm @ 1174

Revision 1174, 60.3 kB (checked in by bchoate, 23 months ago)

Updated copyright year for source.

  • Property svn:keywords set to Author Date Id 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::Object;
8
9use strict;
10use base qw( Data::ObjectDriver::BaseObject MT::ErrorHandler );
11
12use MT;
13use MT::Util qw(offset_time_list);
14
15my (@PRE_INIT_PROPS, @PRE_INIT_META);
16
17sub install_pre_init_properties {
18    # Just in case; to prevent any weird recursion
19    local $MT::plugins_installed = 1;
20
21    foreach my $def (@PRE_INIT_PROPS) {
22        my ($class, $props) = @$def;
23        $class->install_properties($props);
24    }
25    @PRE_INIT_PROPS = ();
26
27    foreach my $def (@PRE_INIT_META) {
28        my ($class, $meta) = @$def;
29        $class->install_meta($meta);
30    }
31    @PRE_INIT_META = ();
32}
33
34sub install_properties {
35    my $class = shift;
36    my ($props) = @_;
37
38    if ( ( $class ne 'MT::Config') && ( !$MT::plugins_installed ) ) {
39        # We're too early in the phase of MT's bootstrapping to
40        # be installing properties; we can't query the registry yet
41        # since plugins are not all accounted for. So save this
42        # set of properties to install it later (odds are, the
43        # package has been loaded to afford installing callbacks
44        # or accessing constants and isn't being used to load
45        # actual data.)
46        #
47        # The only exception to this rule is MT::Config; we must
48        # have access to the MT configuration table in order to
49        # bootstrap MT.
50
51        push @PRE_INIT_PROPS, [$class, $props];
52        return;
53    }
54
55    my $super_props = $class->SUPER::properties();
56    if ($super_props) {
57        # subclass; merge hash
58        for (qw(primary_key meta_column class_column datasource driver audit meta)) {
59            $props->{$_} = $super_props->{$_}
60                if exists $super_props->{$_} && !(exists $props->{$_});
61        }
62        for my $p (qw(column_defs defaults indexes meta_columns)) {
63            if (exists $super_props->{$p}) {
64                foreach my $k (keys %{ $super_props->{$p} }) {
65                    if (!exists $props->{$p}{$k}) {
66                        $props->{$p}{$k} = $super_props->{$p}{$k};
67                    }
68                }
69                if ($p eq 'column_defs') {
70                    $class->__parse_defs($props->{column_defs});
71                }
72            }
73        }
74        if ($super_props->{class_type}) {
75            # copy reference of class_to_type/type_to_class hashes
76            $props->{__class_to_type} = $super_props->{__class_to_type};
77            $props->{__type_to_class} = $super_props->{__type_to_class};
78        }
79    }
80
81    # Legacy MT::Object types only define 'columns'; we still support that
82    # but they aren't handled properly with the upgrade system as a result.
83    if (exists $props->{column_defs}) {
84        $props->{columns} = [ keys %{ $props->{column_defs} } ];
85    } else {
86        map { $props->{column_defs}{$_} = () } @{ $props->{columns} };
87    }
88
89    # Support audit flags
90    if ($props->{audit}) {
91        unless (exists $props->{column_defs}{created_on}) {
92            $props->{column_defs}{created_on} = 'datetime';
93            $props->{column_defs}{created_by} = 'integer';
94            $props->{column_defs}{modified_on} = 'datetime';
95            $props->{column_defs}{modified_by} = 'integer';
96            push @{ $props->{columns} }, qw( created_on created_by modified_on modified_by );
97        }
98    }
99
100    # Metadata column
101    $props->{meta_column} ||= 'meta' if exists $props->{meta};
102    if (my $col = $props->{meta_column}) {
103        if (!$props->{column_defs}{$col}) {
104            $props->{column_defs}{$col} = 'blob';
105            push @{ $props->{columns} }, $col;
106        }
107        no strict 'refs'; ## no critic
108        *{$class . '::' . $col} = \&__meta_column;
109        $class->add_trigger( pre_save => \&pre_save_serialize_metadata );
110    }
111
112    # Classed object types
113    $props->{class_column} ||= 'class' if exists $props->{class_type};
114    if (my $col = $props->{class_column}) {
115        if (!$props->{column_defs}{$col}) {
116            $props->{column_defs}{$col} = 'string(255)';
117            push @{$props->{columns}}, $col;
118            $props->{indexes}{$col} = 1;
119        }
120        if (!$super_props || !$super_props->{class_column}) {
121            $class->add_trigger( pre_search => \&pre_search_scope_terms_to_class );
122            $class->add_trigger( post_load => \&post_load_rebless_object );
123        }
124        if (my $type = $props->{class_type}) {
125            $props->{defaults}{$col} = $type;
126            $props->{__class_to_type}{$class} = $type;
127            $props->{__type_to_class}{$type} = $class;
128        }
129    }
130
131    my $type_id;
132    if ($type_id = $props->{class_type}) {
133        if ($type_id ne $props->{datasource}) {
134            $type_id = $props->{datasource} . '.' . $type_id;
135        }
136    } else {
137        $type_id = $props->{datasource};
138    }
139
140    $class->SUPER::install_properties($props);
141
142    # check for any supplemental columns from other components
143    my $more_props = MT->registry('object_types', $type_id);
144    if ($more_props && (ref($more_props) eq 'ARRAY')) {
145        my $cols = {};
146        for my $prop (@$more_props) {
147            next if ref($prop) ne 'HASH';
148            MT::__merge_hash($cols, $prop, 1);
149        }
150        my @classes = grep { !ref($_) } @$more_props;
151        foreach my $isa_class (@classes) {
152            next if UNIVERSAL::isa($class, $isa_class);
153            eval "require $isa_class;" or die;
154            no strict 'refs'; ## no critic
155            push @{$class . '::ISA'}, $isa_class;
156        }
157        if (%$cols) {
158            # special case for 'plugin' key...
159            delete $cols->{plugin} if exists $cols->{plugin};
160            for my $name (keys %$cols) {
161                next if exists $props->{column_defs}{$name};
162                $class->install_column($name, $cols->{$name});
163                $props->{indexes}{$name} = 1
164                    if $cols->{$name} =~ m/\bindexed\b/;
165                if ($cols->{$name} =~ m/\bdefault (?:'([^']+?)'|(\d+))\b/) {
166                    $props->{defaults}{$name} = defined $1 ? $1 : $2;
167                }
168            }
169        }
170    }
171
172    my $pk = $props->{primary_key} || '';
173    @{$props->{columns}} = sort { $a eq $pk ? -1 : $b eq $pk ? 1 : $a cmp $b }
174        @{$props->{columns}};
175
176    # Child classes are declared as an array;
177    # convert them to a hashref for easier lookup.
178    if ((ref $props->{child_classes}) eq 'ARRAY') {
179        my $classes = $props->{child_classes};
180        $props->{child_classes} = {};
181        @{$props->{child_classes}}{@$classes} = ();
182    }
183
184    # We're declared as a child of some other class; associate ourselves
185    # with that package (the invoking class should have already use'd it.)
186    if (exists $props->{child_of}) {
187        my $parent_classes = $props->{child_of};
188        if (!ref $parent_classes) {
189            $parent_classes = [ $parent_classes ];
190        }
191        foreach my $pc (@$parent_classes) {
192            my $pp = $pc->properties;
193            $pp->{child_classes} ||= {};
194            $pp->{child_classes}{$class} = ();
195        }
196    }
197
198    # Special handling for 'Taggable' objects; automatic saving
199    # and removal of tags.
200    my @isa;
201    {
202        no strict 'refs';
203        @isa = @{ $class . '::ISA' };
204    }
205    foreach my $isa_pkg ( @isa ) {
206        next unless $isa_pkg =~ /able$/;
207        next if $isa_pkg eq $class;
208        if ($isa_pkg->can('install_properties')) {
209            $isa_pkg->install_properties($class);
210        }
211    }
212
213    # install legacy date translation
214    if (0 < scalar @{ $class->columns_of_type('datetime', 'timestamp') }) {
215        if ($props->{audit}) {
216            $class->add_trigger( pre_save  => \&assign_audited_fields);
217            $class->add_trigger( post_save => \&translate_audited_fields );
218        }
219
220        $class->add_trigger( pre_save  => get_date_translator(\&ts2db, 1) );
221        $class->add_trigger( post_load => get_date_translator(\&db2ts, 0) );
222    }
223
224    return $props;
225}
226
227# A post-load trigger for classed objects
228sub post_load_rebless_object {
229    my $obj = shift;
230    my $props = $obj->properties;
231    if (my $col = $props->{class_column}) {
232        my $type = $obj->column($col);
233        my $pkg = ref($obj);
234        if ($pkg->class_type ne $type) {
235            if (my $class = $props->{__type_to_class}{$type}) {
236                bless $obj, $class;
237            } else {
238                my %models = map { $_ => 1 } MT->models($props->{datasource});
239                if (exists $models{ $props->{datasource} . '.' . $type}) {
240                    $class = MT->model($props->{datasource} . '.' . $type);
241                } elsif (exists $models{$type}) {
242                    $class = MT->model($type);
243                }
244                bless $obj, $class if $class;
245            }
246        }
247    }
248}
249
250# A pre-search trigger for classed objects
251sub pre_search_scope_terms_to_class {
252    my ($class, $terms, $args) = @_;
253    # scope search terms to class
254
255    $terms ||= {};
256    return if (ref $terms eq 'HASH') && exists($terms->{id});
257
258    my $props = $class->properties;
259    my $col = $props->{class_column}
260        or return;
261    if (ref $terms eq 'HASH') {
262        if (exists $terms->{$col}) {
263            if ($terms->{$col} eq '*') {
264                # class term is '*', which signifies filtering for all classes.
265                # simply delete the term in this case.
266                delete $terms->{$col} ;
267            } elsif ($terms->{$col} =~ m/^(\w+:)\*$/) {
268                # class term is in form "foo:*"; translate to a sql-compatible
269                # syntax of "like 'foo:%'"
270                $terms->{$col} = \"like '$1%'";
271            }
272            # term has been explicitly given or explictly removed. make
273            # no further changes.
274            return;
275        }
276        $terms->{$col} = $props->{class_type};
277    }
278    elsif (ref $terms eq 'ARRAY') {
279        @$terms = ( { $col => $props->{class_type} } => 'AND' => [ @$terms ] );
280
281    }
282}
283
284sub class_label {
285    my $pkg = shift;
286    return MT->translate($pkg->datasource);
287}
288
289sub class_label_plural {
290    my $pkg = shift;
291    my $label = $pkg->datasource;
292    $label =~ s/y$/ie/;
293    $label .= 's';
294    return MT->translate($label);
295}
296
297sub class_labels {
298    my $pkg = shift;
299    my @all_types = MT->models($pkg->properties->{datasource});
300    my %names;
301    foreach my $type (@all_types) {
302        my $class = $pkg->class_handler($type);
303        $names{$type} = $class->class_label;
304    }
305    return \%names;
306}
307
308# Returns a hashref of asset identifiers mapped to the localized string
309# used to name them. (Ie, image => 'Image').
310sub class_type {
311    my $pkg = shift;
312    if (ref $pkg) {
313        return $pkg->column($pkg->properties->{class_column});
314    } else {
315        return $pkg->properties->{class_type};
316    }
317}
318
319sub class_handler {
320    my $pkg = shift;
321    my $props = $pkg->properties;
322    my ($type) = @_;
323    my $package = $props->{__type_to_class}{$type};
324    unless ($package) {
325        my $ds = $props->{datasource};
326        if (($type eq $ds) || ($type =~ m/\./)) {
327            $package = MT->model($type);
328        } else {
329            $package = MT->model($ds . '.' . $type);
330        }
331    }
332    if ($package) {
333        if (defined *{$package.'::new'}) {
334            return $package;
335        } else {
336            eval "use $package;";
337            return $package unless $@;
338            eval "use $pkg; $package->new;";
339            return $package unless $@;
340        }
341    }
342    return $pkg;
343}
344
345sub add_class {
346    my $pkg = shift;
347    my ($type, $class) = @_;
348    my $props = $pkg->properties;
349    if ($type =~ m/::/) {
350        ($type, $class) = ($class, $type);
351    }
352
353    if (my $old_class = $props->{__type_to_class}{$type}) {
354        delete $props->{__class_to_type}{$old_class};
355    }
356    $props->{__type_to_class}{$type} = $class;
357    $props->{__class_to_type}{$class} = $type;
358}
359
360# 'meta' metadata column support
361
362sub install_meta {
363    my $class = shift;
364    my ($props) = @_;
365    if ( ( $class ne 'MT::Config' ) && (!$MT::plugins_installed) ) {
366        push @PRE_INIT_META, [$class, $props];
367        return;
368    }
369    my $cprops = $class->properties;
370    my $fields = $cprops->{meta_columns} ||= {};
371    my $meta_col = $cprops->{meta_column};
372    foreach my $name (@{ $props->{columns} }) {
373        $fields->{$name} = ();
374        # Skip adding this method if the class overloads it.
375        # this lets the SUPER::columnname magic do it's thing
376        unless ($class->can($name)) {
377            no strict 'refs'; ## no critic
378            *{"${class}::$name"} = sub { shift->$meta_col($name, @_) };
379        }
380    }
381}
382
383sub has_meta {
384    my $props = $_[0]->properties;
385    return $props->{meta} && (@_ > 1 ? exists $props->{meta_columns}{$_[1]} : 1);
386}
387
388sub pre_save_serialize_metadata {
389    my ($obj) = shift;
390    my $meta_col = $obj->properties->{meta_column};
391    if ($obj->{changed_cols}{$meta_col}) {
392        require MT::Serialize;
393        my $meta = $obj->$meta_col;
394        $obj->$meta_col(MT::Serialize->serialize(\$meta));
395    }
396}
397
398sub __thaw_meta {
399    my ($meta) = @_;
400    $$meta = '' unless defined $$meta;
401    require MT::Serialize;
402    my $out = MT::Serialize->unserialize($$meta);
403    if (ref $out eq 'REF') {
404        return $$out;
405    } else {
406        return {};
407    }
408}
409
410# $obj->meta returns a hashref of metadata information
411# $obj->meta($scalar) allows assignment of a serialized value
412# $obj->meta('name', 'value') assigns an individual metadata element
413# $obj->meta('name') returns an individual metadata value
414# $obj->save will automatically serialize the metadata back to the database
415sub __meta_column {
416    my $obj = shift;
417    my $meta_col = $obj->properties->{meta_column} or return;
418
419    if (@_) {
420        my $var = shift;
421        if ((defined $var) && ($var =~ m/^SERG\0\0\0\0/)) {
422            return $obj->column($meta_col, $var);
423        }
424        my $meta = $obj->column($meta_col);
425        if (!ref $meta) {
426            $meta = __thaw_meta(\$meta);
427            $obj->{column_values}{$meta_col} = $meta;
428        }
429        if (@_) {
430            $meta->{$var} = shift if @_;
431            $obj->{changed_cols}{$meta_col}++;
432        }
433        return $meta->{$var};
434    } else {
435        my $meta = $obj->column($meta_col);
436        if (!ref $meta) {
437            $meta = __thaw_meta(\$meta);
438            $obj->{column_values}{$meta_col} = $meta;
439        }
440        # we should assume changes are going to be made, since
441        # we can't really monitor the hash once it has left us
442        $obj->{changed_cols}{$meta_col}++;
443        return $meta;
444    }
445}
446
447sub ts2db { 
448    return unless $_[0]; 
449    if($_[0] =~ m{ \A \d{4} - }xms) { 
450        return $_[0]; 
451    } 
452    my $ret = sprintf '%04d-%02d-%02d %02d:%02d:%02d', unpack 'A4A2A2A2A2A2', $_[0]; 
453    return $ret; 
454} 
455 
456sub db2ts { 
457    my $ts = $_[0]; 
458    $ts =~ s/(?:\+|-)\d{2}$//; 
459    $ts =~ tr/\- ://d; 
460    return $ts; 
461} 
462
463sub get_date_translator {
464    my $translator = shift;
465    my $change = shift;
466    return sub {
467        my $obj = shift;
468        my $dbd = $obj->driver->dbd;
469        FIELD: for my $field (@{$obj->columns_of_type('datetime', 'timestamp')}) {
470            my $value = $obj->column($field);
471            next FIELD if !defined $value;
472            my $new_val = $translator->($value); 
473            if((defined $new_val) && ($new_val ne $value)) {
474                $obj->column($field, $new_val, { no_changed_flag => !$change });
475            }
476        }
477    };
478}
479
480sub translate_audited_fields {
481    my ($obj, $orig_obj) = @_;
482    my $dbd = $obj->driver->dbd;
483    FIELD: for my $field (qw( created_on modified_on )) {
484        my $value = $orig_obj->column($field);
485        next FIELD if !defined $value;
486        my $new_val = db2ts($value); 
487        if((defined $new_val) && ($new_val ne $value)) {
488            $orig_obj->column($field, $new_val);
489        }
490    }
491    return;
492}
493
494sub nextprev {
495    my $obj = shift;
496    my $class = ref($obj);
497    my %param = @_;
498    my ($direction, $terms, $args, $by_field)
499        = @param{qw( direction terms args by )};
500    return undef unless ($direction eq 'next' || $direction eq 'previous');
501    my $next = $direction eq 'next';
502
503    if (!$by_field) {
504        return if !$class->properties->{audit};
505        $by_field = 'created_on';
506    }
507
508    # Selecting the adjacent object can be tricky since timestamps
509    # are not necessarily unique for entries. If we find that the
510    # next/previous object has a matching timestamp, keep selecting entries
511    # to select all entries with the same timestamp, then compare them using
512    # id as a secondary sort column.
513
514    my ($id, $ts) = ($obj->id, $obj->$by_field());
515    local @$args{qw( sort direction range_incl )}
516        = ($by_field, $next ? 'ascend' : 'descend', { $by_field => 1 });
517    my $iter = $class->load_iter({
518        $by_field => ($next ? [ $ts, undef ] : [ undef, $ts ]),
519        %{$terms}
520    }, $args);
521
522    # This selection should always succeed, but handle situation if
523    # it fails by returning undef.
524    return unless $iter;
525
526    # The 'same' array will hold any entries that have matching
527    # timestamps; we will then sort those by id to find the correct
528    # adjacent object.
529    my @same; 
530    while (my $e = $iter->()) {
531        # Don't consider the object that is 'current'
532        next if $e->id == $id;
533        my $e_ts = $e->$by_field();
534        if ($e_ts eq $ts) {
535            # An object with the same timestamp should only be
536            # considered if the id is in the scope we're looking for
537            # (greater than for the 'next' object; less than for
538            # the 'previous' object).
539            push @same, $e
540                if $next && $e->id > $id or !$next && $e->id < $id;
541        } else {
542            # We found an object with a timestamp different than
543            # the 'current' object.
544            if (!@same) {
545                push @same, $e;
546                # We should check to see if this new timestamped object also
547                # has entries adjacent to _it_ that have the same timestamp.
548                while (my $e = $iter->()) {
549                    push(@same, $e), next if $e->$by_field() eq $e_ts;
550                    $iter->('finish'), last;
551                }
552            } else {
553                $iter->('finish');
554            }
555            return $e unless @same;
556            last;
557        }
558    }
559    if (@same) {
560        # If we only have 1 element in @same, return that.
561        return $same[0] if @same == 1;
562        # Sort remaining elements in @same by id.
563        @same = sort { $a->id <=> $b->id } @same;
564        # Return front of list (smallest id) if selecting 'next'
565        # object. Return tail of list (largest id) if selection 'previous'.
566        return $same[$next ? 0 : $#same];
567    }
568    return;
569}
570
571## Drivers.
572
573# Note: Removed methods: set_driver
574
575sub count          { shift->_proxy('count',          @_) }
576sub count_group_by { shift->_proxy('count_group_by', @_) }
577sub sum_group_by   { shift->_proxy('sum_group_by',   @_) }
578sub avg_group_by   { shift->_proxy('avg_group_by',   @_) }
579sub remove_all     { shift->_proxy('remove_all',     @_) }
580
581sub remove {
582    my $obj = shift;
583    my(@args) = @_;
584    if (!ref $obj) {
585        return $obj->driver->direct_remove($obj, @args);
586    } else {
587        return $obj->driver->remove($obj, @args);
588    }
589}
590
591sub load {
592    my $self = shift;
593    if (defined $_[0] && (!ref $_[0] || (ref $_[0] ne 'HASH' && ref $_[0] ne 'ARRAY'))) {
594        return $self->lookup($_[0]);
595    } else {
596        if (wantarray) {
597            ## MT::Object::load returns a list in list context, just like
598            ## a D::OD search.
599            return $self->search(@_);
600        } else {
601            ## MT::Object::load returns the first result in scalar context.
602            my $iter = $self->search(@_);
603            return if !defined $iter;
604            return $iter->();
605        }
606    }
607}
608
609# More or less replacing Data::ObjectDriver::Driver::DBI::search here
610# to provide an 'early-finish' iterator as MT::ObjectDriver had provided.
611
612sub load_iter   {
613    my $class = shift;
614    my($terms, $args) = @_;
615
616    my $driver = $class->driver;
617    my $dbi_driver = $driver;
618
619    while ( $dbi_driver->isa('Data::ObjectDriver::Driver::BaseCache') ) {
620        $dbi_driver = $dbi_driver->fallback;
621    }
622
623    if ($dbi_driver->dbd eq 'MT::ObjectDriver::Driver::SQLite') {
624        # for SQLite, use search method, since this technique
625        # will cause it to lock the table
626        return scalar $class->search(@_);
627    }
628
629    my $rec = {};
630    my $sth = $dbi_driver->fetch($rec, $class, $terms, $args);
631
632    my $iter = sub {
633        ## This is kind of a hack--we need $driver to stay in scope,
634        ## so that the DESTROY method isn't called. So we include it
635        ## in the scope of the closure.
636        my $d = $dbi_driver;
637        my $d2 = $driver;
638
639        if (@_ && ($_[0] eq 'finish')) {
640            if ($sth) {
641                $sth->finish;
642                $dbi_driver->end_query($sth);
643            }
644            undef $sth;
645            return;
646        }
647
648        unless ($sth->fetch) {
649            $sth->finish;
650            $dbi_driver->end_query($sth);
651            return;
652        }
653        my $obj;
654        $obj = $class->new;
655        $obj->set_values_internal($rec);
656        ## Don't need a duplicate as there's no previous version in memory
657        ## to preserve.
658        $obj->call_trigger('post_load') unless $args->{no_triggers};
659        $driver->cache_object($obj) if $obj && (!$args->{fetchonly});
660        $obj;
661    };
662    return $iter;
663}
664
665## Callbacks
666
667sub assign_audited_fields {
668    my ($obj, $orig_obj) = @_;
669    if ($obj->properties->{audit}) {
670        my $blog_id;
671        if ($obj->has_column('blog_id')) {
672            $blog_id = $obj->blog_id;
673        }
674        my @ts = offset_time_list(time, $blog_id);
675        my $ts = sprintf '%04d%02d%02d%02d%02d%02d',
676            $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
677
678        my $app = MT->instance;
679        if ($app && $app->can('user')) {
680            if (my $user = $app->user) {
681                if (!defined $obj->created_on) {
682                    $obj->created_by($user->id);
683                    $orig_obj->created_by($obj->created_by);
684                }
685            }
686        }
687        unless ($obj->created_on) {
688            $obj->created_on($ts);
689            $orig_obj->created_on($ts);
690            # intentionally not calling modified_by to distinguish
691            $obj->modified_on($ts);
692            $orig_obj->modified_on($ts);
693        }
694    }
695}
696
697sub modified_by {
698    my $obj = shift;
699    my ($user_id) = @_;
700    if ($user_id) {
701        if ($obj->properties->{audit}) {
702            my $res = $obj->SUPER::modified_by($user_id);
703
704            my $blog_id;
705            if ($obj->has_column('blog_id')) {
706                $blog_id = $obj->blog_id;
707            }
708            my @ts = offset_time_list(time, $blog_id);
709            my $ts = sprintf '%04d%02d%02d%02d%02d%02d',
710                $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
711            $obj->modified_on($ts);
712            return $res;
713        }
714    }
715    return $obj->SUPER::modified_by(@_);
716}
717
718# D::OD uses Class::Trigger. Map the call_trigger calls to also invoke
719# MT's callbacks (but internal Class::Trigger routines should be invoked
720# first in the case of pre-triggers, and last in the case of post-triggers).
721
722sub call_trigger {
723    my $obj = shift;
724    my $name = shift;
725    my $class = ref $obj || $obj;
726    my $pre_trigger = $name =~ m/^pre_/;
727    $obj->SUPER::call_trigger($name, @_) if $pre_trigger;
728    MT->run_callbacks($class . '::' . $name, $obj, @_);
729    $obj->SUPER::call_trigger($name, @_) unless $pre_trigger;
730}
731
732# Support for MT-based callbacks.
733
734sub add_callback {
735    my $class = shift;
736    my $meth = shift;
737    MT->add_callback($class . '::' . $meth, @_);
738}
739
740## Construction/initialization.
741
742sub init {
743    my $obj = shift;
744    $obj->SUPER::init(@_);
745    $obj->set_defaults();
746    return $obj;
747}
748
749sub set_defaults {
750    my $obj = shift;
751    my $defaults = $obj->properties->{'defaults'};
752    $obj->{'column_values'} = $defaults ? {%$defaults} : {};
753}
754
755sub __properties { }
756
757our $DRIVER;
758sub driver {
759    require MT::ObjectDriverFactory;
760    return $DRIVER ||= MT::ObjectDriverFactory->new;
761}
762
763sub table_name {
764    my $obj = shift;
765    return $obj->driver->table_for($obj);
766}
767
768sub clone {
769    my $obj = shift;
770    my($param) = @_;
771    my $clone = $obj->SUPER::clone_all;
772
773    ## If the caller has listed a set of columns not to copy to the clone,
774    ## delete them from the clone.
775    if ($param && ($param->{Except} || $param->{except})) {
776        for my $col (keys %{ $param->{Except} || $param->{except} }) {
777            $clone->$col(undef);
778        }
779    }
780    return $clone;
781}
782
783sub columns_of_type {
784    my $obj = shift;
785    my(@types) = @_;
786    my $props = $obj->properties;
787    my $cols = $props->{columns};
788    my $col_defs = $obj->column_defs;
789    my @cols;
790    my %types = map { $_ => 1 } @types;
791    for my $col (@$cols) {
792        push @cols, $col
793            if $col_defs->{$col} && exists $types{$col_defs->{$col}{type}};
794    }
795    \@cols;
796}
797
798sub created_on_obj {
799    my $obj = shift;
800    return $obj->column_as_datetime('created_on');
801}
802
803sub column_as_datetime {
804    my $obj = shift;
805    my ($col) = @_;
806    if (my $ts = $obj->column($col)) {
807        my $blog;
808        if ($obj->isa('MT::Blog')) {
809            $blog = $obj;
810        } else {
811            if (my $blog_id = $obj->blog_id) {
812                require MT::Blog;
813                $blog = MT::Blog->lookup($blog_id);
814            }
815        }
816        my($y, $mo, $d, $h, $m, $s) = $ts =~ /(\d\d\d\d)[^\d]?(\d\d)[^\d]?(\d\d)[^\d]?(\d\d)[^\d]?(\d\d)[^\d]?(\d\d)/;
817        require MT::DateTime;
818        my $four_digit_offset;
819        if ($blog) {
820            $four_digit_offset = sprintf('%.02d%.02d', int($blog->server_offset),
821                                        60 * abs($blog->server_offset
822                                                 - int($blog->server_offset)));
823        }
824        return new MT::DateTime(year => $y, month => $mo, day => $d,
825                                hour => $h, minute => $m, second => $s,
826                                time_zone => $four_digit_offset);
827    }
828    undef;
829}
830
831sub join_on {
832    return [ @_ ];
833}
834
835sub remove_children {
836    my $obj = shift;
837    return 1 unless ref $obj;
838
839    my ($param) = @_;
840    my $child_classes = $obj->properties->{child_classes} || {};
841    my @classes = keys %$child_classes;
842    return 1 unless @classes;
843
844    $param ||= {};
845    my $key = $param->{key} || $obj->datasource . '_id';
846    my $obj_id = $obj->id;
847    for my $class (@classes) {
848        eval "use $class;";
849        $class->remove({ $key => $obj_id });
850    }
851    1;
852}
853
854sub get_by_key {
855    my $class = shift;
856    my ($key) = @_;
857    my($obj) = $class->search($key);
858    $obj ||= new $class;
859    $obj->set_values($key);
860    return $obj;
861}
862
863sub set_by_key {
864    my $class = shift;
865    my ($key, $value) = @_;
866    my ($obj) = $class->search($key);
867    unless ($obj) {
868        $obj = new $class;
869        $obj->set_values($key);
870    }
871    $obj->set_values($value) if $value;
872    $obj->save or return;
873    return $obj;
874}
875
876# This method is overridden since D::OD uses column_values to retrieve
877# the content to cache if caching is enabled. Thus, we must ensure any
878# metadata is serialized prior to caching.
879sub column_values {
880    my $props = $_[0]->properties;
881    if ($props->{meta_column}
882        && $_[0]->{changed_cols}{$props->{meta_column}}) {
883        $_[0]->pre_save_serialize_metadata;
884    }
885    return $_[0]->SUPER::column_values(@_);
886}
887
888# We override D::OD's set_values method here only allowing the
889# assignment of a column if the value given is defined. There are
890# some legacy reasons for doing this, mostly for backward
891# compatibility.
892sub set_values {
893    my $obj = shift;
894    my ($values) = @_;
895    for my $col (keys %$values) {
896        unless ( $obj->has_column($col) ) {
897            Carp::croak("You tried to set inexistent column $col to value $values->{$col} on " . ref($obj));
898        }
899        $obj->$col($values->{$col}) if defined $values->{$col};
900    }
901}
902
903sub column_def {
904    my $obj = shift;
905    my ($name) = @_;
906    my $defs = $obj->column_defs;
907    my $def = $defs->{$name};
908    if (!ref($def)) {
909        $defs->{$name} = $def = $obj->__parse_def($name, $def);
910    }
911    return $def;
912}
913
914sub index_defs {
915    my $obj = shift;
916    my $props = $obj->properties;
917    $props->{indexes};
918}
919
920sub column_defs {
921    my $obj = shift;
922    my $props = $obj->properties;
923    my $defs = $props->{column_defs};
924    return undef if !$defs;
925    my ($key) = keys %$defs;
926    if (!(ref $defs->{$key})) {
927        $obj->__parse_defs($props->{column_defs});
928    }
929    $props->{column_defs};
930}
931
932sub __parse_defs {
933    my $obj = shift;
934    my ($defs) = @_;
935    foreach my $col ( keys %$defs ) {
936        next if ref($defs->{$col});
937        $defs->{$col} = $obj->__parse_def($col, $defs->{$col});
938    }
939}
940
941sub __parse_def {
942    my $obj = shift;
943    my ($col, $def) = @_;
944    return undef if !defined $def;
945    my $props = $obj->properties;
946    my %def;
947    if ($def =~ s/^([^( ]+)\s*//) {
948        $def{type} = $1;
949    }
950    if ($def =~ s/^\((\d+)\)\s*//) {
951        $def{size} = $1;
952    }
953    $def{not_null} = 1 if $def =~ m/\bnot null\b/i;
954    $def{key} = 1 if $def =~ m/\bprimary key\b/i;
955    $def{key} = 1 if ($props->{primary_key}) && ($props->{primary_key} eq $col);
956    $def{auto} = 1 if $def =~ m/\bauto[_ ]increment\b/i;
957    $def{default} = $props->{defaults}{$col} if exists $props->{defaults}{$col};
958    \%def;
959}
960
961sub cache_property {
962    my $obj = shift;
963    my $key = shift;
964    my $code = shift;
965    if (ref $key eq 'CODE') {
966        ($key, $code) = ($code, $key);
967    }
968    $key ||= (caller(1))[3];
969
970    if (@_) {
971        $obj->{__cache}{$key} = $_[0];
972    } else {
973        if ((!exists $obj->{__cache}{$key}) && $code) {
974            $obj->{__cache}{$key} = $code->($obj, @_);
975        }
976    }
977    return exists $obj->{__cache}{$key} ? $obj->{__cache}{$key} : undef;
978}
979
980sub to_hash {
981    my $obj = shift;
982    my $hash = {};
983    my $props = $obj->properties;
984    my $pfx = $obj->datasource;
985    my $values = $obj->column_values;
986    foreach (keys %$values) {
987        $hash->{"${pfx}.$_"} = $values->{$_};
988    }
989    if (my $meta = $props->{meta_columns}) {
990        foreach (keys %$meta) {
991            $hash->{"${pfx}.$_"} = $obj->meta($_);
992        }
993    }
994    if ($obj->has_column('blog_id')) {
995        my $blog_id = $obj->blog_id;
996        require MT::Blog;
997        if (my $blog = MT::Blog->lookup($blog_id)) {
998            my $blog_hash = $blog->to_hash;
999            $hash->{"${pfx}.$_"} = $blog_hash->{$_} foreach keys %$blog_hash;
1000        }
1001    }
1002    $hash;
1003}
1004
10051;
1006__END__
1007
1008=head1 NAME
1009
1010MT::Object - Movable Type base class for database-backed objects
1011
1012=head1 SYNOPSIS
1013
1014Creating an I<MT::Object> subclass:
1015
1016    package MT::Foo;
1017    use strict;
1018
1019    use base 'MT::Object';
1020
1021    __PACKAGE__->install_properties({
1022        columns_defs => {
1023            'id'  => 'integer not null auto_increment',
1024            'foo' => 'string(255)',
1025        },
1026        indexes => {
1027            foo => 1,
1028        },
1029        primary_key => 'id',
1030        datasource => 'foo',
1031    });
1032
1033Using an I<MT::Object> subclass:
1034
1035    use MT;
1036    use MT::Foo;
1037
1038    ## Create an MT object to load the system configuration and
1039    ## initialize an object driver.
1040    my $mt = MT->new;
1041
1042    ## Create an MT::Foo object, fill it with data, and save it;
1043    ## the object is saved using the object driver initialized above.
1044    my $foo = MT::Foo->new;
1045    $foo->foo('bar');
1046    $foo->save
1047        or die $foo->errstr;
1048
1049=head1 DESCRIPTION
1050
1051I<MT::Object> is the base class for all Movable Type objects that will be
1052serialized/stored to some location for later retrieval; this location could
1053be a DBM file, a relational database, etc.
1054
1055Movable Type objects know nothing about how they are stored--they know only
1056of what types of data they consist, the names of those types of data (their
1057columns), etc. The actual storage mechanism is in the I<MT::ObjectDriver::Driver::DBI>
1058class and its driver subclasses; I<MT::Object> subclasses, on the other hand,
1059are essentially just standard in-memory Perl objects, but with a little extra
1060self-knowledge.
1061
1062This distinction between storage and in-memory representation allows objects
1063to be serialized to disk in many different ways--for example, an object could
1064be stored in a MySQL database, in a DBM file, etc. Adding a new storage method
1065is as simple as writing an object driver--a non-trivial task, to be sure, but
1066one that will not require touching any other Movable Type code.
1067
1068=head1 SUBCLASSING
1069
1070Creating a subclass of I<MT::Object> is very simple; you simply need to
1071define the properties and metadata about the object you are creating. Start
1072by declaring your class, and inheriting from I<MT::Object>:
1073
1074    package MT::Foo;
1075    use strict;
1076
1077    use base 'MT::Object';
1078
1079=item * __PACKAGE__->install_properties($args)
1080
1081Then call the I<install_properties> method on your class name; an easy way
1082to get your class name is to use the special I<__PACKAGE__> variable:
1083
1084    __PACKAGE__->install_properties({
1085        column_defs => {
1086            'id' => 'integer not null auto_increment',
1087            'foo' => 'string(255)',
1088        },
1089        indexes => {
1090            foo => 1,
1091        },
1092        primary_key => 'id',
1093        datasource => 'foo',
1094    });
1095
1096I<install_properties> performs the necessary magic to install the metadata
1097about your new class in the MT system. The method takes one argument, a hash
1098reference containing the metadata about your class. That hash reference can
1099have the following keys:
1100
1101=over 4
1102
1103=item * column_defs
1104
1105The definition of the columns (fields) in your object. Column names are also
1106used for method names for your object, so your column name should not
1107contain any strange characters. (It could also be used as part of the name of
1108the column in a relational database table, so that is another reason to keep
1109column names somewhat sane.)
1110
1111The value for the I<columns> key should be a reference to an hashref
1112containing the key/value pairs that are names of your columns matched with
1113their schema definition.
1114
1115The type declaration of a column is pseudo-SQL. The data types loosely match
1116SQL types, but are vendor-neutral, and each MT::ObjectDriver will map these
1117to appropriate types for the database it services. The format of a column
1118type is as follows:
1119
1120    'column_name' => 'type(size) options'
1121
1122The 'type' part of the declaration can be any one of:
1123
1124=over 4
1125
1126=item * string
1127
1128For storing string data, typically up to 255 characters, but assigned a length identified by '(size)'.
1129
1130=item * integer
1131
1132For storing integers, maybe limited to 32 bits.
1133
1134=item * boolean
1135
1136For storing boolean values (numeric values of 1 or 0).
1137
1138=item * smallint
1139
1140For storing small integers, typically limited to 16 bits.
1141
1142=item * datetime
1143
1144For storing a full date and time value.
1145
1146=item * timestamp
1147
1148For storing a date and time that automatically updates upon save.
1149
1150=item * blob
1151
1152For storing binary data.
1153
1154=item * text
1155
1156For storing text data.
1157
1158=item * float
1159
1160For storing floating point values.
1161
1162=back
1163
1164Note: The physical data storage capacity of these types will vary depending on
1165the driver's implementation. Please refer to the documentation of the
1166MT::ObjectDriver you're using to determine the actual capacity for these
1167types.
1168
1169The '(size)' element of the declaration is only valid for the 'string' type.
1170
1171The 'options' element of the declaration is not required, but is used to
1172specify additional attributes of the column. Such as:
1173
1174=over 4
1175
1176=item * not null
1177
1178Specify this option when you wish to constrain the column so that it must contain a defined value. This is only enforced by the database itself, not by the MT::ObjectDriver.
1179
1180=item * auto_increment
1181
1182Specify for integer columns (typically the primary key) to automatically assign a value.
1183
1184=item * primary key
1185
1186Specify for identifying the column as the primary key (only valid for a single column).
1187
1188=back
1189
1190=item * indexes
1191
1192Specifies the column indexes on your objects; this only has consequence for
1193some object drivers (DBM, for example), where indexes are not automatically
1194maintained by the datastore (as they are in a relational database).
1195
1196The value for the I<indexes> key should be a reference to a hash containing
1197column names as keys, and the value C<1> for each key--each key represents
1198a column that should be indexed.
1199
1200B<NOTE:> with the DBM driver, if you do not set up an index on a column you
1201will not be able to select objects with values matching that column using the
1202I<load> and I<load_iter> interfaces (see below).
1203
1204=item * audit
1205
1206Automatically adds bookkeeping capabilities to your class--each object will
1207take on four new columns: I<created_on>, I<created_by>, I<modified_on>, and
1208I<modified_by>. The created_on, created_by columns will be populated
1209automatically (if they have not already been assigned at the time of saving
1210the object). Your application is responsible for updating the modified_on,
1211modified_by columns as these may require explicit application-specific
1212assignments (ie, your application may only want them updated during explicit
1213user interaction with the object, as opposed to cases where the object is
1214being changed and saved for mechanical purposes like upgrading a table).
1215
1216=item * datasource
1217
1218The name of the datasource for your class. The datasource is a name uniquely
1219identifying your class--it is used by the object drivers to construct table
1220names, file names, etc. So it should not be specific to any one driver.
1221
1222=item * meta
1223
1224Specify this property if you wish to add an additional 'meta' column to
1225the object properties. This is a special type of column that is used to
1226store complex data structures for the object. The data is serialized into
1227a blob for storage using the L<MT::Serialize> package.
1228
1229=item * meta_column
1230
1231If you wish to specify the name of the column to be used for storing
1232the object metadata, you may declare this property to name the column.
1233The default column name is 'meta'.
1234
1235=item * class_type
1236
1237If class_type is declared, an additional 'class' column is added to the
1238object properties. This column is then used to differentiate between
1239multiple object types that share the same physical table.
1240
1241Note that if this is used, all searches will be constrained to match
1242the class type of the package.
1243
1244=item * class_column
1245
1246Defines the name of the class column (default is 'class') for storing
1247classed objects (see 'class_type' above).
1248
1249=back
1250
1251=head1 USAGE
1252
1253=head2 System Initialization
1254
1255Before using (loading, saving, removing) an I<MT::Object> class and its
1256objects, you must always initialize the Movable Type system. This is done
1257with the following lines of code:
1258
1259    use MT;
1260    my $mt = MT->new;
1261
1262Constructing a new I<MT> objects loads the system configuration from the
1263F<mt.cfg> configuration file, then initializes the object driver that will
1264be used to manage serialized objects.
1265
1266=head2 Creating a new object
1267
1268To create a new object of an I<MT::Object> class, use the I<new> method:
1269
1270    my $foo = MT::Foo->new;
1271
1272I<new> takes no arguments, and simply initializes a new in-memory object.
1273In fact, you need not ever save this object to disk; it can be used as a
1274purely in-memory object.
1275
1276=head2 Setting and retrieving column values
1277
1278To set the column value of an object, use the name of the column as a method
1279name, and pass in the value for the column:
1280
1281    $foo->foo('bar');
1282
1283The return value of the above call will be C<bar>, the value to which you have
1284set the column.
1285
1286To retrieve the existing value of a column, call the same method, but without
1287an argument:
1288
1289    $foo->foo
1290
1291This returns the value of the I<foo> column from the I<$foo> object.
1292
1293=over 4
1294
1295=item * $obj->init()
1296
1297=back
1298
1299This method is used to initialize the object upon construction.
1300
1301=over 4
1302
1303=item * $obj->set_defaults()
1304
1305=back
1306
1307This method is used by the I<init> method to set the object defaults.
1308
1309=head2 Saving an object
1310
1311To save an object using the object driver, call the I<save> method:
1312
1313=over 4
1314
1315=item * $foo->save();
1316
1317=back
1318
1319On success, I<save> will return some true value; on failure, it will return
1320C<undef>, and you can retrieve the error message by calling the I<errstr>
1321method on the object:
1322
1323    $foo->save
1324        or die "Saving foo failed: ", $foo->errstr;
1325
1326If you are saving objects in a loop, take a look at the
1327L</"Note on object locking">.
1328
1329=head2 Loading an existing object or objects
1330
1331=over 4
1332
1333=item * $obj->load()
1334
1335=item * $obj->load_iter()
1336
1337=back
1338
1339You can load an object from the datastore using the I<load> method. I<load>
1340is by far the most complicated method, because there are many different ways
1341to load an object: by ID, by column value, by using a join with another type
1342of object, etc.
1343
1344In addition, you can load objects either into an array (I<load>), or by using
1345an iterator to step through the objects (I<load_iter>).
1346
1347I<load> has the following general form:
1348
1349    my @objects = MT::Foo->load(\%terms, \%arguments);
1350
1351I<load_iter> has the following general form:
1352
1353    my $iter = MT::Foo->load_iter(\%terms, \%arguments);
1354
1355Both methods share the same parameters; the only difference is the manner in
1356which they return the matching objects.
1357
1358If you call I<load> in scalar context, only the first row of the array
1359I<@objects> will be returned; this works well when you know that your I<load>
1360call can only ever result in one object returned--for example, when you load
1361an object by ID.
1362
1363I<\%terms> should be either:
1364
1365=over 4
1366
1367=item * The numeric ID of an object in the datastore.
1368
1369=item * A reference to a hash.
1370
1371The hash should have keys matching column names and the values are the
1372values for that column.
1373
1374For example, to load an I<MT::Foo> object where the I<foo> column is
1375equal to C<bar>, you could do this:
1376
1377    my @foo = MT::Foo->load({ foo => 'bar' });
1378
1379In addition to a simple scalar, the hash value can be a reference to an array;
1380combined with the I<range> setting in the I<\%arguments> list, you can use
1381this to perform range searches. If the value is a reference, the first element
1382in the array specifies the low end of the range, and the second element the
1383high end.
1384
1385=back
1386
1387I<\%arguments> should be a reference to a hash containing parameters for the
1388search. The following parameters are allowed:
1389
1390=over 4
1391
1392=item * sort => "column"
1393
1394Sort the resulting objects by the column C<column>; C<column> must be an
1395indexed column (see L</"indexes">, above).
1396
1397=item * direction => "ascend|descend"
1398
1399To be used together with I<sort>; specifies the sort order (ascending or
1400descending). The default is C<ascend>.
1401
1402=item * limit => "N"
1403
1404Rather than loading all of the matching objects (the default), load only
1405C<N> objects.
1406
1407=item * offset => "M"
1408
1409To be used together with I<limit>; rather than returning the first C<N>
1410matches (the default), return matches C<M> through C<N + M>.
1411
1412=item * start_val => "value"
1413
1414To be used together with I<limit> and I<sort>; rather than returning the
1415first C<N> matches, return the first C<N> matches where C<column> (the sort
1416column) is greater than C<value>.
1417
1418=item * range
1419
1420To be used together with an array reference as the value for a column in
1421I<\%terms>; specifies that the specific column should be searched for a range
1422of values, rather than one specific value.
1423
1424The value of I<range> should be a hash reference, where the keys are column
1425names, and the values are all C<1>; each key specifies a column that should
1426be interpreted as a range.
1427
1428    MT::Foo->load( { created_on => [ '20011008000000', undef ] },
1429        { range => { created_on => 1 } } );
1430
1431This selects C<MT::Foo> objects whose created_on date is greater than
14322001-10-08 00:00:00.
1433
1434=item * range_incl
1435
1436Like the 'range' attribute, but defines an inclusive range.
1437
1438=item * join
1439
1440Can be used to select a set of objects based on criteria, or sorted by
1441criteria, from another set of objects. An example is selecting the C<N>
1442entries most recently commented-upon; the sorting is based on I<MT::Comment>
1443objects, but the objects returned are actually I<MT::Entry> objects. Using
1444I<join> in this situation is faster than loading the most recent
1445I<MT::Comment> objects, then loading each of the I<MT::Entry> objects
1446individually.
1447
1448Note that I<join> is not a normal SQL join, in that the objects returned are
1449always of only one type--in the above example, the objects returned are only
1450I<MT::Entry> objects, and cannot include columns from I<MT::Comment> objects.
1451
1452I<join> has the following general syntax:
1453
1454    join => MT::Foo->join_on( JOIN_COLUMN, I<\%terms>, I<\%arguments> )
1455
1456Use the actual MT::Object-descended package name and the join_on static method
1457providing these parameters: I<JOIN_COLUMN> is the column joining the two
1458object tables, I<\%terms> and I<\%arguments> have the same meaning as they do
1459in the outer I<load> or I<load_iter> argument lists: they are used to select
1460the objects with which the join is performed.
1461
1462For example, to select the last 10 most recently commmented-upon entries, you
1463could use the following statement:
1464
1465    my @entries = MT::Entry->load(undef, {
1466        'join' => MT::Comment->join_on( 'entry_id',
1467                    { blog_id => $blog_id },
1468                    { 'sort' => 'created_on',
1469                      direction => 'descend',
1470                      unique => 1,
1471                      limit => 10 } )
1472    });
1473
1474In this statement, the I<unique> setting ensures that the I<MT::Entry>
1475objects returned are unique; if this flag were not given, two copies of the
1476same I<MT::Entry> could be returned, if two comments were made on the same
1477entry.
1478
1479=item * unique
1480
1481Ensures that the objects being returned are unique.
1482
1483This is really only useful when used within a I<join>, because when loading
1484data out of a single object datastore, the objects are always going to be
1485unique.
1486
1487=back
1488
1489=head2 Removing an object
1490
1491=over 4
1492
1493=item * $foo->remove()
1494
1495=back
1496
1497To remove an object from the datastore, call the I<remove> method on an
1498object that you have already loaded using I<load>:
1499
1500    $foo->remove();
1501
1502On success, I<remove> will return some true value; on failure, it will return
1503C<undef>, and you can retrieve the error message by calling the I<errstr>
1504method on the object:
1505
1506    $foo->remove
1507        or die "Removing foo failed: ", $foo->errstr;
1508
1509If you are removing objects in a loop, take a look at the
1510L</"Note on object locking">.
1511
1512=head2 Removing select objects of a particular class
1513
1514Combining the syntax of the load and remove methods, you can use the
1515static version of the remove method to remove particular objects:
1516
1517    MT::Foo->remove({ bar => 'baz' });
1518
1519The terms you specify to remove by should be indexed columns. This
1520method will load the object and remove it, firing the callback operations
1521associated with those operations.
1522
1523=head2 Removing all of the objects of a particular class
1524
1525To quickly remove all of the objects of a particular class, call the
1526I<remove_all> method on the class name in question:
1527
1528=over 4
1529
1530=item * MT::Foo->remove_all();
1531
1532=back
1533
1534On success, I<remove_all> will return some true value; on failure, it will
1535return C<undef>, and you can retrieve the error message by calling the
1536I<errstr> method on the class name:
1537
1538    MT::Foo->remove_all
1539        or die "Removing all foo objects failed: ", MT::Foo->errstr;
1540
1541=head2 Removing all the children of an object
1542
1543=over 4
1544
1545=item * $obj->remove_children([ \%param ])
1546
1547=back
1548
1549If your class has registered 'child_classes' as part of it's properties,
1550then this method may be used to remove objects that are associated with
1551the active object.
1552
1553This method is typically used in an overridden 'remove' method.
1554
1555    sub remove {
1556        my $obj = shift;
1557        $obj->remove_children({ key => 'object_id' });
1558        $obj->SUPER::remove(@_);
1559    }
1560
1561The 'key' parameter specified here lets you identify the field name used by
1562the children classes to relate back to the parent class. If unspecified,
1563C<remove_children> will assume the key to be the datasource name of the
1564current class with an '_id' suffix.
1565
1566=head2 Getting the count of a number of objects
1567
1568To determine how many objects meeting a particular set of conditions exist,
1569use the I<count> method:
1570
1571    my $count = MT::Foo->count({ foo => 'bar' });
1572
1573I<count> takes the same arguments (I<\%terms> and I<\%arguments>) as I<load>
1574and I<load_iter>, above.
1575
1576=head2 Determining if an object exists in the datastore
1577
1578To check an object for existence in the datastore, use the I<exists> method:
1579
1580    if ($foo->exists) {
1581        print "Foo $foo already exists!";
1582    }
1583
1584=head2 Counting groups of objects
1585
1586=over 4
1587
1588=item * $obj->count_group_by()
1589
1590=back
1591
1592The count_group_by method can be used to retrieve a list of all the
1593distinct values that appear in a given column along with a count of
1594how many objects carry that value. The routine can also be used with
1595more than one column, in which case it retrieves the distinct pairs
1596(or n-tuples) of values in those columns, along with the counts.
1597Yet more powerful, any SQL expression can be used in place of
1598the column names to count how many object produce any given result
1599values when run through those expressions.
1600
1601  $iter = MT::Foo->count_group_by($terms, {%args, group => $group_exprs});
1602
1603C<$terms> and C<%args> pick out a subset of the MT::Foo objects in the
1604usual way. C<$group_expressions> is an array reference containing the
1605SQL expressions for the values you want to group by. A single row will
1606be returned for each distinct tuple of values resulting from the
1607$group_expressions. For example, if $group_expressions were just a
1608single column (e.g. group => ['created_on']) then a single row would
1609be returned for each distinct value of the 'created_on' column. If
1610$group_expressions were multiple columns, a row would be returned for
1611each distinct pair (or n-tuple) of values found in those columns.
1612
1613Each application of the iterator C<$iter> returns a list in the form:
1614
1615  ($count, $group_val1, $group_val2, ...)
1616
1617Where C<$count> is the number of MT::Foo objects for which the group
1618expressions are the values ($group_val1, $group_val2, ...). These
1619values are in the same order as the corresponding group expressions in
1620the $group_exprs argument.
1621
1622In this example, we load up groups of MT::Pip objects, grouped by the
1623pair (cat_id, invoice_id), and print how many pips have that pair of
1624values.
1625
1626    $iter = MT::Pip->count_group_by(undef,
1627                                    {group => ['cat_id',
1628                                               'invoice_id']});
1629    while (($count, $cat, $inv) = $iter->()) {
1630        print "There are $count Pips with " .
1631            "category $cat and invoice $inv\n";
1632    }
1633
1634=head2 Inspecting and Manipulating Object State
1635
1636=over 4
1637
1638=item * $obj->column_values()
1639
1640=back
1641
1642Use C<column_values> and C<set_values> to get and set the fields of an
1643object I<en masse>. The former returns a hash reference mapping column
1644names to their values in this object. For example:
1645
1646    $values = $obj->column_values()
1647
1648=over 4
1649
1650=item * $obj->set_values()
1651
1652=back
1653
1654C<set_values> accepts a similar hash ref, which need not give a value
1655for every field. For example:
1656
1657    $obj->set_values({col1 => $val1, col2 => $val2});
1658
1659is equivalent to
1660
1661    $obj->col1($val1);
1662    $obj->col2($val2);
1663
1664=head2 Other Methods
1665
1666=over 4
1667
1668=item * $obj->clone([\%param])
1669
1670Returns a clone of C<$obj>. That is, a distinct object which has all
1671the same data stored within it. Changing values within one object does
1672not modify the other.
1673
1674An optional C<except> parameter may be provided to exclude particular
1675columns from the cloning operation. For example, the following would
1676clone the elements of the blog except the name attribute.
1677
1678   $blog->clone({ except => { name => 1 } });
1679
1680=item * $obj->column_names()
1681
1682Returns a list of the names of columns in C<$obj>; includes all those
1683specified to the install_properties method as well as the audit
1684properties (C<created_on>, C<modified_on>, C<created_by>,
1685C<modified_by>), if those were enabled in install_properties.
1686
1687=item * $obj->set_driver()
1688
1689This method sets the object driver to use to link with a database.
1690
1691=item * MT::Foo->driver()
1692
1693=item * $obj->driver()
1694
1695Returns the ObjectDriver object that links this object with a database.
1696
1697=item * $obj->created_on_obj()
1698
1699Returns an MT::DateTime object representing the moment when the
1700object was first saved to the database.
1701
1702=item * MT::Foo->set_by_key($key_terms, $value_terms)
1703
1704A convenience method that loads whatever object matches the C<$key_terms>
1705argument and sets some or all of its fields according to the
1706C<$value_terms>. For example:
1707
1708   MT::Foo->set_by_key({name => 'Thor'},
1709                       {region => 'Norway', gender => 'Male'});
1710
1711This loads the C<MT::Foo> object having 'name' field equal to 'Thor'
1712and sets the 'region' and 'gender' fields appropriately.
1713
1714More than one term is acceptable in the C<$key_terms> argument. The
1715matching object is the one that matches all of the C<$key_terms>.
1716
1717This method only useful if you know that there is a unique object
1718matching the given key. There need not be a unique constraint on the
1719columns named in the C<$key_hash>; but if not, you should be confident
1720that only one object will match the key.
1721
1722=item * MT::Foo->get_by_key($key_terms)
1723
1724A convenience method that loads whatever object matches the C<$key_terms>
1725argument. If no matching object is found, a new object will be constructed
1726and the C<$key_terms> provided will be assigned to it. So regardless of
1727whether the key exists already, this method will return an object with the
1728key requested. Note, however: if a new object is instantiated it is
1729not automatically saved.
1730
1731    my $thor = MT::Foo->get_by_key({name => 'Thor'});
1732    $thor->region('Norway');
1733    $thor->gender('Male');
1734    $thor->save;
1735
1736The fact that it returns a new object if one isn't found is to help
1737optimize this pattern:
1738
1739    my $obj = MT::Foo->load({key => $value});
1740    if (!$obj) {
1741        $obj = new MT::Foo;
1742        $obj->key($value);
1743    }
1744
1745This is equivalent to:
1746
1747    my $obj = MT::Foo->get_by_key({key => $value});
1748
1749If you don't appreciate the autoinstantiation behavior of this method,
1750just use the C<load> method instead.
1751
1752More than one term is acceptable in the C<$key_terms> argument. The
1753matching object is the one that matches all of the C<$key_terms>.
1754
1755This method only useful if you know that there is a unique object
1756matching the given key. There need not be a unique constraint on the
1757columns named in the C<$key_hash>; but if not, you should be confident
1758that only one object will match the key.
1759
1760=item * $obj->cache_property($key, $code)
1761
1762Caches the provided key (e.g. entry, trackback) with the return value
1763of the given code reference (which is often an object load call) so
1764that the value does not have to be recomputed each time.
1765
1766=item * $obj->column_def($name)
1767
1768This method returns the value of the given I<$name> C<column_defs>
1769propery.
1770
1771=item * $obj->column_defs()
1772
1773This method returns all the C<column_defs> of the property of the
1774object.
1775
1776=item * $obj->to_hash()
1777
1778TODO - So far I have not divined what this method actually does. Hints?
1779
1780=item * Class->join_on()
1781
1782This method returns the list of used by the join arguments parameter
1783used by the L<MT::App::CMS/listing> method.
1784
1785=item * $obj->properties()
1786
1787TODO - Return the return properties of the object.
1788
1789=item * $obj->to_xml()
1790
1791TODO - Returns the XML representation of the object.
1792This method is defined in MT/BackupRestore.pm - you must first
1793use MT::BackupRestore to use this method.
1794
1795=item * $obj->restore_parent_ids()
1796
1797TODO - Backup file contains parent objects' ids (foreign keys).  However,
1798when parent objcects are restored, their ids will be changed.  This method
1799is to match the old and new ids of parent objects for children objects to be
1800correctly associated.
1801This method is defined in MT/BackupRestore.pm - you must first
1802use MT::BackupRestore to use this method.
1803
1804=item * $obj->parent_names()
1805
1806TODO - Should be overridden by subclasses to return correct hash
1807whose keys are xml element names of the object's parent objects
1808and values are class names of them.
1809This method is defined in MT/BackupRestore.pm - you must first
1810use MT::BackupRestore to use this method.
1811
1812=back
1813
1814=head1 NOTES
1815
1816=head2 Note on object locking
1817
1818When you read objects from the datastore, the object table is locked with a
1819shared lock; when you write to the datastore, the table is locked with an
1820exclusive lock.
1821
1822Thus, note that saving or removing objects in the same loop where you are
1823loading them from an iterator will not work--the reason is that the datastore
1824maintains a shared lock on the object table while objects are being loaded
1825from the iterator, and thus the attempt to gain an exclusive lock when saving
1826or removing an object will cause deadlock.
1827
1828For example, you cannot do the following:
1829
1830    my $iter = MT::Foo->load_iter({ foo => 'bar' });
1831    while (my $foo = $iter->()) {
1832        $foo->remove;
1833    }
1834
1835Instead you should do either this:
1836
1837    my @foo = MT::Foo->load({ foo => 'bar' });
1838    for my $foo (@foo) {
1839        $foo->remove;
1840    }
1841
1842or this:
1843
1844    my $iter = MT::Foo->load_iter({ foo => 'bar' });
1845    my @to_remove;
1846    while (my $foo = $iter->()) {
1847        push @to_remove, $foo
1848            if SOME CONDITION;
1849    }
1850    for my $foo (@to_remove) {
1851        $foo->remove;
1852    }
1853
1854This last example is useful if you will not be removing every I<MT::Foo>
1855object where I<foo> equals C<bar>, because it saves memory--only the
1856I<MT::Foo> objects that you will be deleting are kept in memory at the same
1857time.
1858
1859=head1 CALLBACKS
1860
1861=over 4
1862
1863=item * $obj->add_callback()
1864
1865=back
1866
1867Most MT::Object operations can trigger callbacks to plugin code. Some
1868notable uses of this feature are: to be notified when a database record is
1869modified, or to pre- or post-process the data being flowing to the
1870database.
1871
1872To add a callback, invoke the C<add_callback> method of the I<MT::Object>
1873subclass, as follows:
1874
1875   MT::Foo->add_callback( "pre_save", <priority>,
1876                          <plugin object>, \&callback_function);
1877
1878The first argument is the name of the hook point. Any I<MT::Object>
1879subclass has a pre_ and a post_ hook point for each of the following
1880operations:
1881
1882    load
1883    save
1884    remove
1885    remove_all
1886    (load_iter operations will call the load callbacks)
1887
1888The second argument, E<lt>priorityE<gt>, is the relative order in
1889which the callback should be called. The value should be between 1 and
189010, inclusive. Callbacks with priority 1 will be called before those
1891with 2, 2 before 3, and so on.
1892
1893Plugins which know they need to run first or last can use the priority
1894values 0 and 11. A callback with priority 0 will run before all
1895others, and if two callbacks try to use that value, an error will
1896result. Likewise priority 11 is exclusive, and runs last.
1897
1898How to remember which callback priorities are special? As you know,
1899most guitar amps have a volume knob that goes from 1 to 10. But, like
1900that of certain rock stars, our amp goes up to 11. A callback with
1901priority 11 is the "loudest" or most powerful callback, as it will be
1902called just before the object is saved to the database (in the case of
1903a 'pre' callback), or just before the object is returned (in the case
1904of a 'post' callback). A callback with priority 0 is the "quietest"
1905callback, as following callbacks can completely overwhelm it. This may
1906be a good choice for your plugin, as you may want your plugin to work
1907well with other plugins. Determining the correct priority is a matter
1908of thinking about your plugin in relation to others, and adjusting the
1909priority based on experience so that users get the best use out of the
1910plugin.
1911
1912The E<lt>plugin objectE<gt> is an object of type MT::Plugin which
1913gives some information about the plugin. This is used to include
1914the plugin's name in any error messages.
1915
1916E<lt>callback functionE<gt> is a code referense for a subroutine that
1917will be called. The arguments to this
1918function vary by operation (see I<MT::Callback> for details),
1919but in each case the first parameter is the I<MT::Callback> object
1920itself:
1921
1922  sub my_callback {
1923      my ($cb, ...) = @_;
1924
1925      if ( <error condition> ) {
1926          return $cb->error("Error message");
1927      }
1928  }
1929
1930Strictly speaking, the return value of a callback is ignored. Calling
1931the error() method of the MT::Callback object (C<$cb> in this case)
1932propagates the error message up to the MT activity log.
1933
1934Another way to handle errors is to call C<die>. If a callback dies,
1935I<MT> will warn the error to the activity log, but will continue
1936processing the MT::Object operation: so other callbacks will still
1937run, and the database operation should still occur.
1938
1939=head2 Any-class Object Callbacks
1940
1941If you add a callback to the MT class with a hook point that begins
1942with C<*::>, such as:
1943
1944    MT->add_callback('*::post_save', 7, $my_plugin, \&code_ref);
1945
1946then it will be called whenever post_save callbacks are called.
1947"Any-class" callbacks are called I<after> all class-specific
1948callbacks. Note that C<add_callback> must be called on the C<MT> class,
1949not on a subclass of C<MT::Object>.
1950
1951=over 4
1952
1953=item * $obj->set_callback_routine()
1954
1955This method just calls the set_callback_routine as defined by the
1956MT::ObjectDriver set with the I<set_driver> method.
1957
1958
1959=back
1960
1961=head2 Caveat
1962
1963Be careful how you handle errors. If you transform data as it goes
1964into and out of the database, and it is possible for one of your
1965callbacks to fail, the data may get saved in an undefined state. It
1966may then be difficult or impossible for the user to recover that data.
1967
1968=head1 AUTHOR & COPYRIGHTS
1969
1970Please see the I<MT> manpage for author, copyright, and license information.
1971
1972=cut
Note: See TracBrowser for help on using the browser.