root/branches/release-38/lib/MT/Object.pm @ 2389

Revision 2389, 75.6 kB (checked in by bchoate, 19 months ago)

Support for removing related meta records with static 'remove' call. BugId:79694

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