root/branches/release-33/lib/MT/Object.pm @ 1757

Revision 1757, 59.5 kB (checked in by mpaschal, 20 months ago)

Search all kinds of entries when searching entries
BugzID: 75420

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