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

Revision 2421, 77.2 kB (checked in by fumiakiy, 19 months ago)

Translate datetime for meta fields. BugId:79845

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