root/branches/release-39/lib/MT/Object.pm @ 2459

Revision 2459, 77.7 kB (checked in by bchoate, 18 months ago)

Adding support for a max_group_by method. BugId:79914

  • 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 %meta;
56
57    my $super_props = $class->SUPER::properties();
58    $props->{meta} = 1 if $super_props && $super_props->{meta};
59
60    if ($props->{meta}) {
61        # yank out any meta columns before we start working on column_defs
62        $meta{$_} = delete $props->{column_defs}{$_}
63            for grep { $props->{column_defs}{$_} =~ m/\bmeta\b/ }
64            keys %{ $props->{column_defs} };
65    }
66
67    if ($super_props) {
68        # subclass; merge hash
69        for (qw(primary_key class_column datasource driver audit)) {
70            $props->{$_} = $super_props->{$_}
71                if exists $super_props->{$_} && !(exists $props->{$_});
72        }
73        for my $p (qw(column_defs defaults indexes)) {
74            if (exists $super_props->{$p}) {
75                foreach my $k (keys %{ $super_props->{$p} }) {
76                    if (!exists $props->{$p}{$k}) {
77                        $props->{$p}{$k} = $super_props->{$p}{$k};
78                    }
79                }
80                if ($p eq 'column_defs') {
81                    $class->__parse_defs($props->{column_defs});
82                }
83            }
84        }
85        if ($super_props->{class_type}) {
86            # copy reference of class_to_type/type_to_class hashes
87            $props->{__class_to_type} = $super_props->{__class_to_type};
88            $props->{__type_to_class} = $super_props->{__type_to_class};
89        }
90    }
91
92    # Legacy MT::Object types only define 'columns'; we still support that
93    # but they aren't handled properly with the upgrade system as a result.
94    if (! exists $props->{column_defs}) {
95        map { $props->{column_defs}{$_} = () } @{ $props->{columns} };
96    }
97    $props->{columns} = [ keys %{ $props->{column_defs} } ];
98
99    # Support audit flags
100    if ($props->{audit}) {
101        unless (exists $props->{column_defs}{created_on}) {
102            $props->{column_defs}{created_on} = 'datetime';
103            $props->{column_defs}{created_by} = 'integer';
104            $props->{column_defs}{modified_on} = 'datetime';
105            $props->{column_defs}{modified_by} = 'integer';
106            push @{ $props->{columns} }, qw( created_on created_by modified_on modified_by );
107        }
108    }
109
110    # Classed object types
111    $props->{class_column} ||= 'class' if exists $props->{class_type};
112    if (my $col = $props->{class_column}) {
113        if (!$props->{column_defs}{$col}) {
114            $props->{column_defs}{$col} = 'string(255)';
115            push @{$props->{columns}}, $col;
116            $props->{indexes}{$col} = 1;
117        }
118        if (!$super_props || !$super_props->{class_column}) {
119            $class->add_trigger( pre_search => \&_pre_search_scope_terms_to_class );
120            $class->add_trigger( post_load => \&_post_load_rebless_object );
121        }
122        if (my $type = $props->{class_type}) {
123            $props->{defaults}{$col} = $type;
124            $props->{__class_to_type}{$class} = $type;
125            $props->{__type_to_class}{$type} = $class;
126        }
127    }
128
129    my $type_id;
130    if ($type_id = $props->{class_type}) {
131        if ($type_id ne $props->{datasource}) {
132            $type_id = $props->{datasource} . '.' . $type_id;
133        }
134    } else {
135        $type_id = $props->{datasource};
136    }
137
138    $class->SUPER::install_properties($props);
139
140    # check for any supplemental columns from other components
141    my $more_props = MT->registry('object_types', $type_id);
142    if ($more_props && (ref($more_props) eq 'ARRAY')) {
143        my $cols = {};
144        for my $prop (@$more_props) {
145            next if ref($prop) ne 'HASH';
146            MT::__merge_hash($cols, $prop, 1);
147        }
148        my @classes = grep { !ref($_) } @$more_props;
149        foreach my $isa_class (@classes) {
150            next if UNIVERSAL::isa($class, $isa_class);
151            eval "# line " . __LINE__ . " " . __FILE__ . "\nno warnings 'all';require $isa_class;" or die;
152            no strict 'refs'; ## no critic
153            push @{$class . '::ISA'}, $isa_class;
154        }
155        if (%$cols) {
156            # special case for 'plugin' key...
157            delete $cols->{plugin} if exists $cols->{plugin};
158            for my $name (keys %$cols) {
159                next if exists $props->{column_defs}{$name};
160                if ($cols->{$name} =~ m/\bmeta\b/) {
161                    $meta{$name} = $cols->{$name};
162                    next;
163                }
164
165                $class->install_column($name, $cols->{$name});
166                $props->{indexes}{$name} = 1
167                    if $cols->{$name} =~ m/\bindexed\b/;
168                if ($cols->{$name} =~ m/\bdefault (?:'([^']+?)'|(\d+))\b/) {
169                    $props->{defaults}{$name} = defined $1 ? $1 : $2;
170                }
171            }
172        }
173    }
174
175    my $pk = $props->{primary_key} || '';
176    @{$props->{columns}} = sort { $a eq $pk ? -1 : $b eq $pk ? 1 : $a cmp $b }
177        @{$props->{columns}};
178
179    # Child classes are declared as an array;
180    # convert them to a hashref for easier lookup.
181    if ((ref $props->{child_classes}) eq 'ARRAY') {
182        my $classes = $props->{child_classes};
183        $props->{child_classes} = {};
184        @{$props->{child_classes}}{@$classes} = ();
185    }
186
187    # We're declared as a child of some other class; associate ourselves
188    # with that package (the invoking class should have already use'd it.)
189    if (exists $props->{child_of}) {
190        my $parent_classes = $props->{child_of};
191        if (!ref $parent_classes) {
192            $parent_classes = [ $parent_classes ];
193        }
194        foreach my $pc (@$parent_classes) {
195            my $pp = $pc->properties;
196            $pp->{child_classes} ||= {};
197            $pp->{child_classes}{$class} = ();
198        }
199    }
200
201    # Special handling for 'Taggable' objects; automatic saving
202    # and removal of tags.
203    my @isa;
204    {
205        no strict 'refs';
206        @isa = @{ $class . '::ISA' };
207    }
208    foreach my $isa_pkg ( @isa ) {
209        next unless $isa_pkg =~ /able$/;
210        next if $isa_pkg eq $class;
211        if ($isa_pkg->can('install_properties')) {
212            $isa_pkg->install_properties($class);
213        }
214    }
215
216    # install legacy date translation
217    if (0 < scalar @{ $class->columns_of_type('datetime', 'timestamp') }) {
218        if ($props->{audit}) {
219            $class->add_trigger( pre_save  => \&_assign_audited_fields);
220            $class->add_trigger( post_save => \&_translate_audited_fields );
221        }
222
223        $class->add_trigger( pre_save  => _get_date_translator(\&_ts2db, 1) );
224        $class->add_trigger( post_load => _get_date_translator(\&_db2ts, 0) );
225    }
226
227    if ( exists($props->{cacheable}) && !$props->{cacheable} ) {
228        no warnings 'redefine';
229        no strict 'refs'; ## no critic
230        *{$class . '::driver'} = sub { $_[0]->dbi_driver(@_) };
231    }
232
233    # inherit parent's metadata setup
234    if ($props->{meta}) { # if ($super_props && $super_props->{meta_installed}) {
235        $class->install_meta({ ( %meta ? ( column_defs => \%meta ) : ( columns => [] ) ) });
236        $class->add_trigger( post_remove => \&remove_meta );
237    }
238
239    return $props;
240}
241
242# A post-load trigger for classed objects
243sub _post_load_rebless_object {
244    my $obj = shift;
245    my $props = $obj->properties;
246    if (my $col = $props->{class_column}) {
247        my $type = $obj->column($col);
248        my $pkg = ref($obj);
249        if ($pkg->class_type ne $type) {
250            if (my $class = $props->{__type_to_class}{$type}) {
251                bless $obj, $class;
252            } else {
253                my %models = map { $_ => 1 } MT->models($props->{datasource});
254                if (exists $models{ $props->{datasource} . '.' . $type}) {
255                    $class = MT->model($props->{datasource} . '.' . $type);
256                } elsif (exists $models{$type}) {
257                    $class = MT->model($type);
258                }
259                bless $obj, $class if $class;
260            }
261        }
262    }
263}
264
265# A pre-search trigger for classed objects
266sub _pre_search_scope_terms_to_class {
267    my ($class, $terms, $args) = @_;
268    # scope search terms to class
269
270    $terms ||= {};
271    return if (ref $terms eq 'HASH') && exists($terms->{id});
272
273    my $props = $class->properties;
274    my $col = $props->{class_column}
275        or return;
276    if (ref $terms eq 'HASH') {
277        my $no_class = 0;
278        if ($args->{no_class}) {
279            delete $args->{no_class};
280            $no_class = 1;
281        }
282        if (exists $terms->{$col}) {
283            if ( ( $terms->{$col} eq '*' ) || $no_class ) {
284                # class term is '*', which signifies filtering for all classes.
285                # simply delete the term in this case.
286                delete $terms->{$col} ;
287            } elsif ($terms->{$col} =~ m/^(\w+:)\*$/) {
288                # class term is in form "foo:*"; translate to a sql-compatible
289                # syntax of "like 'foo:%'"
290                $terms->{$col} = \"like '$1%'";
291            }
292            # term has been explicitly given or explictly removed. make
293            # no further changes.
294            return;
295        }
296        $terms->{$col} = $props->{class_type}
297            unless $no_class;
298    }
299    elsif (ref $terms eq 'ARRAY') {
300        if (my @class_terms = grep { ref $_ eq 'HASH' && 1 == scalar keys %$_ && $_->{$col} } @$terms) {
301            # Filter out any unlimiting class terms (class = *).
302            @$terms = grep { ref $_ ne 'HASH' || 1 != scalar keys %$_ || !$_->{$col} || $_->{$col} ne '*' } @$terms;
303
304            # The class column has been explicitly given or removed, so don't
305            # add one.
306            return;
307        }
308        @$terms = ( { $col => $props->{class_type} } => 'AND' => [ @$terms ] );
309    }
310}
311
312sub class_label {
313    my $pkg = shift;
314    return MT->translate($pkg->datasource);
315}
316
317sub class_label_plural {
318    my $pkg = shift;
319    my $label = $pkg->datasource;
320    $label =~ s/y$/ie/;
321    $label .= 's';
322    return MT->translate($label);
323}
324
325sub class_labels {
326    my $pkg = shift;
327    my @all_types = MT->models($pkg->properties->{datasource});
328    my %names;
329    foreach my $type (@all_types) {
330        my $class = $pkg->class_handler($type);
331        $names{$type} = $class->class_label;
332    }
333    return \%names;
334}
335
336# Returns a hashref of asset identifiers mapped to the localized string
337# used to name them. (Ie, image => 'Image').
338sub class_type {
339    my $pkg = shift;
340    if (ref $pkg) {
341        return $pkg->column($pkg->properties->{class_column});
342    } else {
343        return $pkg->properties->{class_type};
344    }
345}
346
347sub class_handler {
348    my $pkg = shift;
349    my $props = $pkg->properties;
350    my ($type) = @_;
351    my $package = $props->{__type_to_class}{$type};
352    unless ($package) {
353        my $ds = $props->{datasource};
354        if (($type eq $ds) || ($type =~ m/\./)) {
355            $package = MT->model($type);
356        } else {
357            $package = MT->model($ds . '.' . $type);
358        }
359    }
360    if ($package) {
361        if (defined *{$package.'::new'}) {
362            return $package;
363        } else {
364            eval "# line " . __LINE__ . " " . __FILE__ . "\nno warnings 'all';use $package;";
365            return $package unless $@;
366            eval "# line " . __LINE__ . " " . __FILE__ . "\nno warnings 'all';use $pkg; $package->new;";
367            return $package unless $@;
368        }
369    }
370    return $pkg;
371}
372
373sub add_class {
374    my $pkg = shift;
375    my ($type, $class) = @_;
376    my $props = $pkg->properties;
377    if ($type =~ m/::/) {
378        ($type, $class) = ($class, $type);
379    }
380
381    if (my $old_class = $props->{__type_to_class}{$type}) {
382        delete $props->{__class_to_type}{$old_class};
383    }
384    $props->{__type_to_class}{$type} = $class;
385    $props->{__class_to_type}{$class} = $type;
386}
387
388# 'meta' metadata column support
389
390sub new {
391    my $class = shift;
392    my $obj = $class->SUPER::new(@_);
393    if ($obj->properties->{meta_installed}) {
394        $obj->init_meta();
395    }
396    return $obj;
397}
398
399sub init_meta {
400    my $obj = shift;
401    require MT::Meta::Proxy;
402    $obj->{__meta} = MT::Meta::Proxy->new($obj);
403}
404
405sub install_meta {
406    my $class = shift;
407    my ($params) = @_;
408    if ( ( $class ne 'MT::Config' ) && (!$MT::plugins_installed) ) {
409        push @PRE_INIT_META, [$class, $params];
410        return;
411    }
412
413    require MT::Meta;
414    my $pkg = ref $class || $class;
415    if (!$pkg->SUPER::properties->{meta_installed}) {
416        $pkg->add_trigger( post_save => \&_post_save_save_metadata );
417        $pkg->add_trigger( post_load => \&_post_load_initialize_metadata );
418    }
419
420    my $props = $class->properties;
421
422    if (!$params->{columns} && !$params->{fields} && !$params->{column_defs}) {
423        return $class->error('No meta fields specified to install_meta');
424    }
425
426    $params->{fields} ||= [];
427    if (my $cols = delete $params->{columns}) {
428        foreach my $col (@$cols) {
429            push @{ $params->{fields} }, {
430                name => $col,
431                type => 'vblob',
432            };
433            # $props->{fields}{$col} = 'vblob';
434        }
435    }
436    if (my $cols = delete $params->{column_defs}) {
437        foreach my $col ( keys %$cols ) {
438            my $type = $cols->{$col};
439            $type =~ s/\s.*//; # take first keyword, ignoring anything after
440            $type .= '_indexed'
441                if $cols->{$col} =~ m/\bindexed\b/;
442            $type = MT::Meta->normalize_type($type);
443
444            push @{ $params->{fields} }, {
445                name => $col,
446                type => $type,
447            };
448            # $props->{fields}{$col} = $type;
449        }
450    }
451
452    $params->{datasource} ||= $class->datasource . '_meta';
453
454    if ($props->{meta_installed} && !@{ $params->{fields} }) {
455        return 1;
456    }
457
458    if (my $fields = MT::Meta->install($pkg, $params)) {
459        # we may have inherited meta fields so lets update with
460        # the fields returned by MT::Meta
461        $props->{fields}->{$_} = $fields->{$_} for keys %$fields;
462    }
463
464    return $props->{meta_installed} = 1;
465}
466
467sub meta_args {
468    my $class = shift;
469    my $id_field = $class->datasource . '_id';
470    return {
471        key         => $class->datasource,
472        column_defs => {
473            $id_field         => 'integer not null',
474            type              => 'string(75) not null',
475            vchar             => 'string(255)',
476            vchar_idx         => 'string(255)',
477            vdatetime         => 'datetime',
478            vdatetime_idx     => 'datetime',
479            vinteger          => 'integer',
480            vinteger_idx      => 'integer',
481            vfloat            => 'float',
482            vfloat_idx        => 'float',
483            vblob             => 'blob',
484            vclob             => 'text',
485        },
486        columns => [ $id_field, qw(
487            type
488            vchar
489            vchar_idx
490            vdatetime
491            vdatetime_idx
492            vinteger
493            vinteger_idx
494            vfloat
495            vfloat_idx
496            vblob
497            vclob
498        ) ],
499        indexes => {
500            $id_field => 1,
501            id_type   => { columns => [ $id_field, 'type' ] },
502            id_type_vchar => { columns => [ $id_field, 'type', 'vchar_idx' ] },
503            id_type_vdt => { columns => [ $id_field, 'type',
504                'vdatetime_idx' ] },
505            id_type_vint => { columns => [ $id_field, 'type',
506                'vinteger_idx' ] },
507            id_type_vflt => { columns => [ $id_field, 'type',
508                'vfloat_idx' ] },
509        },
510        primary_key => [ $id_field, 'type' ],
511    };
512}
513
514sub has_meta {
515    my $obj = shift;
516    return $obj->is_meta_column(@_) if @_;
517    return $obj->properties->{meta_installed} ? 1 : 0;
518}
519
520sub _post_load_initialize_metadata {
521    my $obj = shift;
522    if (defined $obj && $obj->properties->{meta_installed}) {
523        $obj->init_meta();
524        $obj->{__meta}->set_primary_keys($obj);
525    }
526}
527
528sub is_meta_column {
529    my $obj = shift;
530    my ($field) = @_;
531
532    my $props = $obj->properties;
533    return unless $props->{meta_installed};
534
535    my $meta = $obj->meta_pkg;
536    return 1 if $props->{fields}{$field};
537
538    return;
539}
540
541sub meta_pkg {
542    my $class = shift;
543    my $props = $class->properties;
544    return unless $props->{meta}; # this only works for meta-enabled classes
545
546    return $props->{meta_pkg} if $props->{meta_pkg};
547
548    my $meta = ref $class || $class;
549    $meta .= '::Meta';
550    return $props->{meta_pkg} = $meta;
551}
552
553sub has_column {
554    my $obj = shift;
555    return 1 if $obj->SUPER::has_column(@_);
556    return 1 if $obj->is_meta_column(@_);
557    return;
558}
559
560sub _post_save_save_metadata {
561    my $obj = shift;
562    if (defined $obj && exists $obj->{__meta}) {
563        $obj->{__meta}->set_primary_keys($obj);
564        $obj->{__meta}->save;
565    }
566}
567
568sub meta {
569    my $obj = shift;
570    my ( $name, $value ) = @_;
571
572    return !$obj->{__meta} ? undef
573         : 2 == scalar @_  ? $obj->{__meta}->set( $name, $value )
574         : 1 == scalar @_  ? (
575           ref($name) eq 'HASH' ? $obj->{__meta}->set_hash(@_)
576             :                    $obj->{__meta}->get($name) )
577         :                   $obj->{__meta}->get_hash;
578}
579
580sub meta_obj {
581    my $obj = shift;
582    return $obj->{__meta};
583}
584
585sub column_func {
586    my $obj = shift;
587    my ($col) = @_;
588    return if !$col;
589
590    return $obj->SUPER::column_func(@_)
591        if !$obj->is_meta_column($col);
592
593    return sub {
594        my $obj = shift;
595        if (@_) {
596            $obj->{__meta}->set($col, @_);
597        }
598        else {
599            $obj->{__meta}->get($col);
600        }
601    };
602}
603
604sub _ts2db { 
605    return unless $_[0]; 
606    if($_[0] =~ m{ \A \d{4} - }xms) { 
607        return $_[0]; 
608    } 
609    my $ret = sprintf '%04d-%02d-%02d %02d:%02d:%02d', unpack 'A4A2A2A2A2A2', $_[0]; 
610    return $ret; 
611}
612 
613sub _db2ts { 
614    my $ts = $_[0];
615    $ts =~ s/(?:\+|-)\d{2}$//;
616    $ts =~ tr/\- ://d;
617    return $ts;
618}
619
620sub _get_date_translator {
621    my $translator = shift;
622    my $change = shift;
623    return sub {
624        my $obj = shift;
625        my $dbd = $obj->driver->dbd;
626        FIELD: for my $field (@{$obj->columns_of_type('datetime', 'timestamp')}) {
627            my $value = $obj->column($field);
628            next FIELD if !defined $value;
629            my $new_val = $translator->($value); 
630            if((defined $new_val) && ($new_val ne $value)) {
631                $obj->column($field, $new_val, { no_changed_flag => !$change });
632            }
633        }
634        if ( $obj->has_meta ) {
635            my @meta_columns = MT::Meta->metadata_by_class( ref $obj );
636            my @date_meta = grep {
637                   $_->{type} eq 'vdatetime'
638                || $_->{type} eq 'vdatetime_idx'
639            } @meta_columns;
640            META_FIELD: for my $f (@date_meta) {
641                my $field = $f->{name};
642                my $value = $obj->$field;
643                next META_FIELD if !defined $value;
644                my $new_val = $translator->($value); 
645                if((defined $new_val) && ($new_val ne $value)) {
646                    $obj->$field( $new_val );
647                }
648            }
649        }
650    };
651}
652
653sub _translate_audited_fields {
654    my ($obj, $orig_obj) = @_;
655    my $dbd = $obj->driver->dbd;
656    FIELD: for my $field (qw( created_on modified_on )) {
657        my $value = $orig_obj->column($field);
658        next FIELD if !defined $value;
659        my $new_val = _db2ts($value); 
660        if((defined $new_val) && ($new_val ne $value)) {
661            $orig_obj->column($field, $new_val);
662        }
663    }
664    return;
665}
666
667sub nextprev {
668    my $obj = shift;
669    my $class = ref($obj);
670    my %param = @_;
671    my ($direction, $terms, $args, $by_field)
672        = @param{qw( direction terms args by )};
673    return undef unless ($direction eq 'next' || $direction eq 'previous');
674    my $next = $direction eq 'next';
675
676    if (!$by_field) {
677        return if !$class->properties->{audit};
678        $by_field = 'created_on';
679    }
680
681    # Selecting the adjacent object can be tricky since timestamps
682    # are not necessarily unique for entries. If we find that the
683    # next/previous object has a matching timestamp, keep selecting entries
684    # to select all entries with the same timestamp, then compare them using
685    # id as a secondary sort column.
686
687    my ($id, $ts) = ($obj->id, $obj->$by_field());
688    local @$args{qw( sort range_incl )}
689        = ( [ { column => $by_field, desc => $next ? 'ASC' : 'DESC' },
690            { column => 'id', desc => $next ? 'ASC' : 'DESC' } ],
691            { $by_field => 1 });
692
693    my $sibling = $class->load({
694        $by_field => ($next ? [ $ts, undef ] : [ undef, $ts ]),
695        'id' => $id,
696        %{$terms}
697    }, { not => { 'id' => 1 }, limit => 1, %$args });
698
699    return $sibling;
700}
701
702## Drivers.
703
704sub count          { shift->_proxy('count',          @_) }
705sub exist          { shift->_proxy('exist',          @_) }
706sub count_group_by { shift->_proxy('count_group_by', @_) }
707sub sum_group_by   { shift->_proxy('sum_group_by',   @_) }
708sub avg_group_by   { shift->_proxy('avg_group_by',   @_) }
709sub max_group_by   { shift->_proxy('max_group_by',   @_) }
710sub remove_all     { shift->_proxy('remove_all',     @_) }
711
712sub remove {
713    my $obj = shift;
714    my(@args) = @_;
715    if (!ref $obj) {
716        $obj->remove_meta( @args ) if $obj->has_meta;
717        $obj->remove_scores( @args ) if $obj->isa('MT::Scorable');
718        return $obj->driver->direct_remove($obj, @args);
719    } else {
720        return $obj->driver->remove($obj, @args);
721    }
722}
723
724sub load {
725    my $self = shift;
726    if (defined $_[0] && (!ref $_[0] || (ref $_[0] ne 'HASH' && ref $_[0] ne 'ARRAY'))) {
727        return $self->lookup($_[0]);
728    } else {
729        if (wantarray) {
730            ## MT::Object::load returns a list in list context, just like
731            ## a D::OD search.
732            return $self->search(@_);
733        } else {
734            ## MT::Object::load returns the first result in scalar context.
735            my $iter = $self->search(@_);
736            return if !defined $iter;
737            return $iter->();
738        }
739    }
740}
741
742# More or less replacing Data::ObjectDriver::Driver::DBI::search here
743# to provide an 'early-finish' iterator as MT::ObjectDriver had provided.
744
745sub load_iter   {
746    my $class = shift;
747    my($terms, $args) = @_;
748
749    my $driver = $class->driver;
750    my $dbi_driver = $driver;
751
752    while ( $dbi_driver->isa('Data::ObjectDriver::Driver::BaseCache') ) {
753        $dbi_driver = $dbi_driver->fallback;
754    }
755
756    if ($dbi_driver->dbd eq 'MT::ObjectDriver::Driver::DBD::SQLite') {
757        # for SQLite, use search method, since this technique
758        # will cause it to lock the table
759        return scalar $class->search(@_);
760    }
761
762    my $rec = {};
763    my $sth = $dbi_driver->fetch($rec, $class, $terms, $args);
764
765    my $iter = sub {
766        ## This is kind of a hack--we need $driver to stay in scope,
767        ## so that the DESTROY method isn't called. So we include it
768        ## in the scope of the closure.
769        my $d = $dbi_driver;
770        my $d2 = $driver;
771
772        if (@_ && ($_[0] eq 'finish')) {
773            if ($sth) {
774                $sth->finish;
775                $dbi_driver->end_query($sth);
776            }
777            undef $sth;
778            return;
779        }
780
781        unless ($sth->fetch) {
782            $sth->finish;
783            $dbi_driver->end_query($sth);
784            return;
785        }
786        my $obj;
787        $obj = $class->new;
788        $obj->set_values_internal($rec);
789        ## Don't need a duplicate as there's no previous version in memory
790        ## to preserve.
791        $obj->call_trigger('post_load') unless $args->{no_triggers};
792        $driver->cache_object($obj) if $obj && (!$args->{fetchonly});
793        $obj;
794    };
795    return $iter;
796}
797
798## Callbacks
799
800sub _assign_audited_fields {
801    my ($obj, $orig_obj) = @_;
802    if ($obj->properties->{audit}) {
803        my $blog_id;
804        if ($obj->has_column('blog_id')) {
805            $blog_id = $obj->blog_id;
806        }
807        my @ts = offset_time_list(time, $blog_id);
808        my $ts = sprintf '%04d%02d%02d%02d%02d%02d',
809            $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
810
811        my $app = MT->instance;
812        if ($app && $app->can('user')) {
813            if (my $user = $app->user) {
814                if (!defined $obj->created_on) {
815                    $obj->created_by($user->id);
816                    $orig_obj->created_by($obj->created_by);
817                }
818            }
819        }
820        unless ($obj->created_on) {
821            $obj->created_on($ts);
822            $orig_obj->created_on($ts);
823            # intentionally not calling modified_by to distinguish
824            $obj->modified_on($ts);
825            $orig_obj->modified_on($ts);
826        }
827    }
828}
829
830sub modified_by {
831    my $obj = shift;
832    my ($user_id) = @_;
833    if ($user_id) {
834        if ($obj->properties->{audit}) {
835            my $res = $obj->SUPER::modified_by($user_id);
836
837            my $blog_id;
838            if ($obj->has_column('blog_id')) {
839                $blog_id = $obj->blog_id;
840            }
841            my @ts = offset_time_list(time, $blog_id);
842            my $ts = sprintf '%04d%02d%02d%02d%02d%02d',
843                $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
844            $obj->modified_on($ts);
845            return $res;
846        }
847    }
848    return $obj->SUPER::modified_by(@_);
849}
850
851# D::OD uses Class::Trigger. Map the call_trigger calls to also invoke
852# MT's callbacks (but internal Class::Trigger routines should be invoked
853# first in the case of pre-triggers, and last in the case of post-triggers).
854
855sub call_trigger {
856    my $obj = shift;
857    my $name = shift;
858    my $class = ref $obj || $obj;
859    my $pre_trigger = $name =~ m/^pre_/;
860    $obj->SUPER::call_trigger($name, @_) if $pre_trigger;
861    MT->run_callbacks($class . '::' . $name, $obj, @_);
862    $obj->SUPER::call_trigger($name, @_) unless $pre_trigger;
863}
864
865# Support for MT-based callbacks.
866
867sub add_callback {
868    my $class = shift;
869    my $meth = shift;
870    MT->add_callback($class . '::' . $meth, @_);
871}
872
873## Construction/initialization.
874
875sub init {
876    my $obj = shift;
877    $obj->SUPER::init(@_);
878    $obj->set_defaults();
879    return $obj;
880}
881
882sub set_defaults {
883    my $obj = shift;
884    my $defaults = $obj->properties->{'defaults'};
885    $obj->{'column_values'} = $defaults ? {%$defaults} : {};
886}
887
888sub __properties { }
889
890our $DRIVER;
891sub driver {
892    require MT::ObjectDriverFactory;
893    return $DRIVER ||= MT::ObjectDriverFactory->new;
894}
895
896# ref to the fallback driver for non-cacheable classes
897our $DBI_DRIVER;
898sub dbi_driver {
899    unless ($DBI_DRIVER) {
900        my $driver = driver(@_);
901        while ( $driver->can('fallback') ) {
902            if ($driver->fallback) {
903                $driver = $driver->fallback;
904            } else {
905                last;
906            }
907        }
908        $DBI_DRIVER = $driver;
909    }
910    return $DBI_DRIVER;
911}
912
913sub table_name {
914    my $obj = shift;
915    return $obj->driver->table_for($obj);
916}
917
918sub clone_all {
919    my $obj = shift;
920    my $clone = $obj->SUPER::clone_all();
921    if ($clone->properties->{meta_installed}) {
922        $clone->init_meta();
923        $clone->meta( $obj->meta );
924    }
925    return $clone;
926}
927
928sub clone {
929    my $obj = shift;
930    my($param) = @_;
931    my $clone = $obj->clone_all();
932
933    ## If the caller has listed a set of columns not to copy to the clone,
934    ## delete them from the clone.
935    if ($param && ($param->{Except} || $param->{except})) {
936        for my $col (keys %{ $param->{Except} || $param->{except} }) {
937            $clone->$col(undef);
938        }
939    }
940    return $clone;
941}
942
943sub columns_of_type {
944    my $obj = shift;
945    my(@types) = @_;
946    my $props = $obj->properties;
947    my $cols = $props->{columns};
948    my $col_defs = $obj->column_defs;
949    my @cols;
950    my %types = map { $_ => 1 } @types;
951    for my $col (@$cols) {
952        push @cols, $col
953            if $col_defs->{$col} && exists $types{$col_defs->{$col}{type}};
954    }
955    \@cols;
956}
957
958sub created_on_obj {
959    my $obj = shift;
960    return $obj->column_as_datetime('created_on');
961}
962
963sub column_as_datetime {
964    my $obj = shift;
965    my ($col) = @_;
966    if (my $ts = $obj->column($col)) {
967        my $blog;
968        if ($obj->isa('MT::Blog')) {
969            $blog = $obj;
970        } else {
971            if (my $blog_id = $obj->blog_id) {
972                require MT::Blog;
973                $blog = MT::Blog->lookup($blog_id);
974            }
975        }
976        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)/;
977        require MT::DateTime;
978        my $four_digit_offset;
979        if ($blog) {
980            $four_digit_offset = sprintf('%.02d%.02d', int($blog->server_offset),
981                                        60 * abs($blog->server_offset
982                                                 - int($blog->server_offset)));
983        }
984        return new MT::DateTime(year => $y, month => $mo, day => $d,
985                                hour => $h, minute => $m, second => $s,
986                                time_zone => $four_digit_offset);
987    }
988    undef;
989}
990
991sub join_on {
992    return [ @_ ];
993}
994
995sub remove_meta {
996    my $obj = shift;
997    my $mpkg = $obj->meta_pkg or return;
998    if ( ref $obj ) {
999        my $id_field = $obj->datasource . '_id';
1000        return $mpkg->remove({ $id_field => $obj->id });
1001    } else {
1002        # static invocation
1003        my ($terms, $args) = @_;
1004        $args = { %$args } if $args; # copy so we can alter
1005        my $meta_id = $obj->datasource . '_id';
1006        my $offset = 0;
1007        $args ||= {};
1008        $args->{fetchonly} = [ 'id' ];
1009        $args->{join} = [ $mpkg, $meta_id ];
1010        $args->{no_triggers} = 1;
1011        $args->{limit} = 50;
1012        while ( $offset >= 0 ) {
1013            $args->{offset} = $offset;
1014            if (my @list = $obj->load( $terms, $args )) {
1015                my @ids = map { $_->id } @list;
1016                $mpkg->driver->direct_remove( $mpkg, { $meta_id => \@ids });
1017                if ( scalar @list == 50 ) {
1018                    $offset += 50;
1019                } else {
1020                    $offset = -1; # break loop
1021                }
1022            } else {
1023                $offset = -1;
1024            }
1025        }
1026        return 1;
1027    }
1028}
1029
1030sub remove_scores {
1031    my $class = shift;
1032    require MT::ObjectScore;
1033    my ($terms, $args) = @_;
1034    $args = { %$args } if $args; # copy so we can alter
1035    my $offset = 0;
1036    $args ||= {};
1037    $args->{fetchonly} = [ 'id' ];
1038    $args->{join} = [ 'MT::ObjectScore', 'object_id', {
1039        object_ds => $class->datasource } ];
1040    $args->{no_triggers} = 1;
1041    $args->{limit} = 50;
1042    while ( $offset >= 0 ) {
1043        $args->{offset} = $offset;
1044        if (my @list = $class->load( $terms, $args )) {
1045            my @ids = map { $_->id } @list;
1046            MT::ObjectScore->driver->direct_remove( 'MT::ObjectScore', {
1047                object_ds => $class->datasource, 'object_id' => \@ids });
1048            if ( scalar @list == 50 ) {
1049                $offset += 50;
1050            } else {
1051                $offset = -1; # break loop
1052            }
1053        } else {
1054            $offset = -1;
1055        }
1056    }
1057    return 1;
1058}
1059
1060sub remove_children {
1061    my $obj = shift;
1062    return 1 unless ref $obj;
1063
1064    my ($param) = @_;
1065    my $child_classes = $obj->properties->{child_classes} || {};
1066    my @classes = keys %$child_classes;
1067    return 1 unless @classes;
1068
1069    $param ||= {};
1070    my $key = $param->{key} || $obj->datasource . '_id';
1071    my $obj_id = $obj->id;
1072    for my $class (@classes) {
1073        eval "# line " . __LINE__ . " " . __FILE__ . "\nno warnings 'all';use $class;";
1074        $class->remove({ $key => $obj_id });
1075    }
1076    1;
1077}
1078
1079sub get_by_key {
1080    my $class = shift;
1081    my ($key) = @_;
1082    my($obj) = $class->search($key);
1083    $obj ||= new $class;
1084    $obj->set_values($key);
1085    return $obj;
1086}
1087
1088sub set_by_key {
1089    my $class = shift;
1090    my ($key, $value) = @_;
1091    my ($obj) = $class->search($key);
1092    unless ($obj) {
1093        $obj = new $class;
1094        $obj->set_values($key);
1095    }
1096    $obj->set_values($value) if $value;
1097    $obj->save or return $class->error($obj->errstr);
1098    return $obj;
1099}
1100
1101sub deflate {
1102    my $obj = shift;
1103    my $data = $obj->SUPER::deflate();
1104    if ($obj->has_meta()) {
1105        $data->{meta} = $obj->{__meta}->deflate_meta();
1106    }
1107    return $data;
1108}
1109
1110sub inflate {
1111    my $class = shift;
1112    my ($data) = @_;
1113    my $obj = $class->SUPER::inflate(@_);
1114    if ($class->has_meta()) {
1115        $obj->{__meta}->inflate_meta($data->{meta});
1116    }
1117    return $obj;
1118}
1119
1120# We override D::OD's set_values method here only allowing the
1121# assignment of a column if the value given is defined. There are
1122# some legacy reasons for doing this, mostly for backward
1123# compatibility.
1124sub set_values {
1125    my $obj = shift;
1126    my ($values) = @_;
1127    for my $col (keys %$values) {
1128        unless ( $obj->has_column($col) ) {
1129            Carp::croak("You tried to set inexistent column $col to value $values->{$col} on " . ref($obj));
1130        }
1131        $obj->$col($values->{$col}) if defined $values->{$col};
1132    }
1133}
1134
1135sub column_def {
1136    my $obj = shift;
1137    my ($name) = @_;
1138    my $defs = $obj->column_defs;
1139    my $def = $defs->{$name};
1140    if (!ref($def)) {
1141        $defs->{$name} = $def = $obj->__parse_def($name, $def);
1142    }
1143    return $def;
1144}
1145
1146sub index_defs {
1147    my $obj = shift;
1148    my $props = $obj->properties;
1149    $props->{indexes};
1150}
1151
1152sub column_defs {
1153    my $obj = shift;
1154    my $props = $obj->properties;
1155    my $defs = $props->{column_defs};
1156    return undef if !$defs;
1157    my ($key) = keys %$defs;
1158    if (!(ref $defs->{$key})) {
1159        $obj->__parse_defs($props->{column_defs});
1160    }
1161    $props->{column_defs};
1162}
1163
1164sub __parse_defs {
1165    my $obj = shift;
1166    my ($defs) = @_;
1167    foreach my $col ( keys %$defs ) {
1168        next if ref($defs->{$col});
1169        $defs->{$col} = $obj->__parse_def($col, $defs->{$col});
1170    }
1171}
1172
1173sub __parse_def {
1174    my $obj = shift;
1175    my ($col, $def) = @_;
1176    return undef if !defined $def;
1177    my $props = $obj->properties;
1178    my %def;
1179    if ($def =~ s/^([^( ]+)\s*//) {
1180        $def{type} = $1;
1181    }
1182    if ($def =~ s/^\((\d+)\)\s*//) {
1183        $def{size} = $1;
1184    }
1185    $def{not_null} = 1 if $def =~ m/\bnot null\b/i;
1186    $def{key} = 1 if $def =~ m/\bprimary key\b/i;
1187    $def{key} = 1 if ($props->{primary_key}) && ($props->{primary_key} eq $col);
1188    $def{auto} = 1 if $def =~ m/\bauto[_ ]increment\b/i;
1189    $def{default} = $props->{defaults}{$col}
1190        if exists $props->{defaults}{$col};
1191    \%def;
1192}
1193
1194sub cache_property {
1195    my $obj = shift;
1196    my $key = shift;
1197    my $code = shift;
1198    if (ref $key eq 'CODE') {
1199        ($key, $code) = ($code, $key);
1200    }
1201    $key ||= (caller(1))[3];
1202
1203    my $r = MT->request;
1204    my $oc = $r->cache('object_cache');
1205    unless ($oc) {
1206        $oc = {};
1207        $r->cache('object_cache', $oc);
1208    }
1209
1210    my $pk = $obj->primary_key;
1211    $pk = join ":", @$pk if ref $pk;
1212    $oc = $oc->{ref($obj). ':' . $pk} ||= {};
1213
1214    if (@_) {
1215        $oc->{$key} = $_[0];
1216    } else {
1217        if ((!exists $oc->{$key}) && $code) {
1218            $oc->{$key} = $code->($obj, @_);
1219        }
1220    }
1221    return exists $oc->{$key} ? $oc->{$key} : undef;
1222}
1223
1224sub clear_cache {
1225    my $obj = shift;
1226    my $oc = MT->request('object_cache') or return;
1227    if (@_) {
1228        $oc = $oc->{"$obj"};
1229        delete $oc->{shift} if $oc;
1230    } else {
1231        delete $oc->{"$obj"};
1232    }
1233}
1234
1235sub to_hash {
1236    my $obj = shift;
1237    my $hash = {};
1238    my $props = $obj->properties;
1239    my $pfx = $obj->datasource;
1240    my $values = $obj->column_values;
1241    foreach (keys %$values) {
1242        $hash->{"${pfx}.$_"} = $values->{$_};
1243    }
1244    if (my $meta = $props->{meta_columns}) {
1245        foreach (keys %$meta) {
1246            $hash->{"${pfx}.$_"} = $obj->meta($_);
1247        }
1248    }
1249    if ($obj->has_column('blog_id')) {
1250        my $blog_id = $obj->blog_id;
1251        require MT::Blog;
1252        if (my $blog = MT::Blog->lookup($blog_id)) {
1253            my $blog_hash = $blog->to_hash;
1254            $hash->{"${pfx}.$_"} = $blog_hash->{$_} foreach keys %$blog_hash;
1255        }
1256    }
1257    $hash;
1258}
1259
1260sub search_by_meta {
1261    my $class = shift;
1262    my($key, $value, $terms, $args) = @_;
1263    $terms ||= {}; $args ||= {};
1264    return unless $class->properties->{meta_installed};
1265    return $class->error("Unknown meta '$key' on $class")
1266        unless $class->is_meta_column($key);
1267
1268    my $meta_rec = MT::Meta->metadata_by_name($class, $key);
1269    my $type_col = $meta_rec->{type};
1270    my $type_id  = $meta_rec->{name};
1271    my $meta_terms = {
1272        $type_col => $value,
1273        type      => $type_id,
1274        %$terms,
1275    };
1276    my $meta_class = $class->meta_pkg;
1277    my $meta_pk = $meta_class->primary_key_tuple;
1278    my @metaobjs = $meta_class->search(
1279        $meta_terms, { %$args, fetchonly => $meta_pk }
1280    );
1281
1282    my $pk = $class->primary_key_tuple;
1283    my $get_pk = sub { 
1284        my $meta = shift;
1285        [ map { $meta->$_ } @$meta_pk ];
1286    };
1287
1288    return unless @metaobjs;
1289    return grep defined, @{ $class->lookup_multi([ map { $get_pk->($_) } @metaobjs ]) };
1290}
1291
1292package MT::Object::Meta;
1293
1294use base qw( Data::ObjectDriver::BaseObject );
1295
1296sub install_properties {
1297    my $class = shift;
1298    my ($props) = @_;
1299    $props->{column_defs}->{$_} ||= 'string'
1300        for @{ $props->{columns} };
1301    $class->SUPER::install_properties(@_);
1302}
1303
1304sub meta_pkg { undef }
1305
1306*table_name = \&MT::Object::table_name;
1307*column_defs = \&MT::Object::column_defs;
1308*column_def = \&MT::Object::column_def;
1309*index_defs = \&MT::Object::index_defs;
1310*__parse_defs = \&MT::Object::__parse_defs;
1311*__parse_def = \&MT::Object::__parse_def;
1312*count = \&MT::Object::count;
1313*columns_of_type = \&MT::Object::columns_of_type;
1314
1315*driver = \&MT::Object::dbi_driver;
1316
1317# TODO: copy this too
1318sub blob_requires_zip {}
1319
13201;
1321__END__
1322
1323=head1 NAME
1324
1325MT::Object - Movable Type base class for database-backed objects
1326
1327=head1 SYNOPSIS
1328
1329Creating an I<MT::Object> subclass:
1330
1331    package MT::Foo;
1332    use strict;
1333
1334    use base 'MT::Object';
1335
1336    __PACKAGE__->install_properties({
1337        columns_defs => {
1338            'id'  => 'integer not null auto_increment',
1339            'foo' => 'string(255)',
1340        },
1341        indexes => {
1342            foo => 1,
1343        },
1344        primary_key => 'id',
1345        datasource => 'foo',
1346    });
1347
1348Using an I<MT::Object> subclass:
1349
1350    use MT;
1351    use MT::Foo;
1352
1353    ## Create an MT object to load the system configuration and
1354    ## initialize an object driver.
1355    my $mt = MT->new;
1356
1357    ## Create an MT::Foo object, fill it with data, and save it;
1358    ## the object is saved using the object driver initialized above.
1359    my $foo = MT::Foo->new;
1360    $foo->foo('bar');
1361    $foo->save
1362        or die $foo->errstr;
1363
1364=head1 DESCRIPTION
1365
1366I<MT::Object> is the base class for all Movable Type objects that will be
1367serialized/stored to some location for later retrieval.
1368
1369Movable Type objects know nothing about how they are stored--they know only
1370of what types of data they consist, the names of those types of data (their
1371columns), etc. The actual storage mechanism is in the L<Data::ObjectDriver>
1372class and its driver subclasses; I<MT::Object> subclasses, on the other hand,
1373are essentially just standard in-memory Perl objects, but with a little extra
1374self-knowledge.
1375
1376This distinction between storage and in-memory representation allows objects
1377to be serialized to disk in many different ways. Adding a new storage method
1378is as simple as writing an object driver--a non-trivial task, to be sure, but
1379one that will not require touching any other Movable Type code.
1380
1381=head1 SUBCLASSING
1382
1383Creating a subclass of I<MT::Object> is very simple; you simply need to
1384define the properties and metadata about the object you are creating. Start
1385by declaring your class, and inheriting from I<MT::Object>:
1386
1387    package MT::Foo;
1388    use strict;
1389
1390    use base 'MT::Object';
1391
1392=item * __PACKAGE__->install_properties($args)
1393
1394Then call the I<install_properties> method on your class name; an easy way
1395to get your class name is to use the special I<__PACKAGE__> variable:
1396
1397    __PACKAGE__->install_properties({
1398        column_defs => {
1399            'id' => 'integer not null auto_increment',
1400            'foo' => 'string(255)',
1401        },
1402        indexes => {
1403            foo => 1,
1404        },
1405        primary_key => 'id',
1406        datasource => 'foo',
1407    });
1408
1409I<install_properties> performs the necessary magic to install the metadata
1410about your new class in the MT system. The method takes one argument, a hash
1411reference containing the metadata about your class. That hash reference can
1412have the following keys:
1413
1414=over 4
1415
1416=item * column_defs
1417
1418The definition of the columns (fields) in your object. Column names are also
1419used for method names for your object, so your column name should not
1420contain any strange characters. (It could also be used as part of the name of
1421the column in a relational database table, so that is another reason to keep
1422column names somewhat sane.)
1423
1424The value for the I<columns> key should be a reference to an hashref
1425containing the key/value pairs that are names of your columns matched with
1426their schema definition.
1427
1428The type declaration of a column is pseudo-SQL. The data types loosely match
1429SQL types, but are vendor-neutral, and each MT::ObjectDriver will map these
1430to appropriate types for the database it services. The format of a column
1431type is as follows:
1432
1433    'column_name' => 'type(size) options'
1434
1435The 'type' part of the declaration can be any one of:
1436
1437=over 4
1438
1439=item * string
1440
1441For storing string data, typically up to 255 characters, but assigned a length identified by '(size)'.
1442
1443=item * integer
1444
1445For storing integers, maybe limited to 32 bits.
1446
1447=item * boolean
1448
1449For storing boolean values (numeric values of 1 or 0).
1450
1451=item * smallint
1452
1453For storing small integers, typically limited to 16 bits.
1454
1455=item * datetime
1456
1457For storing a full date and time value.
1458
1459=item * timestamp
1460
1461For storing a date and time that automatically updates upon save.
1462
1463=item * blob
1464
1465For storing binary data.
1466
1467=item * text
1468
1469For storing text data.
1470
1471=item * float
1472
1473For storing floating point values.
1474
1475=back
1476
1477Note: The physical data storage capacity of these types will vary depending on
1478the driver's implementation.
1479
1480The '(size)' element of the declaration is only valid for the 'string' type.
1481
1482The 'options' element of the declaration is not required, but is used to
1483specify additional attributes of the column. Such as:
1484
1485=over 4
1486
1487=item * not null
1488
1489Specify 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.
1490
1491=item * auto_increment
1492
1493Specify for integer columns (typically the primary key) to automatically assign a value.
1494
1495=item * primary key
1496
1497Specify for identifying the column as the primary key (only valid for a single column).
1498
1499=item * indexed
1500
1501Identifies that this column should also be individually indexed.
1502
1503=item * meta
1504
1505Declares the column as a meta column, which means it is stored in a separate
1506table that is used for storing metadata. See L<Metadata> for more information.
1507
1508=back
1509
1510=item * indexes
1511
1512Specifies the column indexes on your objects.
1513
1514The value for the I<indexes> key should be a reference to a hash containing
1515column names as keys, and the value C<1> for each key--each key represents
1516a column that should be indexed:
1517
1518    indexes => {
1519        'column_1' => 1,
1520        'column_2' => 1,
1521    },
1522
1523For multi-column indexes, you must declare the individual columns as the
1524value for the index key:
1525
1526    indexes => {
1527        'column_catkey' => {
1528            columns => [ 'column_1', 'column_2' ],
1529        },
1530    },
1531
1532For declaring a unique constraint, add a 'unique' element to this hash:
1533
1534    indexes => {
1535        'column_catkey' => {
1536            columns => [ 'column_1', 'column_2' ],
1537            unique => 1,
1538        },
1539    },
1540
1541=item * audit
1542
1543Automatically adds bookkeeping capabilities to your class--each object will
1544take on four new columns: I<created_on>, I<created_by>, I<modified_on>, and
1545I<modified_by>. The created_on, created_by columns will be populated
1546automatically (if they have not already been assigned at the time of saving
1547the object). Your application is responsible for updating the modified_on,
1548modified_by columns as these may require explicit application-specific
1549assignments (ie, your application may only want them updated during explicit
1550user interaction with the object, as opposed to cases where the object is
1551being changed and saved for mechanical purposes like upgrading a table).
1552
1553=item * datasource
1554
1555The name of the datasource for your class. The datasource is a name uniquely
1556identifying your class--it is used by the object drivers to construct table
1557names, file names, etc. So it should not be specific to any one driver.
1558
1559Please note: the length of the datasource name should be conservative; some
1560drivers place limits on the length of table and column names.
1561
1562=item * meta
1563
1564Specify this property if you wish to support the storage of additional
1565metadata for this class. By doing so, a second table will be declared to
1566MT's registry, one that is designed to hold any metadata associated
1567with your class.
1568
1569=item * class_type
1570
1571If class_type is declared, an additional 'class' column is added to the
1572object properties. This column is then used to differentiate between
1573multiple object types that share the same physical table.
1574
1575Note that if this is used, all searches will be constrained to match
1576the class type of the package.
1577
1578=item * class_column
1579
1580Defines the name of the class column (default is 'class') for storing
1581classed objects (see 'class_type' above).
1582
1583=back
1584
1585=head1 USAGE
1586
1587=head2 System Initialization
1588
1589Before using (loading, saving, removing) an I<MT::Object> class and its
1590objects, you must always initialize the Movable Type system. This is done
1591with the following lines of code:
1592
1593    use MT;
1594    my $mt = MT->new;
1595
1596Constructing a new I<MT> objects loads the system configuration from the
1597F<mt.cfg> configuration file, then initializes the object driver that will
1598be used to manage serialized objects.
1599
1600=head2 Creating a new object
1601
1602To create a new object of an I<MT::Object> class, use the I<new> method:
1603
1604    my $foo = MT::Foo->new;
1605
1606I<new> takes no arguments, and simply initializes a new in-memory object.
1607In fact, you need not ever save this object to disk; it can be used as a
1608purely in-memory object.
1609
1610=head2 Setting and retrieving column values
1611
1612To set the column value of an object, use the name of the column as a method
1613name, and pass in the value for the column:
1614
1615    $foo->foo('bar');
1616
1617The return value of the above call will be C<bar>, the value to which you have
1618set the column.
1619
1620To retrieve the existing value of a column, call the same method, but without
1621an argument:
1622
1623    $foo->foo
1624
1625This returns the value of the I<foo> column from the I<$foo> object.
1626
1627=over 4
1628
1629=item * $obj->init()
1630
1631=back
1632
1633This method is used to initialize the object upon construction.
1634
1635=over 4
1636
1637=item * $obj->set_defaults()
1638
1639=back
1640
1641This method is used by the I<init> method to set the object defaults.
1642
1643=head2 Saving an object
1644
1645To save an object using the object driver, call the I<save> method:
1646
1647=over 4
1648
1649=item * $foo->save();
1650
1651=back
1652
1653On success, I<save> will return some true value; on failure, it will return
1654C<undef>, and you can retrieve the error message by calling the I<errstr>
1655method on the object:
1656
1657    $foo->save
1658        or die "Saving foo failed: ", $foo->errstr;
1659
1660If you are saving objects in a loop, take a look at the
1661L</"Note on object locking">.
1662
1663=head2 Loading an existing object or objects
1664
1665=over 4
1666
1667=item * $obj->load()
1668
1669=item * $obj->load_iter()
1670
1671=back
1672
1673You can load an object from the datastore using the I<load> method. I<load>
1674is by far the most complicated method, because there are many different ways
1675to load an object: by ID, by column value, by using a join with another type
1676of object, etc.
1677
1678In addition, you can load objects either into an array (I<load>), or by using
1679an iterator to step through the objects (I<load_iter>).
1680
1681I<load> has the following general form:
1682
1683    my $object = MT::Foo->load( $id );
1684
1685    my @objects = MT::Foo->load(\%terms, \%arguments);
1686
1687    my @objects = MT::Foo->load(\@terms, \%arguments);
1688
1689I<load_iter> has the following general form:
1690
1691    my $iter = MT::Foo->load_iter(\%terms, \%arguments);
1692
1693    my $iter = MT::Foo->load_iter(\@terms, \%arguments);
1694
1695Both methods share the same parameters; the only difference is the manner in
1696which they return the matching objects.
1697
1698If you call I<load> in scalar context, only the first row of the array
1699I<@objects> will be returned; this works well when you know that your I<load>
1700call can only ever result in one object returned--for example, when you load
1701an object by ID.
1702
1703I<\%terms> should be either:
1704
1705=over 4
1706
1707=item * The numeric ID of an object in the datastore.
1708
1709=item * A reference to a hash.
1710
1711The hash should have keys matching column names and the values are the
1712values for that column.
1713
1714For example, to load an I<MT::Foo> object where the I<foo> column is
1715equal to C<bar>, you could do this:
1716
1717    my @foo = MT::Foo->load({ foo => 'bar' });
1718
1719In addition to a simple scalar, the hash value can be a reference to an array;
1720combined with the I<range> setting in the I<\%arguments> list, you can use
1721this to perform range searches. If the value is a reference, the first element
1722in the array specifies the low end of the range, and the second element the
1723high end.
1724
1725=item * A reference to an array.
1726
1727In this form, the arrayref contains a list of selection terms for more
1728complex selections.
1729
1730    my @foo = MT::Foo->load( [ { foo => 'bar' }
1731        => -or => { foo => 'baz' } ] );
1732
1733The separating operator keywords inbetween terms can be any of C<-or>,
1734C<-and>, C<-or_not>, C<-and_not> (the leading '-' is not required, and the
1735operator itself is case-insensitive).
1736
1737=back
1738
1739Values assigned to terms for selecting data can be either simple or complex
1740in nature. Simple scalar values require an exact match. For instance:
1741
1742    my @foo = MT::Foo->load( { foo => 'bar' });
1743
1744This selects all I<MT::Foo> objects where foo == 'bar'. But you can provide
1745a hashref value to provide more options:
1746
1747    my @foo = MT::Foo->load( { foo => { like => 'bar%' } });
1748
1749This selects all I<MT::Foo> objects where foo starts with 'bar'. Other
1750possibilities include 'not_like', 'not_null', 'not', 'between', '>',
1751'>=', '<', '<=', '!='. Note that 'not' and 'between' both accept an
1752arrayref for their value; 'between' expects a two element array, and
1753'not' will accept an array of 1 or more values which translates to
1754a 'NOT IN (...)' SQL clause.
1755
1756I<\%arguments> should be a reference to a hash containing parameters for the
1757search. The following parameters are allowed:
1758
1759=over 4
1760
1761=item * sort => "column"
1762
1763Sort the resulting objects by the column C<column>; C<column> must be an
1764indexed column (see L</"indexes">, above).
1765
1766Sort may also be specified as an arrayref of multiple columns to sort on.
1767For example:
1768
1769    sort => [
1770        { column => "column_1", desc => "descend" },
1771        { column => "column_2", }   # default direction is 'ascend'
1772    ]
1773
1774=item * direction => "ascend|descend"
1775
1776To be used together with a scalar I<sort> value; specifies the sort
1777order (ascending or descending). The default is C<ascend>.
1778
1779=item * limit => "N"
1780
1781Rather than loading all of the matching objects (the default), load only
1782C<N> objects.
1783
1784=item * offset => "M"
1785
1786To be used together with I<limit>; rather than returning the first C<N>
1787matches (the default), return matches C<M> through C<N + M>.
1788
1789=item * start_val => "value"
1790
1791To be used together with I<limit> and I<sort>; rather than returning the
1792first C<N> matches, return the first C<N> matches where C<column> (the sort
1793column) is greater than C<value>.
1794
1795=item * range
1796
1797To be used together with an array reference as the value for a column in
1798I<\%terms>; specifies that the specific column should be searched for a range
1799of values, rather than one specific value.
1800
1801The value of I<range> should be a hash reference, where the keys are column
1802names, and the values are all C<1>; each key specifies a column that should
1803be interpreted as a range.
1804
1805    MT::Foo->load( { created_on => [ '20011008000000', undef ] },
1806        { range => { created_on => 1 } } );
1807
1808This selects C<MT::Foo> objects whose created_on date is greater than
18092001-10-08 00:00:00.
1810
1811=item * range_incl
1812
1813Like the 'range' attribute, but defines an inclusive range.
1814
1815=item * join
1816
1817Can be used to select a set of objects based on criteria, or sorted by
1818criteria, from another set of objects. An example is selecting the C<N>
1819entries most recently commented-upon; the sorting is based on I<MT::Comment>
1820objects, but the objects returned are actually I<MT::Entry> objects. Using
1821I<join> in this situation is faster than loading the most recent
1822I<MT::Comment> objects, then loading each of the I<MT::Entry> objects
1823individually.
1824
1825Note that I<join> is not a normal SQL join, in that the objects returned are
1826always of only one type--in the above example, the objects returned are only
1827I<MT::Entry> objects, and cannot include columns from I<MT::Comment> objects.
1828
1829I<join> has the following general syntax:
1830
1831    join => MT::Foo->join_on( JOIN_COLUMN, I<\%terms>, I<\%arguments> )
1832
1833Use the actual MT::Object-descended package name and the join_on static method
1834providing these parameters: I<JOIN_COLUMN> is the column joining the two
1835object tables, I<\%terms> and I<\%arguments> have the same meaning as they do
1836in the outer I<load> or I<load_iter> argument lists: they are used to select
1837the objects with which the join is performed.
1838
1839For example, to select the last 10 most recently commmented-upon entries, you
1840could use the following statement:
1841
1842    my @entries = MT::Entry->load(undef, {
1843        'join' => MT::Comment->join_on( 'entry_id',
1844                    { blog_id => $blog_id },
1845                    { 'sort' => 'created_on',
1846                      direction => 'descend',
1847                      unique => 1,
1848                      limit => 10 } )
1849    });
1850
1851In this statement, the I<unique> setting ensures that the I<MT::Entry>
1852objects returned are unique; if this flag were not given, two copies of the
1853same I<MT::Entry> could be returned, if two comments were made on the same
1854entry.
1855
1856=item * unique
1857
1858Ensures that the objects being returned are unique.
1859
1860This is really only useful when used within a I<join>, because when loading
1861data out of a single object datastore, the objects are always going to be
1862unique.
1863
1864=back
1865
1866=head2 Removing an object
1867
1868=over 4
1869
1870=item * $foo->remove()
1871
1872=back
1873
1874To remove an object from the datastore, call the I<remove> method on an
1875object that you have already loaded using I<load>:
1876
1877    $foo->remove();
1878
1879On success, I<remove> will return some true value; on failure, it will return
1880C<undef>, and you can retrieve the error message by calling the I<errstr>
1881method on the object:
1882
1883    $foo->remove
1884        or die "Removing foo failed: ", $foo->errstr;
1885
1886If you are removing objects in a loop, take a look at the
1887L</"Note on object locking">.
1888
1889=head2 Removing select objects of a particular class
1890
1891Combining the syntax of the load and remove methods, you can use the
1892static version of the remove method to remove particular objects:
1893
1894    MT::Foo->remove({ bar => 'baz' });
1895
1896The terms you specify to remove by should be indexed columns. This
1897method will load the object and remove it, firing the callback operations
1898associated with those operations.
1899
1900=head2 Removing all of the objects of a particular class
1901
1902To quickly remove all of the objects of a particular class, call the
1903I<remove_all> method on the class name in question:
1904
1905=over 4
1906
1907=item * MT::Foo->remove_all();
1908
1909=back
1910
1911On success, I<remove_all> will return some true value; on failure, it will
1912return C<undef>, and you can retrieve the error message by calling the
1913I<errstr> method on the class name:
1914
1915    MT::Foo->remove_all
1916        or die "Removing all foo objects failed: ", MT::Foo->errstr;
1917
1918=head2 Removing all the children of an object
1919
1920=over 4
1921
1922=item * $obj->remove_children([ \%param ])
1923
1924=back
1925
1926If your class has registered 'child_classes' as part of it's properties,
1927then this method may be used to remove objects that are associated with
1928the active object.
1929
1930This method is typically used in an overridden 'remove' method.
1931
1932    sub remove {
1933        my $obj = shift;
1934        $obj->remove_children({ key => 'object_id' });
1935        $obj->SUPER::remove(@_);
1936    }
1937
1938The 'key' parameter specified here lets you identify the field name used by
1939the children classes to relate back to the parent class. If unspecified,
1940C<remove_children> will assume the key to be the datasource name of the
1941current class with an '_id' suffix.
1942
1943=head2 Getting the count of a number of objects
1944
1945To determine how many objects meeting a particular set of conditions exist,
1946use the I<count> method:
1947
1948    my $count = MT::Foo->count({ foo => 'bar' });
1949
1950I<count> takes the same arguments as I<load> and I<load_iter>.
1951
1952=head2 Determining if an object exists in the datastore
1953
1954To check an object for existence in the datastore, use the I<exists> method:
1955
1956=over 4
1957
1958=item * $obj->exists()
1959
1960=back
1961
1962    if ($foo->exists) {
1963        print "Foo $foo already exists!";
1964    }
1965
1966To test for the existence of an unloaded object, use the 'exist' method:
1967
1968=over 4
1969
1970=item * Class->exist( \%terms )
1971
1972=back
1973
1974    if (MT::Foo->exist( { foo => 'bar' })) {
1975        print "Already exists!";
1976    }
1977
1978This is typically faster than issuing a L<count> call.
1979
1980=head2 Counting groups of objects
1981
1982=over 4
1983
1984=item * Class->count_group_by()
1985
1986=back
1987
1988The count_group_by method can be used to retrieve a list of all the
1989distinct values that appear in a given column along with a count of
1990how many objects carry that value. The routine can also be used with
1991more than one column, in which case it retrieves the distinct pairs
1992(or n-tuples) of values in those columns, along with the counts.
1993Yet more powerful, any SQL expression can be used in place of
1994the column names to count how many object produce any given result
1995values when run through those expressions.
1996
1997  $iter = MT::Foo->count_group_by($terms, {%args, group => $group_exprs});
1998
1999C<$terms> and C<%args> pick out a subset of the MT::Foo objects in the
2000usual way. C<$group_expressions> is an array reference containing the
2001SQL expressions for the values you want to group by. A single row will
2002be returned for each distinct tuple of values resulting from the
2003$group_expressions. For example, if $group_expressions were just a
2004single column (e.g. group => ['created_on']) then a single row would
2005be returned for each distinct value of the 'created_on' column. If
2006$group_expressions were multiple columns, a row would be returned for
2007each distinct pair (or n-tuple) of values found in those columns.
2008
2009Each application of the iterator C<$iter> returns a list in the form:
2010
2011  ($count, $group_val1, $group_val2, ...)
2012
2013Where C<$count> is the number of MT::Foo objects for which the group
2014expressions are the values ($group_val1, $group_val2, ...). These
2015values are in the same order as the corresponding group expressions in
2016the $group_exprs argument.
2017
2018In this example, we load up groups of MT::Pip objects, grouped by the
2019pair (cat_id, invoice_id), and print how many pips have that pair of
2020values.
2021
2022    $iter = MT::Pip->count_group_by(undef,
2023                                    {group => ['cat_id',
2024                                               'invoice_id']});
2025    while (($count, $cat, $inv) = $iter->()) {
2026        print "There are $count Pips with " .
2027            "category $cat and invoice $inv\n";
2028    }
2029
2030=head2 Averaging by Group
2031
2032=over 4
2033
2034=item * Class->avg_group_by()
2035
2036=back
2037
2038Like the count_group_by method, you can select groups of averages from
2039a MT::Object store.
2040
2041    my $iter = MT::Foo->avg_group_by($terms, {%args, group => $group_exprs,
2042        avg => 'property_to_average' })
2043
2044=head2 Max by Group
2045
2046=over 4
2047
2048=item * Class->max_group_by()
2049
2050=back
2051
2052Like the count_group_by method, you can select objects from a MT::Object
2053store using a SQL 'MAX' operator.
2054
2055    my $iter = MT::Foo->max_group_by($terms, {%args, group => $group_exprs,
2056        max => 'column_name' })
2057
2058=head2 Sum by Group
2059
2060=over 4
2061
2062=item * Class->sum_group_by()
2063
2064=back
2065
2066Like the count_group_by method, you can select groups of sums from
2067a MT::Object store.
2068
2069    my $iter = MT::Foo->sum_group_by($terms, {%args, group => $group_exprs,
2070        avg => 'property_to_sum' })
2071
2072=head2 Inspecting and Manipulating Object State
2073
2074=over 4
2075
2076=item * $obj->column_values()
2077
2078=back
2079
2080Use C<column_values> and C<set_values> to get and set the fields of an
2081object I<en masse>. The former returns a hash reference mapping column
2082names to their values in this object. For example:
2083
2084    $values = $obj->column_values()
2085
2086=over 4
2087
2088=item * $obj->set_values()
2089
2090=back
2091
2092C<set_values> accepts a similar hash ref, which need not give a value
2093for every field. For example:
2094
2095    $obj->set_values({col1 => $val1, col2 => $val2});
2096
2097is equivalent to
2098
2099    $obj->col1($val1);
2100    $obj->col2($val2);
2101
2102=head2 Other Methods
2103
2104=over 4
2105
2106=item * $obj->clone([\%param])
2107
2108Returns a clone of C<$obj>. That is, a distinct object which has all
2109the same data stored within it. Changing values within one object does
2110not modify the other.
2111
2112An optional C<except> parameter may be provided to exclude particular
2113columns from the cloning operation. For example, the following would
2114clone the elements of the blog except the name attribute.
2115
2116   $blog->clone({ except => { name => 1 } });
2117
2118=item * $obj->clone_all()
2119
2120Similar to the C<clone> method, but also makes a clones the metadata
2121information.
2122
2123=item * $obj->column_names()
2124
2125Returns a list of the names of columns in C<$obj>; includes all those
2126specified to the install_properties method as well as the audit
2127properties (C<created_on>, C<modified_on>, C<created_by>,
2128C<modified_by>), if those were enabled in install_properties.
2129
2130=item * MT::Foo->driver()
2131
2132=item * $obj->driver()
2133
2134Returns the ObjectDriver object that links this object with a database.
2135This is a subclass of L<Data::ObjectDriver>.
2136
2137=item * $obj->dbi_driver()
2138
2139This method is similar to the 'driver' method, but will always return
2140a DBI driver (a subclass of the L<Data::ObjectDriver::Driver::DBI>
2141class) and not a caching driver.
2142
2143=item * $obj->created_on_obj()
2144
2145Returns a MT::DateTime object representing the moment when the
2146object was first saved to the database.
2147
2148=item * $obj->column_as_datetime( $column )
2149
2150Returns a MT::DateTime object for the specified datetime/timestamp
2151column specified.
2152
2153=item * MT::Foo->set_by_key($key_terms, $value_terms)
2154
2155A convenience method that loads whatever object matches the C<$key_terms>
2156argument and sets some or all of its fields according to the
2157C<$value_terms>. For example:
2158
2159   MT::Foo->set_by_key({name => 'Thor'},
2160                       {region => 'Norway', gender => 'Male'});
2161
2162This loads the C<MT::Foo> object having 'name' field equal to 'Thor'
2163and sets the 'region' and 'gender' fields appropriately.
2164
2165More than one term is acceptable in the C<$key_terms> argument. The
2166matching object is the one that matches all of the C<$key_terms>.
2167
2168This method only useful if you know that there is a unique object
2169matching the given key. There need not be a unique constraint on the
2170columns named in the C<$key_hash>; but if not, you should be confident
2171that only one object will match the key.
2172
2173=item * MT::Foo->get_by_key($key_terms)
2174
2175A convenience method that loads whatever object matches the C<$key_terms>
2176argument. If no matching object is found, a new object will be constructed
2177and the C<$key_terms> provided will be assigned to it. So regardless of
2178whether the key exists already, this method will return an object with the
2179key requested. Note, however: if a new object is instantiated it is
2180not automatically saved.
2181
2182    my $thor = MT::Foo->get_by_key({name => 'Thor'});
2183    $thor->region('Norway');
2184    $thor->gender('Male');
2185    $thor->save;
2186
2187The fact that it returns a new object if one isn't found is to help
2188optimize this pattern:
2189
2190    my $obj = MT::Foo->load({key => $value});
2191    if (!$obj) {
2192        $obj = new MT::Foo;
2193        $obj->key($value);
2194    }
2195
2196This is equivalent to:
2197
2198    my $obj = MT::Foo->get_by_key({key => $value});
2199
2200If you don't appreciate the autoinstantiation behavior of this method,
2201just use the C<load> method instead.
2202
2203More than one term is acceptable in the C<$key_terms> argument. The
2204matching object is the one that matches all of the C<$key_terms>.
2205
2206This method only useful if you know that there is a unique object
2207matching the given key. There need not be a unique constraint on the
2208columns named in the C<$key_hash>; but if not, you should be confident
2209that only one object will match the key.
2210
2211=item * $obj->cache_property($key, $code)
2212
2213Caches the provided key (e.g. entry, trackback) with the return value
2214of the given code reference (which is often an object load call) so
2215that the value does not have to be recomputed each time.
2216
2217=item * $obj->clear_cache()
2218
2219Clears any object-level cache data (from the C<cache_property> method)
2220that may existing.
2221
2222=item * $obj->column_def($name)
2223
2224This method returns the value of the given I<$name> C<column_defs>
2225propery.
2226
2227=item * $obj->column_defs()
2228
2229This method returns all the C<column_defs> of the property of the
2230object.
2231
2232=item Class->index_defs()
2233
2234This method returns all the index definitions assigned to this class.
2235This is the 'indexes' member of the properties installed for the class.
2236
2237=item * $obj->to_hash()
2238
2239Returns a hashref containing column and metadata key/value pairs for
2240the object. If the object has a blog relationship, it also populates
2241data from that blog. For example:
2242
2243    my $entry_hash = $entry->to_hash();
2244    # returns: { entry.title => "Title", entry.blog.name => "Foo", ... }
2245
2246=item * Class->join_on( $join_column, \%join_terms, \%join_args )
2247
2248A simple helper method that returns an arrayref of join terms suitable
2249for the C<load> and C<load_iter> methods.
2250
2251=item * $obj->properties()
2252
2253Returns a hashref of the object properties that were declared with the
2254I<install_properties> method.
2255
2256=item * $obj->to_xml()
2257
2258Returns an XML representation of the object.
2259This method is defined in MT/BackupRestore.pm - you must first
2260use MT::BackupRestore to use this method.
2261
2262=item * $obj->restore_parent_ids()
2263
2264TODO - Backup file contains parent objects' ids (foreign keys).  However,
2265when parent objcects are restored, their ids will be changed.  This method
2266is to match the old and new ids of parent objects for children objects to be
2267correctly associated.
2268This method is defined in MT/BackupRestore.pm - you must first
2269use MT::BackupRestore to use this method.
2270
2271=item * $obj->parent_names()
2272
2273TODO - Should be overridden by subclasses to return correct hash
2274whose keys are xml element names of the object's parent objects
2275and values are class names of them.
2276This method is defined in MT/BackupRestore.pm - you must first
2277use MT::BackupRestore to use this method.
2278
2279=item * Class->class_handler($type)
2280
2281Returns the appropriate Perl package name for the given type identifier.
2282For example,
2283
2284    # Yields MT::Asset::Image
2285    MT::Asset->class_handler('asset.image');
2286
2287=item * Class->class_label
2288
2289Provides a descriptive name for the requested class package.
2290This is a localized name, using the currently assigned language.
2291
2292=item * Class->class_label_plural
2293
2294Returns a descriptive pluralized name for the requested class package.
2295This is a localized name, using the currently assigned language.
2296
2297=item * Class->class_labels
2298
2299Returns a hashref of type identifiers to class labels for all subclasses
2300associated with a multiclassed object type. For instance:
2301
2302    # returns { 'asset' => 'Asset', 'asset.video' => 'Video', ... }
2303    my $labels = MT::Asset->class_labels;
2304
2305=item * Class->columns_of_type(@types)
2306
2307Returns an arrayref of column names that are of the requested type.
2308
2309    my @dates = MT::Foo->columns_of_type('datetime', 'timestamp')
2310
2311=item * Class->has_column( $name )
2312
2313Returns a boolean as to whether the column C<$name> is defined for
2314this class.
2315
2316=item * Class->table_name()
2317
2318Returns the database table name (including any prefix) for the class.
2319
2320=item * $obj->column_func( $column )
2321
2322Creates an accessor/mutator method for column C<$column>, returning it as a
2323coderef. This method overrides the one in L<Data::ObjectDriver::BaseObject>,
2324by supporting metadata column as well.
2325
2326=item * $obj->call_trigger( 'trigger_name', @params )
2327
2328Issues a call to any Class::Trigger triggers installed for the given object.
2329Also invokes any MT callbacks that are registered using MT's callback
2330system. "pre" callbacks are invoked prior to triggers; "post" callbacks
2331are invoked after triggers are called.
2332
2333=item * $obj->deflate
2334
2335Returns a minimal representation of the object, including any metadata.
2336See also L<Data::ObjectDriver::BaseObject>.
2337
2338=item * Class->inflate( $deflated )
2339
2340Inflates the deflated representation of the object I<$deflated> into a proper
2341object in the class I<Class>. That is, undoes the operation C<$deflated =
2342$obj-E<gt>deflate()> by returning a new object equivalent to C<$obj>.
2343
2344=item * Class->install_pre_init_properties
2345
2346This static method is used to install any class properties that were
2347registered prior to the bootstrapping of MT plugins.
2348
2349=item * $obj->modified_by
2350
2351A modified getter/setter accessor method for audited classes with a
2352'modified_by', 'modified_on' columns. In the event this method is called
2353to assign a 'modified_by' value, it automatically updates the 'modified_on'
2354column as well.
2355
2356=item * $obj->nextprev( %params )
2357
2358Method to determine adjancent objects, based on a date column and/or id.
2359The C<%params> hash provides the following elements:
2360
2361=over 4
2362
2363=item * direction
2364
2365Either "next" or "previous".
2366
2367=item * terms
2368
2369Any additional terms to supply to the C<load> method.
2370
2371=item * args
2372
2373Any additional arguments to supply to the C<load> method (such as a join).
2374
2375=item * by
2376
2377The column to use to determine the next/previous object. By default for
2378audited classes, this is 'created_on'.
2379
2380=back
2381
2382=back
2383
2384=head1 NOTES
2385
2386=head2 Note on object locking
2387
2388When you read objects from the datastore, the object table is locked with a
2389shared lock; when you write to the datastore, the table is locked with an
2390exclusive lock.
2391
2392Thus, note that saving or removing objects in the same loop where you are
2393loading them from an iterator will not work--the reason is that the datastore
2394maintains a shared lock on the object table while objects are being loaded
2395from the iterator, and thus the attempt to gain an exclusive lock when saving
2396or removing an object will cause deadlock.
2397
2398For example, you cannot do the following:
2399
2400    my $iter = MT::Foo->load_iter({ foo => 'bar' });
2401    while (my $foo = $iter->()) {
2402        $foo->remove;
2403    }
2404
2405Instead you should do either this:
2406
2407    my @foo = MT::Foo->load({ foo => 'bar' });
2408    for my $foo (@foo) {
2409        $foo->remove;
2410    }
2411
2412or this:
2413
2414    my $iter = MT::Foo->load_iter({ foo => 'bar' });
2415    my @to_remove;
2416    while (my $foo = $iter->()) {
2417        push @to_remove, $foo
2418            if SOME CONDITION;
2419    }
2420    for my $foo (@to_remove) {
2421        $foo->remove;
2422    }
2423
2424This last example is useful if you will not be removing every I<MT::Foo>
2425object where I<foo> equals C<bar>, because it saves memory--only the
2426I<MT::Foo> objects that you will be deleting are kept in memory at the same
2427time.
2428
2429=head1 SUBCLASSING
2430
2431It is possible to declare a subclass of an existing MT::Object class,
2432one that shares the same table storage as the parent class. Examples of
2433this include L<MT::Log>, L<MT::Entry>, L<MT::Category>. In these cases,
2434the subclass identifies a 'class_type' property. The parent class must also
2435have a column where this identifier is stored. Upon loading records from the
2436table, the object is reblessed into the appropriate package.
2437
2438=over 4
2439
2440=item Class->add_class( $type_id, $class )
2441
2442This method can be called directly to register a new subclass type
2443and package for the base class.
2444
2445    MT::Foo->add_class( 'foochild' => 'MT::Foo::Subclass' );
2446
2447=back
2448
2449=head1 METADATA
2450
2451The following methods facilitate the storage and management of metadata;
2452available when the 'meta' key is included in the installed properties for
2453the class.
2454
2455=over 4
2456
2457=item * $obj->init_meta()
2458
2459For object classes that have metadata storage, this method will initialize
2460the metadata member.
2461
2462=item * Class->install_meta( \%meta_properties )
2463
2464Called to register metadata properties on a particular class. The
2465C<%meta_properties> may contain an arrayref of 'columns', or a hashref
2466of 'column_defs' (similar to the C<install_properties> method):
2467
2468    MT::Foo->install_meta( { column_defs => {
2469        'metadata1' => 'integer indexed',
2470        'metadata2' => 'string indexed',
2471    } });
2472
2473In this form, the storage type is explicitly declared, so the metadata
2474is stored into the appropriate column (vinteger_idx and vchar_idx
2475respectively).
2476
2477    MT::Foo->install_meta( { columns => [ 'metadata1', 'metadata2' ] } )
2478
2479In this form, the metadata properties store their data into a 'blob'
2480column in the meta table. This type of metadata cannot be used to sort
2481or filter on. This form is supported for backward compatibility and is
2482considered deprecated.
2483
2484=item * $obj->remove_meta()
2485
2486Deletes all related metadata for the given object.
2487
2488=item * Class->search_by_meta( $key, $value, [ \%terms [, \%args ] ] )
2489
2490Returns objects that have a C<$key> metadata value of C<$value>. Further
2491restrictions on the class may be applied through the optional C<%terms>
2492and C<%args> parameters.
2493
2494=item * $obj->meta_obj()
2495
2496Returns the L<MT::Object> class
2497
2498=item * Class->meta_pkg()
2499
2500Returns the Perl package name for storing it's metadata objects.
2501
2502=item * Class->meta_args
2503
2504Returns the source of a Perl package declaration that is loaded to
2505declare and process metadata objects for the C<Class>.
2506
2507=item * Class->has_meta( [ $name ] )
2508
2509Returns a boolean as to whether the class has metadata when called
2510without a parameter, or whether there exists a metadata column
2511of the given C<$name>.
2512
2513=item * Class->is_meta_column( $name )
2514
2515Returns a boolean as to whether the class has a meta column named
2516C<$name>.
2517
2518=back
2519
2520=head1 CALLBACKS
2521
2522=over 4
2523
2524=item * $obj->add_callback()
2525
2526=back
2527
2528Most MT::Object operations can trigger callbacks to plugin code. Some
2529notable uses of this feature are: to be notified when a database record is
2530modified, or to pre- or post-process the data being flowing to the
2531database.
2532
2533To add a callback, invoke the C<add_callback> method of the I<MT::Object>
2534subclass, as follows:
2535
2536   MT::Foo->add_callback( "pre_save", <priority>,
2537                          <plugin object>, \&callback_function);
2538
2539The first argument is the name of the hook point. Any I<MT::Object>
2540subclass has a pre_ and a post_ hook point for each of the following
2541operations:
2542
2543    load
2544    save
2545    update (issued for save on existing objects)
2546    insert (issued for save on new objects)
2547    remove
2548    remove_all
2549    (load_iter operations will call the load callbacks)
2550
2551The second argument, E<lt>priorityE<gt>, is the relative order in
2552which the callback should be called. The value should be between 1 and
255310, inclusive. Callbacks with priority 1 will be called before those
2554with 2, 2 before 3, and so on.
2555
2556Plugins which know they need to run first or last can use the priority
2557values 0 and 11. A callback with priority 0 will run before all
2558others, and if two callbacks try to use that value, an error will
2559result. Likewise priority 11 is exclusive, and runs last.
2560
2561How to remember which callback priorities are special? As you know,
2562most guitar amps have a volume knob that goes from 1 to 10. But, like
2563that of certain rock stars, our amp goes up to 11. A callback with
2564priority 11 is the "loudest" or most powerful callback, as it will be
2565called just before the object is saved to the database (in the case of
2566a 'pre' callback), or just before the object is returned (in the case
2567of a 'post' callback). A callback with priority 0 is the "quietest"
2568callback, as following callbacks can completely overwhelm it. This may
2569be a good choice for your plugin, as you may want your plugin to work
2570well with other plugins. Determining the correct priority is a matter
2571of thinking about your plugin in relation to others, and adjusting the
2572priority based on experience so that users get the best use out of the
2573plugin.
2574
2575The E<lt>plugin objectE<gt> is an object of type MT::Plugin which
2576gives some information about the plugin. This is used to include
2577the plugin's name in any error messages.
2578
2579E<lt>callback functionE<gt> is a code referense for a subroutine that
2580will be called. The arguments to this
2581function vary by operation (see I<MT::Callback> for details),
2582but in each case the first parameter is the I<MT::Callback> object
2583itself:
2584
2585  sub my_callback {
2586      my ($cb, ...) = @_;
2587
2588      if ( <error condition> ) {
2589          return $cb->error("Error message");
2590      }
2591  }
2592
2593Strictly speaking, the return value of a callback is ignored. Calling
2594the error() method of the MT::Callback object (C<$cb> in this case)
2595propagates the error message up to the MT activity log.
2596
2597Another way to handle errors is to call C<die>. If a callback dies,
2598I<MT> will warn the error to the activity log, but will continue
2599processing the MT::Object operation: so other callbacks will still
2600run, and the database operation should still occur.
2601
2602=head2 Any-class Object Callbacks
2603
2604If you add a callback to the MT class with a hook point that begins
2605with C<*::>, such as:
2606
2607    MT->add_callback('*::post_save', 7, $my_plugin, \&code_ref);
2608
2609then it will be called whenever post_save callbacks are called.
2610"Any-class" callbacks are called I<after> all class-specific
2611callbacks. Note that C<add_callback> must be called on the C<MT> class,
2612not on a subclass of C<MT::Object>.
2613
2614=head2 Caveat
2615
2616Be careful how you handle errors. If you transform data as it goes
2617into and out of the database, and it is possible for one of your
2618callbacks to fail, the data may get saved in an undefined state. It
2619may then be difficult or impossible for the user to recover that data.
2620
2621=head1 AUTHOR & COPYRIGHTS
2622
2623Please see the I<MT> manpage for author, copyright, and license information.
2624
2625=cut
Note: See TracBrowser for help on using the browser.