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

Revision 2548, 77.6 kB (checked in by bchoate, 18 months ago)

Updates to iterator handling and use of 'window_size' argument for load_iter method of MT::Object. BugId:79247

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