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

Revision 2390, 76.6 kB (checked in by bchoate, 19 months ago)

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