root/branches/release-40/lib/MT/Object.pm @ 2608

Revision 2608, 77.5 kB (checked in by mpaschal, 17 months ago)

Correct these indexes for efficient search_by_meta() use
BugzID: 80212

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