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

Revision 2530, 78.0 kB (checked in by auno, 18 months ago)

Fixed to work test properly. BugzID:79876
* fixed some expected data
* clear cache data and not to set cache data
* fixed mtcomments lastn order

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