root/branches/feature-narrow-tables/lib/MT/Object.pm @ 1844

Revision 1844, 63.4 kB (checked in by bchoate, 20 months ago)

Added 'clob' storage type.

  • 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__ . "\nrequire $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    # inherit parent's metadata setup
228    if ($props->{meta}) { # if ($super_props && $super_props->{meta_installed}) {
229        $class->install_meta({ ( %meta ? ( column_defs => \%meta ) : ( columns => [] ) ) });
230        $class->add_trigger( post_remove => \&remove_meta );
231    }
232
233    return $props;
234}
235
236# A post-load trigger for classed objects
237sub post_load_rebless_object {
238    my $obj = shift;
239    my $props = $obj->properties;
240    if (my $col = $props->{class_column}) {
241        my $type = $obj->column($col);
242        my $pkg = ref($obj);
243        if ($pkg->class_type ne $type) {
244            if (my $class = $props->{__type_to_class}{$type}) {
245                bless $obj, $class;
246            } else {
247                my %models = map { $_ => 1 } MT->models($props->{datasource});
248                if (exists $models{ $props->{datasource} . '.' . $type}) {
249                    $class = MT->model($props->{datasource} . '.' . $type);
250                } elsif (exists $models{$type}) {
251                    $class = MT->model($type);
252                }
253                bless $obj, $class if $class;
254            }
255        }
256    }
257}
258
259# A pre-search trigger for classed objects
260sub pre_search_scope_terms_to_class {
261    my ($class, $terms, $args) = @_;
262    # scope search terms to class
263
264    $terms ||= {};
265    return if (ref $terms eq 'HASH') && exists($terms->{id});
266
267    my $props = $class->properties;
268    my $col = $props->{class_column}
269        or return;
270    if (ref $terms eq 'HASH') {
271        if (exists $terms->{$col}) {
272            if ($terms->{$col} eq '*') {
273                # class term is '*', which signifies filtering for all classes.
274                # simply delete the term in this case.
275                delete $terms->{$col} ;
276            } elsif ($terms->{$col} =~ m/^(\w+:)\*$/) {
277                # class term is in form "foo:*"; translate to a sql-compatible
278                # syntax of "like 'foo:%'"
279                $terms->{$col} = \"like '$1%'";
280            }
281            # term has been explicitly given or explictly removed. make
282            # no further changes.
283            return;
284        }
285        $terms->{$col} = $props->{class_type};
286    }
287    elsif (ref $terms eq 'ARRAY') {
288        @$terms = ( { $col => $props->{class_type} } => 'AND' => [ @$terms ] );
289
290    }
291}
292
293sub class_label {
294    my $pkg = shift;
295    return MT->translate($pkg->datasource);
296}
297
298sub class_label_plural {
299    my $pkg = shift;
300    my $label = $pkg->datasource;
301    $label =~ s/y$/ie/;
302    $label .= 's';
303    return MT->translate($label);
304}
305
306sub class_labels {
307    my $pkg = shift;
308    my @all_types = MT->models($pkg->properties->{datasource});
309    my %names;
310    foreach my $type (@all_types) {
311        my $class = $pkg->class_handler($type);
312        $names{$type} = $class->class_label;
313    }
314    return \%names;
315}
316
317# Returns a hashref of asset identifiers mapped to the localized string
318# used to name them. (Ie, image => 'Image').
319sub class_type {
320    my $pkg = shift;
321    if (ref $pkg) {
322        return $pkg->column($pkg->properties->{class_column});
323    } else {
324        return $pkg->properties->{class_type};
325    }
326}
327
328sub class_handler {
329    my $pkg = shift;
330    my $props = $pkg->properties;
331    my ($type) = @_;
332    my $package = $props->{__type_to_class}{$type};
333    unless ($package) {
334        my $ds = $props->{datasource};
335        if (($type eq $ds) || ($type =~ m/\./)) {
336            $package = MT->model($type);
337        } else {
338            $package = MT->model($ds . '.' . $type);
339        }
340    }
341    if ($package) {
342        if (defined *{$package.'::new'}) {
343            return $package;
344        } else {
345            eval "# line " . __LINE__ . " " . __FILE__ . "\nuse $package;";
346            return $package unless $@;
347            eval "# line " . __LINE__ . " " . __FILE__ . "\nuse $pkg; $package->new;";
348            return $package unless $@;
349        }
350    }
351    return $pkg;
352}
353
354sub add_class {
355    my $pkg = shift;
356    my ($type, $class) = @_;
357    my $props = $pkg->properties;
358    if ($type =~ m/::/) {
359        ($type, $class) = ($class, $type);
360    }
361
362    if (my $old_class = $props->{__type_to_class}{$type}) {
363        delete $props->{__class_to_type}{$old_class};
364    }
365    $props->{__type_to_class}{$type} = $class;
366    $props->{__class_to_type}{$class} = $type;
367}
368
369# 'meta' metadata column support
370
371sub new {
372    my $class = shift;
373    my $obj = $class->SUPER::new(@_);
374    if ($obj->properties->{meta_installed}) {
375        $obj->init_meta();
376    }
377    return $obj;
378}
379
380sub init_meta {
381    my $obj = shift;
382    require MT::Meta::Proxy;
383    $obj->{__meta} = MT::Meta::Proxy->new($obj);
384}
385
386sub install_meta {
387    my $class = shift;
388    my ($params) = @_;
389    if ( ( $class ne 'MT::Config' ) && (!$MT::plugins_installed) ) {
390        push @PRE_INIT_META, [$class, $params];
391        return;
392    }
393
394    require MT::Meta;
395    my $pkg = ref $class || $class;
396    if (!$pkg->SUPER::properties->{meta_installed}) {
397        $pkg->add_trigger( post_save => \&post_save_save_metadata );
398        $pkg->add_trigger( post_load => \&post_load_initialize_metadata );
399    }
400
401    my $props = $class->properties;
402
403    if (!$params->{columns} && !$params->{fields} && !$params->{column_defs}) {
404        return $class->error('No meta fields specified to install_meta');
405    }
406
407    $params->{fields} ||= [];
408    if (my $cols = delete $params->{columns}) {
409        foreach my $col (@$cols) {
410            push @{ $params->{fields} }, {
411                name => $col,
412                type => 'vblob',
413            };
414            # $props->{fields}{$col} = 'vblob';
415        }
416    }
417    if (my $cols = delete $params->{column_defs}) {
418        foreach my $col ( keys %$cols ) {
419            my $type = $cols->{$col};
420            $type =~ s/\s.*//; # take first keyword, ignoring anything after
421            $type .= '_indexed'
422                if $cols->{$col} =~ m/\bindexed\b/;
423            $type = MT::Meta->normalize_type($type);
424
425            push @{ $params->{fields} }, {
426                name => $col,
427                type => $type,
428            };
429            # $props->{fields}{$col} = $type;
430        }
431    }
432
433    $params->{datasource} ||= $class->datasource . '_meta';
434
435    if ($props->{meta_installed} && !@{ $params->{fields} }) {
436        return 1;
437    }
438
439    if (my $fields = MT::Meta->install($pkg, $params)) {
440        # we may have inherited meta fields so lets update with
441        # the fields returned by MT::Meta
442        $props->{fields} = $fields;
443    }
444
445    return $props->{meta_installed} = 1;
446}
447
448sub meta_args {
449    my $class = shift;
450    my $id_field = $class->datasource . '_id';
451    return {
452        key         => $class->datasource,
453        column_defs => {
454            $id_field         => 'integer not null',
455            type              => 'string(255) not null',
456            vchar             => 'string(255)',
457            vchar_indexed     => 'string(255)',
458            vdatetime         => 'datetime',
459            vdatetime_indexed => 'datetime',
460            vinteger          => 'integer',
461            vinteger_indexed  => 'integer',
462            vfloat            => 'float',
463            vfloat_indexed    => 'float',
464            vblob             => 'blob',
465            vclob             => 'text',
466        },
467        columns => [ $id_field, qw(
468            type
469            vchar
470            vchar_indexed
471            vdatetime
472            vdatetime_indexed
473            vinteger
474            vinteger_indexed
475            vfloat
476            vfloat_indexed
477            vblob
478            vclob
479        ) ],
480        indexes => {
481            $id_field => 1,
482            id_type   => { columns => [ $id_field, 'type' ] },
483            id_type_vchar => { columns => [ $id_field, 'type', 'vchar_indexed' ] },
484            id_type_vdatetime => { columns => [ $id_field, 'type',
485                'vdatetime_indexed' ] },
486            id_type_vinteger => { columns => [ $id_field, 'type',
487                'vinteger_indexed' ] },
488            id_type_vfloat => { columns => [ $id_field, 'type',
489                'vfloat_indexed' ] },
490        },
491        primary_key => [ $class->datasource . '_id', 'type' ],
492    };
493}
494
495sub has_meta {
496    my $obj = shift;
497    return $obj->is_meta_column(@_) if @_;
498    return $obj->properties->{meta_installed} ? 1 : 0;
499}
500
501sub post_load_initialize_metadata {
502    my $obj = shift;
503    if (defined $obj && $obj->properties->{meta_installed}) {
504        $obj->init_meta();
505        $obj->{__meta}->set_primary_keys($obj);
506    }
507}
508
509sub is_meta_column {
510    my $obj = shift;
511    my ($field) = @_;
512
513    my $props = $obj->properties;
514    return unless $props->{meta_installed};
515
516    my $meta = $obj->meta_pkg;
517    return 1 if $props->{fields}{$field};
518
519    return;
520}
521
522sub meta_pkg {
523    my $class = shift;
524    my $props = $class->properties;
525    return unless $props->{meta}; # this only works for meta-enabled classes
526
527    return $props->{meta_pkg} if $props->{meta_pkg};
528
529    my $meta = ref $class || $class;
530    $meta .= '::Meta';
531    return $props->{meta_pkg} = $meta;
532}
533
534sub has_column {
535    my $obj = shift;
536    return 1 if $obj->SUPER::has_column(@_);
537    return 1 if $obj->is_meta_column(@_);
538    return;
539}
540
541sub post_save_save_metadata {
542    my $obj = shift;
543    if (defined $obj && exists $obj->{__meta}) {
544        $obj->{__meta}->set_primary_keys($obj);
545        $obj->{__meta}->save;
546    }
547}
548
549sub meta {
550    my $obj = shift;
551    my ($name, $value) = @_;
552
553    return !$obj->{__meta} ? undef
554         : 2 == scalar @_  ? $obj->{__meta}->set($name, $value)
555         : 1 == scalar @_  ? $obj->{__meta}->get($name)
556         :                   $obj->{__meta}->get_hash
557         ;
558}
559
560sub meta_obj {
561    my $obj = shift;
562    return $obj->{__meta};
563}
564
565sub column_func {
566    my $obj = shift;
567    my ($col) = @_;
568    return if !$col;
569
570    return $obj->SUPER::column_func(@_)
571        if !$obj->is_meta_column($col);
572
573    return sub {
574        my $obj = shift;
575        if (@_) {
576            $obj->{__meta}->set($col, @_);
577        }
578        else {
579            $obj->{__meta}->get($col);
580        }
581    };
582}
583
584sub ts2db { 
585    return unless $_[0]; 
586    if($_[0] =~ m{ \A \d{4} - }xms) { 
587        return $_[0]; 
588    } 
589    my $ret = sprintf '%04d-%02d-%02d %02d:%02d:%02d', unpack 'A4A2A2A2A2A2', $_[0]; 
590    return $ret; 
591}
592 
593sub db2ts { 
594    my $ts = $_[0]; 
595    $ts =~ s/(?:\+|-)\d{2}$//; 
596    $ts =~ tr/\- ://d; 
597    return $ts; 
598} 
599
600sub get_date_translator {
601    my $translator = shift;
602    my $change = shift;
603    return sub {
604        my $obj = shift;
605        my $dbd = $obj->driver->dbd;
606        FIELD: for my $field (@{$obj->columns_of_type('datetime', 'timestamp')}) {
607            my $value = $obj->column($field);
608            next FIELD if !defined $value;
609            my $new_val = $translator->($value); 
610            if((defined $new_val) && ($new_val ne $value)) {
611                $obj->column($field, $new_val, { no_changed_flag => !$change });
612            }
613        }
614    };
615}
616
617sub translate_audited_fields {
618    my ($obj, $orig_obj) = @_;
619    my $dbd = $obj->driver->dbd;
620    FIELD: for my $field (qw( created_on modified_on )) {
621        my $value = $orig_obj->column($field);
622        next FIELD if !defined $value;
623        my $new_val = db2ts($value); 
624        if((defined $new_val) && ($new_val ne $value)) {
625            $orig_obj->column($field, $new_val);
626        }
627    }
628    return;
629}
630
631sub nextprev {
632    my $obj = shift;
633    my $class = ref($obj);
634    my %param = @_;
635    my ($direction, $terms, $args, $by_field)
636        = @param{qw( direction terms args by )};
637    return undef unless ($direction eq 'next' || $direction eq 'previous');
638    my $next = $direction eq 'next';
639
640    if (!$by_field) {
641        return if !$class->properties->{audit};
642        $by_field = 'created_on';
643    }
644
645    # Selecting the adjacent object can be tricky since timestamps
646    # are not necessarily unique for entries. If we find that the
647    # next/previous object has a matching timestamp, keep selecting entries
648    # to select all entries with the same timestamp, then compare them using
649    # id as a secondary sort column.
650
651    my ($id, $ts) = ($obj->id, $obj->$by_field());
652    local @$args{qw( sort range_incl )}
653        = ( [ { column => $by_field, desc => $next ? 'ASC' : 'DESC' },
654            { column => 'id', desc => $next ? 'ASC' : 'DESC' } ],
655            { $by_field => 1 });
656
657    my $sibling = $class->load({
658        $by_field => ($next ? [ $ts, undef ] : [ undef, $ts ]),
659        'id' => $id,
660        %{$terms}
661    }, { not => { 'id' => 1 }, limit => 1, %$args });
662
663    return $sibling;
664}
665
666## Drivers.
667
668# Note: Removed methods: set_driver
669
670sub count          { shift->_proxy('count',          @_) }
671sub count_group_by { shift->_proxy('count_group_by', @_) }
672sub sum_group_by   { shift->_proxy('sum_group_by',   @_) }
673sub avg_group_by   { shift->_proxy('avg_group_by',   @_) }
674sub remove_all     { shift->_proxy('remove_all',     @_) }
675
676sub remove {
677    my $obj = shift;
678    my(@args) = @_;
679    if (!ref $obj) {
680        return $obj->driver->direct_remove($obj, @args);
681    } else {
682        return $obj->driver->remove($obj, @args);
683    }
684}
685
686sub load {
687    my $self = shift;
688    if (defined $_[0] && (!ref $_[0] || (ref $_[0] ne 'HASH' && ref $_[0] ne 'ARRAY'))) {
689        return $self->lookup($_[0]);
690    } else {
691        if (wantarray) {
692            ## MT::Object::load returns a list in list context, just like
693            ## a D::OD search.
694            return $self->search(@_);
695        } else {
696            ## MT::Object::load returns the first result in scalar context.
697            my $iter = $self->search(@_);
698            return if !defined $iter;
699            return $iter->();
700        }
701    }
702}
703
704# More or less replacing Data::ObjectDriver::Driver::DBI::search here
705# to provide an 'early-finish' iterator as MT::ObjectDriver had provided.
706
707sub load_iter   {
708    my $class = shift;
709    my($terms, $args) = @_;
710
711    my $driver = $class->driver;
712    my $dbi_driver = $driver;
713
714    while ( $dbi_driver->isa('Data::ObjectDriver::Driver::BaseCache') ) {
715        $dbi_driver = $dbi_driver->fallback;
716    }
717
718    if ($dbi_driver->dbd eq 'MT::ObjectDriver::Driver::SQLite') {
719        # for SQLite, use search method, since this technique
720        # will cause it to lock the table
721        return scalar $class->search(@_);
722    }
723
724    my $rec = {};
725    my $sth = $dbi_driver->fetch($rec, $class, $terms, $args);
726
727    my $iter = sub {
728        ## This is kind of a hack--we need $driver to stay in scope,
729        ## so that the DESTROY method isn't called. So we include it
730        ## in the scope of the closure.
731        my $d = $dbi_driver;
732        my $d2 = $driver;
733
734        if (@_ && ($_[0] eq 'finish')) {
735            if ($sth) {
736                $sth->finish;
737                $dbi_driver->end_query($sth);
738            }
739            undef $sth;
740            return;
741        }
742
743        unless ($sth->fetch) {
744            $sth->finish;
745            $dbi_driver->end_query($sth);
746            return;
747        }
748        my $obj;
749        $obj = $class->new;
750        $obj->set_values_internal($rec);
751        ## Don't need a duplicate as there's no previous version in memory
752        ## to preserve.
753        $obj->call_trigger('post_load') unless $args->{no_triggers};
754        $driver->cache_object($obj) if $obj && (!$args->{fetchonly});
755        $obj;
756    };
757    return $iter;
758}
759
760## Callbacks
761
762sub assign_audited_fields {
763    my ($obj, $orig_obj) = @_;
764    if ($obj->properties->{audit}) {
765        my $blog_id;
766        if ($obj->has_column('blog_id')) {
767            $blog_id = $obj->blog_id;
768        }
769        my @ts = offset_time_list(time, $blog_id);
770        my $ts = sprintf '%04d%02d%02d%02d%02d%02d',
771            $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
772
773        my $app = MT->instance;
774        if ($app && $app->can('user')) {
775            if (my $user = $app->user) {
776                if (!defined $obj->created_on) {
777                    $obj->created_by($user->id);
778                    $orig_obj->created_by($obj->created_by);
779                }
780            }
781        }
782        unless ($obj->created_on) {
783            $obj->created_on($ts);
784            $orig_obj->created_on($ts);
785            # intentionally not calling modified_by to distinguish
786            $obj->modified_on($ts);
787            $orig_obj->modified_on($ts);
788        }
789    }
790}
791
792sub modified_by {
793    my $obj = shift;
794    my ($user_id) = @_;
795    if ($user_id) {
796        if ($obj->properties->{audit}) {
797            my $res = $obj->SUPER::modified_by($user_id);
798
799            my $blog_id;
800            if ($obj->has_column('blog_id')) {
801                $blog_id = $obj->blog_id;
802            }
803            my @ts = offset_time_list(time, $blog_id);
804            my $ts = sprintf '%04d%02d%02d%02d%02d%02d',
805                $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
806            $obj->modified_on($ts);
807            return $res;
808        }
809    }
810    return $obj->SUPER::modified_by(@_);
811}
812
813# D::OD uses Class::Trigger. Map the call_trigger calls to also invoke
814# MT's callbacks (but internal Class::Trigger routines should be invoked
815# first in the case of pre-triggers, and last in the case of post-triggers).
816
817sub call_trigger {
818    my $obj = shift;
819    my $name = shift;
820    my $class = ref $obj || $obj;
821    my $pre_trigger = $name =~ m/^pre_/;
822    $obj->SUPER::call_trigger($name, @_) if $pre_trigger;
823    MT->run_callbacks($class . '::' . $name, $obj, @_);
824    $obj->SUPER::call_trigger($name, @_) unless $pre_trigger;
825}
826
827# Support for MT-based callbacks.
828
829sub add_callback {
830    my $class = shift;
831    my $meth = shift;
832    MT->add_callback($class . '::' . $meth, @_);
833}
834
835## Construction/initialization.
836
837sub init {
838    my $obj = shift;
839    $obj->SUPER::init(@_);
840    $obj->set_defaults();
841    return $obj;
842}
843
844sub set_defaults {
845    my $obj = shift;
846    my $defaults = $obj->properties->{'defaults'};
847    $obj->{'column_values'} = $defaults ? {%$defaults} : {};
848}
849
850sub __properties { }
851
852our $DRIVER;
853sub driver {
854    require MT::ObjectDriverFactory;
855    return $DRIVER ||= MT::ObjectDriverFactory->new;
856}
857
858sub table_name {
859    my $obj = shift;
860    return $obj->driver->table_for($obj);
861}
862
863sub clone_all {
864    my $obj = shift;
865    my $clone = $obj->SUPER::clone_all();
866    $clone->{__meta} = $obj->{__meta};  # TODO: clone this too
867    return $clone;
868}
869
870sub clone {
871    my $obj = shift;
872    my($param) = @_;
873    my $clone = $obj->clone_all();
874
875    ## If the caller has listed a set of columns not to copy to the clone,
876    ## delete them from the clone.
877    if ($param && ($param->{Except} || $param->{except})) {
878        for my $col (keys %{ $param->{Except} || $param->{except} }) {
879            $clone->$col(undef);
880        }
881    }
882    return $clone;
883}
884
885sub columns_of_type {
886    my $obj = shift;
887    my(@types) = @_;
888    my $props = $obj->properties;
889    my $cols = $props->{columns};
890    my $col_defs = $obj->column_defs;
891    my @cols;
892    my %types = map { $_ => 1 } @types;
893    for my $col (@$cols) {
894        push @cols, $col
895            if $col_defs->{$col} && exists $types{$col_defs->{$col}{type}};
896    }
897    \@cols;
898}
899
900sub created_on_obj {
901    my $obj = shift;
902    return $obj->column_as_datetime('created_on');
903}
904
905sub column_as_datetime {
906    my $obj = shift;
907    my ($col) = @_;
908    if (my $ts = $obj->column($col)) {
909        my $blog;
910        if ($obj->isa('MT::Blog')) {
911            $blog = $obj;
912        } else {
913            if (my $blog_id = $obj->blog_id) {
914                require MT::Blog;
915                $blog = MT::Blog->lookup($blog_id);
916            }
917        }
918        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)/;
919        require MT::DateTime;
920        my $four_digit_offset;
921        if ($blog) {
922            $four_digit_offset = sprintf('%.02d%.02d', int($blog->server_offset),
923                                        60 * abs($blog->server_offset
924                                                 - int($blog->server_offset)));
925        }
926        return new MT::DateTime(year => $y, month => $mo, day => $d,
927                                hour => $h, minute => $m, second => $s,
928                                time_zone => $four_digit_offset);
929    }
930    undef;
931}
932
933sub join_on {
934    return [ @_ ];
935}
936
937sub remove_meta {
938    my $obj = shift;
939    return 1 unless ref $obj;
940    my $mpkg = $obj->meta_pkg or return;
941    my $id_field = $obj->datasource . '_id';
942    return $mpkg->remove({ $id_field => $obj->id });
943}
944
945sub remove_children {
946    my $obj = shift;
947    return 1 unless ref $obj;
948
949    my ($param) = @_;
950    my $child_classes = $obj->properties->{child_classes} || {};
951    my @classes = keys %$child_classes;
952    return 1 unless @classes;
953
954    $param ||= {};
955    my $key = $param->{key} || $obj->datasource . '_id';
956    my $obj_id = $obj->id;
957    for my $class (@classes) {
958        eval "# line " . __LINE__ . " " . __FILE__ . "\nuse $class;";
959        $class->remove({ $key => $obj_id });
960    }
961    1;
962}
963
964sub get_by_key {
965    my $class = shift;
966    my ($key) = @_;
967    my($obj) = $class->search($key);
968    $obj ||= new $class;
969    $obj->set_values($key);
970    return $obj;
971}
972
973sub set_by_key {
974    my $class = shift;
975    my ($key, $value) = @_;
976    my ($obj) = $class->search($key);
977    unless ($obj) {
978        $obj = new $class;
979        $obj->set_values($key);
980    }
981    $obj->set_values($value) if $value;
982    $obj->save or return;
983    return $obj;
984}
985
986# This method is overridden since D::OD uses column_values to retrieve
987# the content to cache if caching is enabled. Thus, we must ensure any
988# metadata is serialized prior to caching.
989sub column_values {
990    my $props = $_[0]->properties;
991    if ($props->{meta_column}
992        && $_[0]->{changed_cols}{$props->{meta_column}}) {
993        $_[0]->pre_save_serialize_metadata;
994    }
995    return $_[0]->SUPER::column_values(@_);
996}
997
998# We override D::OD's set_values method here only allowing the
999# assignment of a column if the value given is defined. There are
1000# some legacy reasons for doing this, mostly for backward
1001# compatibility.
1002sub set_values {
1003    my $obj = shift;
1004    my ($values) = @_;
1005    for my $col (keys %$values) {
1006        unless ( $obj->has_column($col) ) {
1007            Carp::croak("You tried to set inexistent column $col to value $values->{$col} on " . ref($obj));
1008        }
1009        $obj->$col($values->{$col}) if defined $values->{$col};
1010    }
1011}
1012
1013sub column_def {
1014    my $obj = shift;
1015    my ($name) = @_;
1016    my $defs = $obj->column_defs;
1017    my $def = $defs->{$name};
1018    if (!ref($def)) {
1019        $defs->{$name} = $def = $obj->__parse_def($name, $def);
1020    }
1021    return $def;
1022}
1023
1024sub index_defs {
1025    my $obj = shift;
1026    my $props = $obj->properties;
1027    $props->{indexes};
1028}
1029
1030sub column_defs {
1031    my $obj = shift;
1032    my $props = $obj->properties;
1033    my $defs = $props->{column_defs};
1034    return undef if !$defs;
1035    my ($key) = keys %$defs;
1036    if (!(ref $defs->{$key})) {
1037        $obj->__parse_defs($props->{column_defs});
1038    }
1039    $props->{column_defs};
1040}
1041
1042sub __parse_defs {
1043    my $obj = shift;
1044    my ($defs) = @_;
1045    foreach my $col ( keys %$defs ) {
1046        next if ref($defs->{$col});
1047        $defs->{$col} = $obj->__parse_def($col, $defs->{$col});
1048    }
1049}
1050
1051sub __parse_def {
1052    my $obj = shift;
1053    my ($col, $def) = @_;
1054    return undef if !defined $def;
1055    my $props = $obj->properties;
1056    my %def;
1057    if ($def =~ s/^([^( ]+)\s*//) {
1058        $def{type} = $1;
1059    }
1060    if ($def =~ s/^\((\d+)\)\s*//) {
1061        $def{size} = $1;
1062    }
1063    $def{not_null} = 1 if $def =~ m/\bnot null\b/i;
1064    $def{key} = 1 if $def =~ m/\bprimary key\b/i;
1065    $def{key} = 1 if ($props->{primary_key}) && ($props->{primary_key} eq $col);
1066    $def{auto} = 1 if $def =~ m/\bauto[_ ]increment\b/i;
1067    $def{default} = $props->{defaults}{$col}
1068        if exists $props->{defaults}{$col};
1069    \%def;
1070}
1071
1072sub cache_property {
1073    my $obj = shift;
1074    my $key = shift;
1075    my $code = shift;
1076    if (ref $key eq 'CODE') {
1077        ($key, $code) = ($code, $key);
1078    }
1079    $key ||= (caller(1))[3];
1080
1081    my $r = MT->request;
1082    my $oc = $r->cache('object_cache');
1083    unless ($oc) {
1084        $oc = {};
1085        $r->cache('object_cache', $oc);
1086    }
1087    $oc = $oc->{"$obj"} ||= {};
1088    if (@_) {
1089        $oc->{$key} = $_[0];
1090    } else {
1091        if ((!exists $oc->{$key}) && $code) {
1092            $oc->{$key} = $code->($obj, @_);
1093        }
1094    }
1095    return exists $oc->{$key} ? $oc->{$key} : undef;
1096}
1097
1098sub clear_cache {
1099    my $obj = shift;
1100    my $oc = MT->request('object_cache') or return;
1101    if (@_) {
1102        $oc = $oc->{"$obj"};
1103        delete $oc->{shift} if $oc;
1104    } else {
1105        delete $oc->{"$obj"};
1106    }
1107}
1108
1109sub to_hash {
1110    my $obj = shift;
1111    my $hash = {};
1112    my $props = $obj->properties;
1113    my $pfx = $obj->datasource;
1114    my $values = $obj->column_values;
1115    foreach (keys %$values) {
1116        $hash->{"${pfx}.$_"} = $values->{$_};
1117    }
1118    if (my $meta = $props->{meta_columns}) {
1119        foreach (keys %$meta) {
1120            $hash->{"${pfx}.$_"} = $obj->meta($_);
1121        }
1122    }
1123    if ($obj->has_column('blog_id')) {
1124        my $blog_id = $obj->blog_id;
1125        require MT::Blog;
1126        if (my $blog = MT::Blog->lookup($blog_id)) {
1127            my $blog_hash = $blog->to_hash;
1128            $hash->{"${pfx}.$_"} = $blog_hash->{$_} foreach keys %$blog_hash;
1129        }
1130    }
1131    $hash;
1132}
1133
1134package MT::Object::Meta;
1135
1136use base qw( Data::ObjectDriver::BaseObject );
1137
1138sub driver { $MT::Object::DRIVER ||= MT::ObjectDriverFactory->new }
1139
1140sub install_properties {
1141    my $class = shift;
1142    my ($props) = @_;
1143    $props->{column_defs}->{$_} ||= 'string'
1144        for @{ $props->{columns} };
1145    $class->SUPER::install_properties(@_);
1146}
1147
1148sub meta_pkg { undef }
1149
1150*table_name = \&MT::Object::table_name;
1151*column_defs = \&MT::Object::column_defs;
1152*column_def = \&MT::Object::column_def;
1153*index_defs = \&MT::Object::index_defs;
1154*__parse_defs = \&MT::Object::__parse_defs;
1155*__parse_def = \&MT::Object::__parse_def;
1156*count = \&MT::Object::count;
1157
1158# TODO: copy this too
1159sub blob_requires_zip {}
1160
11611;
1162__END__
1163
1164=head1 NAME
1165
1166MT::Object - Movable Type base class for database-backed objects
1167
1168=head1 SYNOPSIS
1169
1170Creating an I<MT::Object> subclass:
1171
1172    package MT::Foo;
1173    use strict;
1174
1175    use base 'MT::Object';
1176
1177    __PACKAGE__->install_properties({
1178        columns_defs => {
1179            'id'  => 'integer not null auto_increment',
1180            'foo' => 'string(255)',
1181        },
1182        indexes => {
1183            foo => 1,
1184        },
1185        primary_key => 'id',
1186        datasource => 'foo',
1187    });
1188
1189Using an I<MT::Object> subclass:
1190
1191    use MT;
1192    use MT::Foo;
1193
1194    ## Create an MT object to load the system configuration and
1195    ## initialize an object driver.
1196    my $mt = MT->new;
1197
1198    ## Create an MT::Foo object, fill it with data, and save it;
1199    ## the object is saved using the object driver initialized above.
1200    my $foo = MT::Foo->new;
1201    $foo->foo('bar');
1202    $foo->save
1203        or die $foo->errstr;
1204
1205=head1 DESCRIPTION
1206
1207I<MT::Object> is the base class for all Movable Type objects that will be
1208serialized/stored to some location for later retrieval; this location could
1209be a DBM file, a relational database, etc.
1210
1211Movable Type objects know nothing about how they are stored--they know only
1212of what types of data they consist, the names of those types of data (their
1213columns), etc. The actual storage mechanism is in the I<MT::ObjectDriver::Driver::DBI>
1214class and its driver subclasses; I<MT::Object> subclasses, on the other hand,
1215are essentially just standard in-memory Perl objects, but with a little extra
1216self-knowledge.
1217
1218This distinction between storage and in-memory representation allows objects
1219to be serialized to disk in many different ways--for example, an object could
1220be stored in a MySQL database, in a DBM file, etc. Adding a new storage method
1221is as simple as writing an object driver--a non-trivial task, to be sure, but
1222one that will not require touching any other Movable Type code.
1223
1224=head1 SUBCLASSING
1225
1226Creating a subclass of I<MT::Object> is very simple; you simply need to
1227define the properties and metadata about the object you are creating. Start
1228by declaring your class, and inheriting from I<MT::Object>:
1229
1230    package MT::Foo;
1231    use strict;
1232
1233    use base 'MT::Object';
1234
1235=item * __PACKAGE__->install_properties($args)
1236
1237Then call the I<install_properties> method on your class name; an easy way
1238to get your class name is to use the special I<__PACKAGE__> variable:
1239
1240    __PACKAGE__->install_properties({
1241        column_defs => {
1242            'id' => 'integer not null auto_increment',
1243            'foo' => 'string(255)',
1244        },
1245        indexes => {
1246            foo => 1,
1247        },
1248        primary_key => 'id',
1249        datasource => 'foo',
1250    });
1251
1252I<install_properties> performs the necessary magic to install the metadata
1253about your new class in the MT system. The method takes one argument, a hash
1254reference containing the metadata about your class. That hash reference can
1255have the following keys:
1256
1257=over 4
1258
1259=item * column_defs
1260
1261The definition of the columns (fields) in your object. Column names are also
1262used for method names for your object, so your column name should not
1263contain any strange characters. (It could also be used as part of the name of
1264the column in a relational database table, so that is another reason to keep
1265column names somewhat sane.)
1266
1267The value for the I<columns> key should be a reference to an hashref
1268containing the key/value pairs that are names of your columns matched with
1269their schema definition.
1270
1271The type declaration of a column is pseudo-SQL. The data types loosely match
1272SQL types, but are vendor-neutral, and each MT::ObjectDriver will map these
1273to appropriate types for the database it services. The format of a column
1274type is as follows:
1275
1276    'column_name' => 'type(size) options'
1277
1278The 'type' part of the declaration can be any one of:
1279
1280=over 4
1281
1282=item * string
1283
1284For storing string data, typically up to 255 characters, but assigned a length identified by '(size)'.
1285
1286=item * integer
1287
1288For storing integers, maybe limited to 32 bits.
1289
1290=item * boolean
1291
1292For storing boolean values (numeric values of 1 or 0).
1293
1294=item * smallint
1295
1296For storing small integers, typically limited to 16 bits.
1297
1298=item * datetime
1299
1300For storing a full date and time value.
1301
1302=item * timestamp
1303
1304For storing a date and time that automatically updates upon save.
1305
1306=item * blob
1307
1308For storing binary data.
1309
1310=item * text
1311
1312For storing text data.
1313
1314=item * float
1315
1316For storing floating point values.
1317
1318=back
1319
1320Note: The physical data storage capacity of these types will vary depending on
1321the driver's implementation. Please refer to the documentation of the
1322MT::ObjectDriver you're using to determine the actual capacity for these
1323types.
1324
1325The '(size)' element of the declaration is only valid for the 'string' type.
1326
1327The 'options' element of the declaration is not required, but is used to
1328specify additional attributes of the column. Such as:
1329
1330=over 4
1331
1332=item * not null
1333
1334Specify 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.
1335
1336=item * auto_increment
1337
1338Specify for integer columns (typically the primary key) to automatically assign a value.
1339
1340=item * primary key
1341
1342Specify for identifying the column as the primary key (only valid for a single column).
1343
1344=back
1345
1346=item * indexes
1347
1348Specifies the column indexes on your objects; this only has consequence for
1349some object drivers (DBM, for example), where indexes are not automatically
1350maintained by the datastore (as they are in a relational database).
1351
1352The value for the I<indexes> key should be a reference to a hash containing
1353column names as keys, and the value C<1> for each key--each key represents
1354a column that should be indexed.
1355
1356B<NOTE:> with the DBM driver, if you do not set up an index on a column you
1357will not be able to select objects with values matching that column using the
1358I<load> and I<load_iter> interfaces (see below).
1359
1360=item * audit
1361
1362Automatically adds bookkeeping capabilities to your class--each object will
1363take on four new columns: I<created_on>, I<created_by>, I<modified_on>, and
1364I<modified_by>. The created_on, created_by columns will be populated
1365automatically (if they have not already been assigned at the time of saving
1366the object). Your application is responsible for updating the modified_on,
1367modified_by columns as these may require explicit application-specific
1368assignments (ie, your application may only want them updated during explicit
1369user interaction with the object, as opposed to cases where the object is
1370being changed and saved for mechanical purposes like upgrading a table).
1371
1372=item * datasource
1373
1374The name of the datasource for your class. The datasource is a name uniquely
1375identifying your class--it is used by the object drivers to construct table
1376names, file names, etc. So it should not be specific to any one driver.
1377
1378=item * meta
1379
1380Specify this property if you wish to add an additional 'meta' column to
1381the object properties. This is a special type of column that is used to
1382store complex data structures for the object. The data is serialized into
1383a blob for storage using the L<MT::Serialize> package.
1384
1385=item * meta_column
1386
1387If you wish to specify the name of the column to be used for storing
1388the object metadata, you may declare this property to name the column.
1389The default column name is 'meta'.
1390
1391=item * class_type
1392
1393If class_type is declared, an additional 'class' column is added to the
1394object properties. This column is then used to differentiate between
1395multiple object types that share the same physical table.
1396
1397Note that if this is used, all searches will be constrained to match
1398the class type of the package.
1399
1400=item * class_column
1401
1402Defines the name of the class column (default is 'class') for storing
1403classed objects (see 'class_type' above).
1404
1405=back
1406
1407=head1 USAGE
1408
1409=head2 System Initialization
1410
1411Before using (loading, saving, removing) an I<MT::Object> class and its
1412objects, you must always initialize the Movable Type system. This is done
1413with the following lines of code:
1414
1415    use MT;
1416    my $mt = MT->new;
1417
1418Constructing a new I<MT> objects loads the system configuration from the
1419F<mt.cfg> configuration file, then initializes the object driver that will
1420be used to manage serialized objects.
1421
1422=head2 Creating a new object
1423
1424To create a new object of an I<MT::Object> class, use the I<new> method:
1425
1426    my $foo = MT::Foo->new;
1427
1428I<new> takes no arguments, and simply initializes a new in-memory object.
1429In fact, you need not ever save this object to disk; it can be used as a
1430purely in-memory object.
1431
1432=head2 Setting and retrieving column values
1433
1434To set the column value of an object, use the name of the column as a method
1435name, and pass in the value for the column:
1436
1437    $foo->foo('bar');
1438
1439The return value of the above call will be C<bar>, the value to which you have
1440set the column.
1441
1442To retrieve the existing value of a column, call the same method, but without
1443an argument:
1444
1445    $foo->foo
1446
1447This returns the value of the I<foo> column from the I<$foo> object.
1448
1449=over 4
1450
1451=item * $obj->init()
1452
1453=back
1454
1455This method is used to initialize the object upon construction.
1456
1457=over 4
1458
1459=item * $obj->set_defaults()
1460
1461=back
1462
1463This method is used by the I<init> method to set the object defaults.
1464
1465=head2 Saving an object
1466
1467To save an object using the object driver, call the I<save> method:
1468
1469=over 4
1470
1471=item * $foo->save();
1472
1473=back
1474
1475On success, I<save> will return some true value; on failure, it will return
1476C<undef>, and you can retrieve the error message by calling the I<errstr>
1477method on the object:
1478
1479    $foo->save
1480        or die "Saving foo failed: ", $foo->errstr;
1481
1482If you are saving objects in a loop, take a look at the
1483L</"Note on object locking">.
1484
1485=head2 Loading an existing object or objects
1486
1487=over 4
1488
1489=item * $obj->load()
1490
1491=item * $obj->load_iter()
1492
1493=back
1494
1495You can load an object from the datastore using the I<load> method. I<load>
1496is by far the most complicated method, because there are many different ways
1497to load an object: by ID, by column value, by using a join with another type
1498of object, etc.
1499
1500In addition, you can load objects either into an array (I<load>), or by using
1501an iterator to step through the objects (I<load_iter>).
1502
1503I<load> has the following general form:
1504
1505    my @objects = MT::Foo->load(\%terms, \%arguments);
1506
1507I<load_iter> has the following general form:
1508
1509    my $iter = MT::Foo->load_iter(\%terms, \%arguments);
1510
1511Both methods share the same parameters; the only difference is the manner in
1512which they return the matching objects.
1513
1514If you call I<load> in scalar context, only the first row of the array
1515I<@objects> will be returned; this works well when you know that your I<load>
1516call can only ever result in one object returned--for example, when you load
1517an object by ID.
1518
1519I<\%terms> should be either:
1520
1521=over 4
1522
1523=item * The numeric ID of an object in the datastore.
1524
1525=item * A reference to a hash.
1526
1527The hash should have keys matching column names and the values are the
1528values for that column.
1529
1530For example, to load an I<MT::Foo> object where the I<foo> column is
1531equal to C<bar>, you could do this:
1532
1533    my @foo = MT::Foo->load({ foo => 'bar' });
1534
1535In addition to a simple scalar, the hash value can be a reference to an array;
1536combined with the I<range> setting in the I<\%arguments> list, you can use
1537this to perform range searches. If the value is a reference, the first element
1538in the array specifies the low end of the range, and the second element the
1539high end.
1540
1541=back
1542
1543I<\%arguments> should be a reference to a hash containing parameters for the
1544search. The following parameters are allowed:
1545
1546=over 4
1547
1548=item * sort => "column"
1549
1550Sort the resulting objects by the column C<column>; C<column> must be an
1551indexed column (see L</"indexes">, above).
1552
1553=item * direction => "ascend|descend"
1554
1555To be used together with I<sort>; specifies the sort order (ascending or
1556descending). The default is C<ascend>.
1557
1558=item * limit => "N"
1559
1560Rather than loading all of the matching objects (the default), load only
1561C<N> objects.
1562
1563=item * offset => "M"
1564
1565To be used together with I<limit>; rather than returning the first C<N>
1566matches (the default), return matches C<M> through C<N + M>.
1567
1568=item * start_val => "value"
1569
1570To be used together with I<limit> and I<sort>; rather than returning the
1571first C<N> matches, return the first C<N> matches where C<column> (the sort
1572column) is greater than C<value>.
1573
1574=item * range
1575
1576To be used together with an array reference as the value for a column in
1577I<\%terms>; specifies that the specific column should be searched for a range
1578of values, rather than one specific value.
1579
1580The value of I<range> should be a hash reference, where the keys are column
1581names, and the values are all C<1>; each key specifies a column that should
1582be interpreted as a range.
1583
1584    MT::Foo->load( { created_on => [ '20011008000000', undef ] },
1585        { range => { created_on => 1 } } );
1586
1587This selects C<MT::Foo> objects whose created_on date is greater than
15882001-10-08 00:00:00.
1589
1590=item * range_incl
1591
1592Like the 'range' attribute, but defines an inclusive range.
1593
1594=item * join
1595
1596Can be used to select a set of objects based on criteria, or sorted by
1597criteria, from another set of objects. An example is selecting the C<N>
1598entries most recently commented-upon; the sorting is based on I<MT::Comment>
1599objects, but the objects returned are actually I<MT::Entry> objects. Using
1600I<join> in this situation is faster than loading the most recent
1601I<MT::Comment> objects, then loading each of the I<MT::Entry> objects
1602individually.
1603
1604Note that I<join> is not a normal SQL join, in that the objects returned are
1605always of only one type--in the above example, the objects returned are only
1606I<MT::Entry> objects, and cannot include columns from I<MT::Comment> objects.
1607
1608I<join> has the following general syntax:
1609
1610    join => MT::Foo->join_on( JOIN_COLUMN, I<\%terms>, I<\%arguments> )
1611
1612Use the actual MT::Object-descended package name and the join_on static method
1613providing these parameters: I<JOIN_COLUMN> is the column joining the two
1614object tables, I<\%terms> and I<\%arguments> have the same meaning as they do
1615in the outer I<load> or I<load_iter> argument lists: they are used to select
1616the objects with which the join is performed.
1617
1618For example, to select the last 10 most recently commmented-upon entries, you
1619could use the following statement:
1620
1621    my @entries = MT::Entry->load(undef, {
1622        'join' => MT::Comment->join_on( 'entry_id',
1623                    { blog_id => $blog_id },
1624                    { 'sort' => 'created_on',
1625                      direction => 'descend',
1626                      unique => 1,
1627                      limit => 10 } )
1628    });
1629
1630In this statement, the I<unique> setting ensures that the I<MT::Entry>
1631objects returned are unique; if this flag were not given, two copies of the
1632same I<MT::Entry> could be returned, if two comments were made on the same
1633entry.
1634
1635=item * unique
1636
1637Ensures that the objects being returned are unique.
1638
1639This is really only useful when used within a I<join>, because when loading
1640data out of a single object datastore, the objects are always going to be
1641unique.
1642
1643=back
1644
1645=head2 Removing an object
1646
1647=over 4
1648
1649=item * $foo->remove()
1650
1651=back
1652
1653To remove an object from the datastore, call the I<remove> method on an
1654object that you have already loaded using I<load>:
1655
1656    $foo->remove();
1657
1658On success, I<remove> will return some true value; on failure, it will return
1659C<undef>, and you can retrieve the error message by calling the I<errstr>
1660method on the object:
1661
1662    $foo->remove
1663        or die "Removing foo failed: ", $foo->errstr;
1664
1665If you are removing objects in a loop, take a look at the
1666L</"Note on object locking">.
1667
1668=head2 Removing select objects of a particular class
1669
1670Combining the syntax of the load and remove methods, you can use the
1671static version of the remove method to remove particular objects:
1672
1673    MT::Foo->remove({ bar => 'baz' });
1674
1675The terms you specify to remove by should be indexed columns. This
1676method will load the object and remove it, firing the callback operations
1677associated with those operations.
1678
1679=head2 Removing all of the objects of a particular class
1680
1681To quickly remove all of the objects of a particular class, call the
1682I<remove_all> method on the class name in question:
1683
1684=over 4
1685
1686=item * MT::Foo->remove_all();
1687
1688=back
1689
1690On success, I<remove_all> will return some true value; on failure, it will
1691return C<undef>, and you can retrieve the error message by calling the
1692I<errstr> method on the class name:
1693
1694    MT::Foo->remove_all
1695        or die "Removing all foo objects failed: ", MT::Foo->errstr;
1696
1697=head2 Removing all the children of an object
1698
1699=over 4
1700
1701=item * $obj->remove_children([ \%param ])
1702
1703=back
1704
1705If your class has registered 'child_classes' as part of it's properties,
1706then this method may be used to remove objects that are associated with
1707the active object.
1708
1709This method is typically used in an overridden 'remove' method.
1710
1711    sub remove {
1712        my $obj = shift;
1713        $obj->remove_children({ key => 'object_id' });
1714        $obj->SUPER::remove(@_);
1715    }
1716
1717The 'key' parameter specified here lets you identify the field name used by
1718the children classes to relate back to the parent class. If unspecified,
1719C<remove_children> will assume the key to be the datasource name of the
1720current class with an '_id' suffix.
1721
1722=head2 Getting the count of a number of objects
1723
1724To determine how many objects meeting a particular set of conditions exist,
1725use the I<count> method:
1726
1727    my $count = MT::Foo->count({ foo => 'bar' });
1728
1729I<count> takes the same arguments (I<\%terms> and I<\%arguments>) as I<load>
1730and I<load_iter>, above.
1731
1732=head2 Determining if an object exists in the datastore
1733
1734To check an object for existence in the datastore, use the I<exists> method:
1735
1736    if ($foo->exists) {
1737        print "Foo $foo already exists!";
1738    }
1739
1740=head2 Counting groups of objects
1741
1742=over 4
1743
1744=item * $obj->count_group_by()
1745
1746=back
1747
1748The count_group_by method can be used to retrieve a list of all the
1749distinct values that appear in a given column along with a count of
1750how many objects carry that value. The routine can also be used with
1751more than one column, in which case it retrieves the distinct pairs
1752(or n-tuples) of values in those columns, along with the counts.
1753Yet more powerful, any SQL expression can be used in place of
1754the column names to count how many object produce any given result
1755values when run through those expressions.
1756
1757  $iter = MT::Foo->count_group_by($terms, {%args, group => $group_exprs});
1758
1759C<$terms> and C<%args> pick out a subset of the MT::Foo objects in the
1760usual way. C<$group_expressions> is an array reference containing the
1761SQL expressions for the values you want to group by. A single row will
1762be returned for each distinct tuple of values resulting from the
1763$group_expressions. For example, if $group_expressions were just a
1764single column (e.g. group => ['created_on']) then a single row would
1765be returned for each distinct value of the 'created_on' column. If
1766$group_expressions were multiple columns, a row would be returned for
1767each distinct pair (or n-tuple) of values found in those columns.
1768
1769Each application of the iterator C<$iter> returns a list in the form:
1770
1771  ($count, $group_val1, $group_val2, ...)
1772
1773Where C<$count> is the number of MT::Foo objects for which the group
1774expressions are the values ($group_val1, $group_val2, ...). These
1775values are in the same order as the corresponding group expressions in
1776the $group_exprs argument.
1777
1778In this example, we load up groups of MT::Pip objects, grouped by the
1779pair (cat_id, invoice_id), and print how many pips have that pair of
1780values.
1781
1782    $iter = MT::Pip->count_group_by(undef,
1783                                    {group => ['cat_id',
1784                                               'invoice_id']});
1785    while (($count, $cat, $inv) = $iter->()) {
1786        print "There are $count Pips with " .
1787            "category $cat and invoice $inv\n";
1788    }
1789
1790=head2 Inspecting and Manipulating Object State
1791
1792=over 4
1793
1794=item * $obj->column_values()
1795
1796=back
1797
1798Use C<column_values> and C<set_values> to get and set the fields of an
1799object I<en masse>. The former returns a hash reference mapping column
1800names to their values in this object. For example:
1801
1802    $values = $obj->column_values()
1803
1804=over 4
1805
1806=item * $obj->set_values()
1807
1808=back
1809
1810C<set_values> accepts a similar hash ref, which need not give a value
1811for every field. For example:
1812
1813    $obj->set_values({col1 => $val1, col2 => $val2});
1814
1815is equivalent to
1816
1817    $obj->col1($val1);
1818    $obj->col2($val2);
1819
1820=head2 Other Methods
1821
1822=over 4
1823
1824=item * $obj->clone([\%param])
1825
1826Returns a clone of C<$obj>. That is, a distinct object which has all
1827the same data stored within it. Changing values within one object does
1828not modify the other.
1829
1830An optional C<except> parameter may be provided to exclude particular
1831columns from the cloning operation. For example, the following would
1832clone the elements of the blog except the name attribute.
1833
1834   $blog->clone({ except => { name => 1 } });
1835
1836=item * $obj->column_names()
1837
1838Returns a list of the names of columns in C<$obj>; includes all those
1839specified to the install_properties method as well as the audit
1840properties (C<created_on>, C<modified_on>, C<created_by>,
1841C<modified_by>), if those were enabled in install_properties.
1842
1843=item * $obj->set_driver()
1844
1845This method sets the object driver to use to link with a database.
1846
1847=item * MT::Foo->driver()
1848
1849=item * $obj->driver()
1850
1851Returns the ObjectDriver object that links this object with a database.
1852
1853=item * $obj->created_on_obj()
1854
1855Returns an MT::DateTime object representing the moment when the
1856object was first saved to the database.
1857
1858=item * MT::Foo->set_by_key($key_terms, $value_terms)
1859
1860A convenience method that loads whatever object matches the C<$key_terms>
1861argument and sets some or all of its fields according to the
1862C<$value_terms>. For example:
1863
1864   MT::Foo->set_by_key({name => 'Thor'},
1865                       {region => 'Norway', gender => 'Male'});
1866
1867This loads the C<MT::Foo> object having 'name' field equal to 'Thor'
1868and sets the 'region' and 'gender' fields appropriately.
1869
1870More than one term is acceptable in the C<$key_terms> argument. The
1871matching object is the one that matches all of the C<$key_terms>.
1872
1873This method only useful if you know that there is a unique object
1874matching the given key. There need not be a unique constraint on the
1875columns named in the C<$key_hash>; but if not, you should be confident
1876that only one object will match the key.
1877
1878=item * MT::Foo->get_by_key($key_terms)
1879
1880A convenience method that loads whatever object matches the C<$key_terms>
1881argument. If no matching object is found, a new object will be constructed
1882and the C<$key_terms> provided will be assigned to it. So regardless of
1883whether the key exists already, this method will return an object with the
1884key requested. Note, however: if a new object is instantiated it is
1885not automatically saved.
1886
1887    my $thor = MT::Foo->get_by_key({name => 'Thor'});
1888    $thor->region('Norway');
1889    $thor->gender('Male');
1890    $thor->save;
1891
1892The fact that it returns a new object if one isn't found is to help
1893optimize this pattern:
1894
1895    my $obj = MT::Foo->load({key => $value});
1896    if (!$obj) {
1897        $obj = new MT::Foo;
1898        $obj->key($value);
1899    }
1900
1901This is equivalent to:
1902
1903    my $obj = MT::Foo->get_by_key({key => $value});
1904
1905If you don't appreciate the autoinstantiation behavior of this method,
1906just use the C<load> method instead.
1907
1908More than one term is acceptable in the C<$key_terms> argument. The
1909matching object is the one that matches all of the C<$key_terms>.
1910
1911This method only useful if you know that there is a unique object
1912matching the given key. There need not be a unique constraint on the
1913columns named in the C<$key_hash>; but if not, you should be confident
1914that only one object will match the key.
1915
1916=item * $obj->cache_property($key, $code)
1917
1918Caches the provided key (e.g. entry, trackback) with the return value
1919of the given code reference (which is often an object load call) so
1920that the value does not have to be recomputed each time.
1921
1922=item * $obj->column_def($name)
1923
1924This method returns the value of the given I<$name> C<column_defs>
1925propery.
1926
1927=item * $obj->column_defs()
1928
1929This method returns all the C<column_defs> of the property of the
1930object.
1931
1932=item * $obj->to_hash()
1933
1934TODO - So far I have not divined what this method actually does. Hints?
1935
1936=item * Class->join_on()
1937
1938This method returns the list of used by the join arguments parameter
1939used by the L<MT::App::CMS/listing> method.
1940
1941=item * $obj->properties()
1942
1943TODO - Return the return properties of the object.
1944
1945=item * $obj->to_xml()
1946
1947TODO - Returns the XML representation of the object.
1948This method is defined in MT/BackupRestore.pm - you must first
1949use MT::BackupRestore to use this method.
1950
1951=item * $obj->restore_parent_ids()
1952
1953TODO - Backup file contains parent objects' ids (foreign keys).  However,
1954when parent objcects are restored, their ids will be changed.  This method
1955is to match the old and new ids of parent objects for children objects to be
1956correctly associated.
1957This method is defined in MT/BackupRestore.pm - you must first
1958use MT::BackupRestore to use this method.
1959
1960=item * $obj->parent_names()
1961
1962TODO - Should be overridden by subclasses to return correct hash
1963whose keys are xml element names of the object's parent objects
1964and values are class names of them.
1965This method is defined in MT/BackupRestore.pm - you must first
1966use MT::BackupRestore to use this method.
1967
1968=back
1969
1970=head1 NOTES
1971
1972=head2 Note on object locking
1973
1974When you read objects from the datastore, the object table is locked with a
1975shared lock; when you write to the datastore, the table is locked with an
1976exclusive lock.
1977
1978Thus, note that saving or removing objects in the same loop where you are
1979loading them from an iterator will not work--the reason is that the datastore
1980maintains a shared lock on the object table while objects are being loaded
1981from the iterator, and thus the attempt to gain an exclusive lock when saving
1982or removing an object will cause deadlock.
1983
1984For example, you cannot do the following:
1985
1986    my $iter = MT::Foo->load_iter({ foo => 'bar' });
1987    while (my $foo = $iter->()) {
1988        $foo->remove;
1989    }
1990
1991Instead you should do either this:
1992
1993    my @foo = MT::Foo->load({ foo => 'bar' });
1994    for my $foo (@foo) {
1995        $foo->remove;
1996    }
1997
1998or this:
1999
2000    my $iter = MT::Foo->load_iter({ foo => 'bar' });
2001    my @to_remove;
2002    while (my $foo = $iter->()) {
2003        push @to_remove, $foo
2004            if SOME CONDITION;
2005    }
2006    for my $foo (@to_remove) {
2007        $foo->remove;
2008    }
2009
2010This last example is useful if you will not be removing every I<MT::Foo>
2011object where I<foo> equals C<bar>, because it saves memory--only the
2012I<MT::Foo> objects that you will be deleting are kept in memory at the same
2013time.
2014
2015=head1 CALLBACKS
2016
2017=over 4
2018
2019=item * $obj->add_callback()
2020
2021=back
2022
2023Most MT::Object operations can trigger callbacks to plugin code. Some
2024notable uses of this feature are: to be notified when a database record is
2025modified, or to pre- or post-process the data being flowing to the
2026database.
2027
2028To add a callback, invoke the C<add_callback> method of the I<MT::Object>
2029subclass, as follows:
2030
2031   MT::Foo->add_callback( "pre_save", <priority>,
2032                          <plugin object>, \&callback_function);
2033
2034The first argument is the name of the hook point. Any I<MT::Object>
2035subclass has a pre_ and a post_ hook point for each of the following
2036operations:
2037
2038    load
2039    save
2040    remove
2041    remove_all
2042    (load_iter operations will call the load callbacks)
2043
2044The second argument, E<lt>priorityE<gt>, is the relative order in
2045which the callback should be called. The value should be between 1 and
204610, inclusive. Callbacks with priority 1 will be called before those
2047with 2, 2 before 3, and so on.
2048
2049Plugins which know they need to run first or last can use the priority
2050values 0 and 11. A callback with priority 0 will run before all
2051others, and if two callbacks try to use that value, an error will
2052result. Likewise priority 11 is exclusive, and runs last.
2053
2054How to remember which callback priorities are special? As you know,
2055most guitar amps have a volume knob that goes from 1 to 10. But, like
2056that of certain rock stars, our amp goes up to 11. A callback with
2057priority 11 is the "loudest" or most powerful callback, as it will be
2058called just before the object is saved to the database (in the case of
2059a 'pre' callback), or just before the object is returned (in the case
2060of a 'post' callback). A callback with priority 0 is the "quietest"
2061callback, as following callbacks can completely overwhelm it. This may
2062be a good choice for your plugin, as you may want your plugin to work
2063well with other plugins. Determining the correct priority is a matter
2064of thinking about your plugin in relation to others, and adjusting the
2065priority based on experience so that users get the best use out of the
2066plugin.
2067
2068The E<lt>plugin objectE<gt> is an object of type MT::Plugin which
2069gives some information about the plugin. This is used to include
2070the plugin's name in any error messages.
2071
2072E<lt>callback functionE<gt> is a code referense for a subroutine that
2073will be called. The arguments to this
2074function vary by operation (see I<MT::Callback> for details),
2075but in each case the first parameter is the I<MT::Callback> object
2076itself:
2077
2078  sub my_callback {
2079      my ($cb, ...) = @_;
2080
2081      if ( <error condition> ) {
2082          return $cb->error("Error message");
2083      }
2084  }
2085
2086Strictly speaking, the return value of a callback is ignored. Calling
2087the error() method of the MT::Callback object (C<$cb> in this case)
2088propagates the error message up to the MT activity log.
2089
2090Another way to handle errors is to call C<die>. If a callback dies,
2091I<MT> will warn the error to the activity log, but will continue
2092processing the MT::Object operation: so other callbacks will still
2093run, and the database operation should still occur.
2094
2095=head2 Any-class Object Callbacks
2096
2097If you add a callback to the MT class with a hook point that begins
2098with C<*::>, such as:
2099
2100    MT->add_callback('*::post_save', 7, $my_plugin, \&code_ref);
2101
2102then it will be called whenever post_save callbacks are called.
2103"Any-class" callbacks are called I<after> all class-specific
2104callbacks. Note that C<add_callback> must be called on the C<MT> class,
2105not on a subclass of C<MT::Object>.
2106
2107=over 4
2108
2109=item * $obj->set_callback_routine()
2110
2111This method just calls the set_callback_routine as defined by the
2112MT::ObjectDriver set with the I<set_driver> method.
2113
2114
2115=back
2116
2117=head2 Caveat
2118
2119Be careful how you handle errors. If you transform data as it goes
2120into and out of the database, and it is possible for one of your
2121callbacks to fail, the data may get saved in an undefined state. It
2122may then be difficult or impossible for the user to recover that data.
2123
2124=head1 AUTHOR & COPYRIGHTS
2125
2126Please see the I<MT> manpage for author, copyright, and license information.
2127
2128=cut
Note: See TracBrowser for help on using the browser.