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

Revision 1849, 63.5 kB (checked in by mpaschal, 20 months ago)

Column defs in MT::Objects are hashes, not just strings, so check for blob type fields appropriately
Use MT::Object's hash-column-def-aware columns_of_type() method
BugzID: 68749

  • 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*columns_of_type = \&MT::Object::columns_of_type;
1158
1159# TODO: copy this too
1160sub blob_requires_zip {}
1161
11621;
1163__END__
1164
1165=head1 NAME
1166
1167MT::Object - Movable Type base class for database-backed objects
1168
1169=head1 SYNOPSIS
1170
1171Creating an I<MT::Object> subclass:
1172
1173    package MT::Foo;
1174    use strict;
1175
1176    use base 'MT::Object';
1177
1178    __PACKAGE__->install_properties({
1179        columns_defs => {
1180            'id'  => 'integer not null auto_increment',
1181            'foo' => 'string(255)',
1182        },
1183        indexes => {
1184            foo => 1,
1185        },
1186        primary_key => 'id',
1187        datasource => 'foo',
1188    });
1189
1190Using an I<MT::Object> subclass:
1191
1192    use MT;
1193    use MT::Foo;
1194
1195    ## Create an MT object to load the system configuration and
1196    ## initialize an object driver.
1197    my $mt = MT->new;
1198
1199    ## Create an MT::Foo object, fill it with data, and save it;
1200    ## the object is saved using the object driver initialized above.
1201    my $foo = MT::Foo->new;
1202    $foo->foo('bar');
1203    $foo->save
1204        or die $foo->errstr;
1205
1206=head1 DESCRIPTION
1207
1208I<MT::Object> is the base class for all Movable Type objects that will be
1209serialized/stored to some location for later retrieval; this location could
1210be a DBM file, a relational database, etc.
1211
1212Movable Type objects know nothing about how they are stored--they know only
1213of what types of data they consist, the names of those types of data (their
1214columns), etc. The actual storage mechanism is in the I<MT::ObjectDriver::Driver::DBI>
1215class and its driver subclasses; I<MT::Object> subclasses, on the other hand,
1216are essentially just standard in-memory Perl objects, but with a little extra
1217self-knowledge.
1218
1219This distinction between storage and in-memory representation allows objects
1220to be serialized to disk in many different ways--for example, an object could
1221be stored in a MySQL database, in a DBM file, etc. Adding a new storage method
1222is as simple as writing an object driver--a non-trivial task, to be sure, but
1223one that will not require touching any other Movable Type code.
1224
1225=head1 SUBCLASSING
1226
1227Creating a subclass of I<MT::Object> is very simple; you simply need to
1228define the properties and metadata about the object you are creating. Start
1229by declaring your class, and inheriting from I<MT::Object>:
1230
1231    package MT::Foo;
1232    use strict;
1233
1234    use base 'MT::Object';
1235
1236=item * __PACKAGE__->install_properties($args)
1237
1238Then call the I<install_properties> method on your class name; an easy way
1239to get your class name is to use the special I<__PACKAGE__> variable:
1240
1241    __PACKAGE__->install_properties({
1242        column_defs => {
1243            'id' => 'integer not null auto_increment',
1244            'foo' => 'string(255)',
1245        },
1246        indexes => {
1247            foo => 1,
1248        },
1249        primary_key => 'id',
1250        datasource => 'foo',
1251    });
1252
1253I<install_properties> performs the necessary magic to install the metadata
1254about your new class in the MT system. The method takes one argument, a hash
1255reference containing the metadata about your class. That hash reference can
1256have the following keys:
1257
1258=over 4
1259
1260=item * column_defs
1261
1262The definition of the columns (fields) in your object. Column names are also
1263used for method names for your object, so your column name should not
1264contain any strange characters. (It could also be used as part of the name of
1265the column in a relational database table, so that is another reason to keep
1266column names somewhat sane.)
1267
1268The value for the I<columns> key should be a reference to an hashref
1269containing the key/value pairs that are names of your columns matched with
1270their schema definition.
1271
1272The type declaration of a column is pseudo-SQL. The data types loosely match
1273SQL types, but are vendor-neutral, and each MT::ObjectDriver will map these
1274to appropriate types for the database it services. The format of a column
1275type is as follows:
1276
1277    'column_name' => 'type(size) options'
1278
1279The 'type' part of the declaration can be any one of:
1280
1281=over 4
1282
1283=item * string
1284
1285For storing string data, typically up to 255 characters, but assigned a length identified by '(size)'.
1286
1287=item * integer
1288
1289For storing integers, maybe limited to 32 bits.
1290
1291=item * boolean
1292
1293For storing boolean values (numeric values of 1 or 0).
1294
1295=item * smallint
1296
1297For storing small integers, typically limited to 16 bits.
1298
1299=item * datetime
1300
1301For storing a full date and time value.
1302
1303=item * timestamp
1304
1305For storing a date and time that automatically updates upon save.
1306
1307=item * blob
1308
1309For storing binary data.
1310
1311=item * text
1312
1313For storing text data.
1314
1315=item * float
1316
1317For storing floating point values.
1318
1319=back
1320
1321Note: The physical data storage capacity of these types will vary depending on
1322the driver's implementation. Please refer to the documentation of the
1323MT::ObjectDriver you're using to determine the actual capacity for these
1324types.
1325
1326The '(size)' element of the declaration is only valid for the 'string' type.
1327
1328The 'options' element of the declaration is not required, but is used to
1329specify additional attributes of the column. Such as:
1330
1331=over 4
1332
1333=item * not null
1334
1335Specify 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.
1336
1337=item * auto_increment
1338
1339Specify for integer columns (typically the primary key) to automatically assign a value.
1340
1341=item * primary key
1342
1343Specify for identifying the column as the primary key (only valid for a single column).
1344
1345=back
1346
1347=item * indexes
1348
1349Specifies the column indexes on your objects; this only has consequence for
1350some object drivers (DBM, for example), where indexes are not automatically
1351maintained by the datastore (as they are in a relational database).
1352
1353The value for the I<indexes> key should be a reference to a hash containing
1354column names as keys, and the value C<1> for each key--each key represents
1355a column that should be indexed.
1356
1357B<NOTE:> with the DBM driver, if you do not set up an index on a column you
1358will not be able to select objects with values matching that column using the
1359I<load> and I<load_iter> interfaces (see below).
1360
1361=item * audit
1362
1363Automatically adds bookkeeping capabilities to your class--each object will
1364take on four new columns: I<created_on>, I<created_by>, I<modified_on>, and
1365I<modified_by>. The created_on, created_by columns will be populated
1366automatically (if they have not already been assigned at the time of saving
1367the object). Your application is responsible for updating the modified_on,
1368modified_by columns as these may require explicit application-specific
1369assignments (ie, your application may only want them updated during explicit
1370user interaction with the object, as opposed to cases where the object is
1371being changed and saved for mechanical purposes like upgrading a table).
1372
1373=item * datasource
1374
1375The name of the datasource for your class. The datasource is a name uniquely
1376identifying your class--it is used by the object drivers to construct table
1377names, file names, etc. So it should not be specific to any one driver.
1378
1379=item * meta
1380
1381Specify this property if you wish to add an additional 'meta' column to
1382the object properties. This is a special type of column that is used to
1383store complex data structures for the object. The data is serialized into
1384a blob for storage using the L<MT::Serialize> package.
1385
1386=item * meta_column
1387
1388If you wish to specify the name of the column to be used for storing
1389the object metadata, you may declare this property to name the column.
1390The default column name is 'meta'.
1391
1392=item * class_type
1393
1394If class_type is declared, an additional 'class' column is added to the
1395object properties. This column is then used to differentiate between
1396multiple object types that share the same physical table.
1397
1398Note that if this is used, all searches will be constrained to match
1399the class type of the package.
1400
1401=item * class_column
1402
1403Defines the name of the class column (default is 'class') for storing
1404classed objects (see 'class_type' above).
1405
1406=back
1407
1408=head1 USAGE
1409
1410=head2 System Initialization
1411
1412Before using (loading, saving, removing) an I<MT::Object> class and its
1413objects, you must always initialize the Movable Type system. This is done
1414with the following lines of code:
1415
1416    use MT;
1417    my $mt = MT->new;
1418
1419Constructing a new I<MT> objects loads the system configuration from the
1420F<mt.cfg> configuration file, then initializes the object driver that will
1421be used to manage serialized objects.
1422
1423=head2 Creating a new object
1424
1425To create a new object of an I<MT::Object> class, use the I<new> method:
1426
1427    my $foo = MT::Foo->new;
1428
1429I<new> takes no arguments, and simply initializes a new in-memory object.
1430In fact, you need not ever save this object to disk; it can be used as a
1431purely in-memory object.
1432
1433=head2 Setting and retrieving column values
1434
1435To set the column value of an object, use the name of the column as a method
1436name, and pass in the value for the column:
1437
1438    $foo->foo('bar');
1439
1440The return value of the above call will be C<bar>, the value to which you have
1441set the column.
1442
1443To retrieve the existing value of a column, call the same method, but without
1444an argument:
1445
1446    $foo->foo
1447
1448This returns the value of the I<foo> column from the I<$foo> object.
1449
1450=over 4
1451
1452=item * $obj->init()
1453
1454=back
1455
1456This method is used to initialize the object upon construction.
1457
1458=over 4
1459
1460=item * $obj->set_defaults()
1461
1462=back
1463
1464This method is used by the I<init> method to set the object defaults.
1465
1466=head2 Saving an object
1467
1468To save an object using the object driver, call the I<save> method:
1469
1470=over 4
1471
1472=item * $foo->save();
1473
1474=back
1475
1476On success, I<save> will return some true value; on failure, it will return
1477C<undef>, and you can retrieve the error message by calling the I<errstr>
1478method on the object:
1479
1480    $foo->save
1481        or die "Saving foo failed: ", $foo->errstr;
1482
1483If you are saving objects in a loop, take a look at the
1484L</"Note on object locking">.
1485
1486=head2 Loading an existing object or objects
1487
1488=over 4
1489
1490=item * $obj->load()
1491
1492=item * $obj->load_iter()
1493
1494=back
1495
1496You can load an object from the datastore using the I<load> method. I<load>
1497is by far the most complicated method, because there are many different ways
1498to load an object: by ID, by column value, by using a join with another type
1499of object, etc.
1500
1501In addition, you can load objects either into an array (I<load>), or by using
1502an iterator to step through the objects (I<load_iter>).
1503
1504I<load> has the following general form:
1505
1506    my @objects = MT::Foo->load(\%terms, \%arguments);
1507
1508I<load_iter> has the following general form:
1509
1510    my $iter = MT::Foo->load_iter(\%terms, \%arguments);
1511
1512Both methods share the same parameters; the only difference is the manner in
1513which they return the matching objects.
1514
1515If you call I<load> in scalar context, only the first row of the array
1516I<@objects> will be returned; this works well when you know that your I<load>
1517call can only ever result in one object returned--for example, when you load
1518an object by ID.
1519
1520I<\%terms> should be either:
1521
1522=over 4
1523
1524=item * The numeric ID of an object in the datastore.
1525
1526=item * A reference to a hash.
1527
1528The hash should have keys matching column names and the values are the
1529values for that column.
1530
1531For example, to load an I<MT::Foo> object where the I<foo> column is
1532equal to C<bar>, you could do this:
1533
1534    my @foo = MT::Foo->load({ foo => 'bar' });
1535
1536In addition to a simple scalar, the hash value can be a reference to an array;
1537combined with the I<range> setting in the I<\%arguments> list, you can use
1538this to perform range searches. If the value is a reference, the first element
1539in the array specifies the low end of the range, and the second element the
1540high end.
1541
1542=back
1543
1544I<\%arguments> should be a reference to a hash containing parameters for the
1545search. The following parameters are allowed:
1546
1547=over 4
1548
1549=item * sort => "column"
1550
1551Sort the resulting objects by the column C<column>; C<column> must be an
1552indexed column (see L</"indexes">, above).
1553
1554=item * direction => "ascend|descend"
1555
1556To be used together with I<sort>; specifies the sort order (ascending or
1557descending). The default is C<ascend>.
1558
1559=item * limit => "N"
1560
1561Rather than loading all of the matching objects (the default), load only
1562C<N> objects.
1563
1564=item * offset => "M"
1565
1566To be used together with I<limit>; rather than returning the first C<N>
1567matches (the default), return matches C<M> through C<N + M>.
1568
1569=item * start_val => "value"
1570
1571To be used together with I<limit> and I<sort>; rather than returning the
1572first C<N> matches, return the first C<N> matches where C<column> (the sort
1573column) is greater than C<value>.
1574
1575=item * range
1576
1577To be used together with an array reference as the value for a column in
1578I<\%terms>; specifies that the specific column should be searched for a range
1579of values, rather than one specific value.
1580
1581The value of I<range> should be a hash reference, where the keys are column
1582names, and the values are all C<1>; each key specifies a column that should
1583be interpreted as a range.
1584
1585    MT::Foo->load( { created_on => [ '20011008000000', undef ] },
1586        { range => { created_on => 1 } } );
1587
1588This selects C<MT::Foo> objects whose created_on date is greater than
15892001-10-08 00:00:00.
1590
1591=item * range_incl
1592
1593Like the 'range' attribute, but defines an inclusive range.
1594
1595=item * join
1596
1597Can be used to select a set of objects based on criteria, or sorted by
1598criteria, from another set of objects. An example is selecting the C<N>
1599entries most recently commented-upon; the sorting is based on I<MT::Comment>
1600objects, but the objects returned are actually I<MT::Entry> objects. Using
1601I<join> in this situation is faster than loading the most recent
1602I<MT::Comment> objects, then loading each of the I<MT::Entry> objects
1603individually.
1604
1605Note that I<join> is not a normal SQL join, in that the objects returned are
1606always of only one type--in the above example, the objects returned are only
1607I<MT::Entry> objects, and cannot include columns from I<MT::Comment> objects.
1608
1609I<join> has the following general syntax:
1610
1611    join => MT::Foo->join_on( JOIN_COLUMN, I<\%terms>, I<\%arguments> )
1612
1613Use the actual MT::Object-descended package name and the join_on static method
1614providing these parameters: I<JOIN_COLUMN> is the column joining the two
1615object tables, I<\%terms> and I<\%arguments> have the same meaning as they do
1616in the outer I<load> or I<load_iter> argument lists: they are used to select
1617the objects with which the join is performed.
1618
1619For example, to select the last 10 most recently commmented-upon entries, you
1620could use the following statement:
1621
1622    my @entries = MT::Entry->load(undef, {
1623        'join' => MT::Comment->join_on( 'entry_id',
1624                    { blog_id => $blog_id },
1625                    { 'sort' => 'created_on',
1626                      direction => 'descend',
1627                      unique => 1,
1628                      limit => 10 } )
1629    });
1630
1631In this statement, the I<unique> setting ensures that the I<MT::Entry>
1632objects returned are unique; if this flag were not given, two copies of the
1633same I<MT::Entry> could be returned, if two comments were made on the same
1634entry.
1635
1636=item * unique
1637
1638Ensures that the objects being returned are unique.
1639
1640This is really only useful when used within a I<join>, because when loading
1641data out of a single object datastore, the objects are always going to be
1642unique.
1643
1644=back
1645
1646=head2 Removing an object
1647
1648=over 4
1649
1650=item * $foo->remove()
1651
1652=back
1653
1654To remove an object from the datastore, call the I<remove> method on an
1655object that you have already loaded using I<load>:
1656
1657    $foo->remove();
1658
1659On success, I<remove> will return some true value; on failure, it will return
1660C<undef>, and you can retrieve the error message by calling the I<errstr>
1661method on the object:
1662
1663    $foo->remove
1664        or die "Removing foo failed: ", $foo->errstr;
1665
1666If you are removing objects in a loop, take a look at the
1667L</"Note on object locking">.
1668
1669=head2 Removing select objects of a particular class
1670
1671Combining the syntax of the load and remove methods, you can use the
1672static version of the remove method to remove particular objects:
1673
1674    MT::Foo->remove({ bar => 'baz' });
1675
1676The terms you specify to remove by should be indexed columns. This
1677method will load the object and remove it, firing the callback operations
1678associated with those operations.
1679
1680=head2 Removing all of the objects of a particular class
1681
1682To quickly remove all of the objects of a particular class, call the
1683I<remove_all> method on the class name in question:
1684
1685=over 4
1686
1687=item * MT::Foo->remove_all();
1688
1689=back
1690
1691On success, I<remove_all> will return some true value; on failure, it will
1692return C<undef>, and you can retrieve the error message by calling the
1693I<errstr> method on the class name:
1694
1695    MT::Foo->remove_all
1696        or die "Removing all foo objects failed: ", MT::Foo->errstr;
1697
1698=head2 Removing all the children of an object
1699
1700=over 4
1701
1702=item * $obj->remove_children([ \%param ])
1703
1704=back
1705
1706If your class has registered 'child_classes' as part of it's properties,
1707then this method may be used to remove objects that are associated with
1708the active object.
1709
1710This method is typically used in an overridden 'remove' method.
1711
1712    sub remove {
1713        my $obj = shift;
1714        $obj->remove_children({ key => 'object_id' });
1715        $obj->SUPER::remove(@_);
1716    }
1717
1718The 'key' parameter specified here lets you identify the field name used by
1719the children classes to relate back to the parent class. If unspecified,
1720C<remove_children> will assume the key to be the datasource name of the
1721current class with an '_id' suffix.
1722
1723=head2 Getting the count of a number of objects
1724
1725To determine how many objects meeting a particular set of conditions exist,
1726use the I<count> method:
1727
1728    my $count = MT::Foo->count({ foo => 'bar' });
1729
1730I<count> takes the same arguments (I<\%terms> and I<\%arguments>) as I<load>
1731and I<load_iter>, above.
1732
1733=head2 Determining if an object exists in the datastore
1734
1735To check an object for existence in the datastore, use the I<exists> method:
1736
1737    if ($foo->exists) {
1738        print "Foo $foo already exists!";
1739    }
1740
1741=head2 Counting groups of objects
1742
1743=over 4
1744
1745=item * $obj->count_group_by()
1746
1747=back
1748
1749The count_group_by method can be used to retrieve a list of all the
1750distinct values that appear in a given column along with a count of
1751how many objects carry that value. The routine can also be used with
1752more than one column, in which case it retrieves the distinct pairs
1753(or n-tuples) of values in those columns, along with the counts.
1754Yet more powerful, any SQL expression can be used in place of
1755the column names to count how many object produce any given result
1756values when run through those expressions.
1757
1758  $iter = MT::Foo->count_group_by($terms, {%args, group => $group_exprs});
1759
1760C<$terms> and C<%args> pick out a subset of the MT::Foo objects in the
1761usual way. C<$group_expressions> is an array reference containing the
1762SQL expressions for the values you want to group by. A single row will
1763be returned for each distinct tuple of values resulting from the
1764$group_expressions. For example, if $group_expressions were just a
1765single column (e.g. group => ['created_on']) then a single row would
1766be returned for each distinct value of the 'created_on' column. If
1767$group_expressions were multiple columns, a row would be returned for
1768each distinct pair (or n-tuple) of values found in those columns.
1769
1770Each application of the iterator C<$iter> returns a list in the form:
1771
1772  ($count, $group_val1, $group_val2, ...)
1773
1774Where C<$count> is the number of MT::Foo objects for which the group
1775expressions are the values ($group_val1, $group_val2, ...). These
1776values are in the same order as the corresponding group expressions in
1777the $group_exprs argument.
1778
1779In this example, we load up groups of MT::Pip objects, grouped by the
1780pair (cat_id, invoice_id), and print how many pips have that pair of
1781values.
1782
1783    $iter = MT::Pip->count_group_by(undef,
1784                                    {group => ['cat_id',
1785                                               'invoice_id']});
1786    while (($count, $cat, $inv) = $iter->()) {
1787        print "There are $count Pips with " .
1788            "category $cat and invoice $inv\n";
1789    }
1790
1791=head2 Inspecting and Manipulating Object State
1792
1793=over 4
1794
1795=item * $obj->column_values()
1796
1797=back
1798
1799Use C<column_values> and C<set_values> to get and set the fields of an
1800object I<en masse>. The former returns a hash reference mapping column
1801names to their values in this object. For example:
1802
1803    $values = $obj->column_values()
1804
1805=over 4
1806
1807=item * $obj->set_values()
1808
1809=back
1810
1811C<set_values> accepts a similar hash ref, which need not give a value
1812for every field. For example:
1813
1814    $obj->set_values({col1 => $val1, col2 => $val2});
1815
1816is equivalent to
1817
1818    $obj->col1($val1);
1819    $obj->col2($val2);
1820
1821=head2 Other Methods
1822
1823=over 4
1824
1825=item * $obj->clone([\%param])
1826
1827Returns a clone of C<$obj>. That is, a distinct object which has all
1828the same data stored within it. Changing values within one object does
1829not modify the other.
1830
1831An optional C<except> parameter may be provided to exclude particular
1832columns from the cloning operation. For example, the following would
1833clone the elements of the blog except the name attribute.
1834
1835   $blog->clone({ except => { name => 1 } });
1836
1837=item * $obj->column_names()
1838
1839Returns a list of the names of columns in C<$obj>; includes all those
1840specified to the install_properties method as well as the audit
1841properties (C<created_on>, C<modified_on>, C<created_by>,
1842C<modified_by>), if those were enabled in install_properties.
1843
1844=item * $obj->set_driver()
1845
1846This method sets the object driver to use to link with a database.
1847
1848=item * MT::Foo->driver()
1849
1850=item * $obj->driver()
1851
1852Returns the ObjectDriver object that links this object with a database.
1853
1854=item * $obj->created_on_obj()
1855
1856Returns an MT::DateTime object representing the moment when the
1857object was first saved to the database.
1858
1859=item * MT::Foo->set_by_key($key_terms, $value_terms)
1860
1861A convenience method that loads whatever object matches the C<$key_terms>
1862argument and sets some or all of its fields according to the
1863C<$value_terms>. For example:
1864
1865   MT::Foo->set_by_key({name => 'Thor'},
1866                       {region => 'Norway', gender => 'Male'});
1867
1868This loads the C<MT::Foo> object having 'name' field equal to 'Thor'
1869and sets the 'region' and 'gender' fields appropriately.
1870
1871More than one term is acceptable in the C<$key_terms> argument. The
1872matching object is the one that matches all of the C<$key_terms>.
1873
1874This method only useful if you know that there is a unique object
1875matching the given key. There need not be a unique constraint on the
1876columns named in the C<$key_hash>; but if not, you should be confident
1877that only one object will match the key.
1878
1879=item * MT::Foo->get_by_key($key_terms)
1880
1881A convenience method that loads whatever object matches the C<$key_terms>
1882argument. If no matching object is found, a new object will be constructed
1883and the C<$key_terms> provided will be assigned to it. So regardless of
1884whether the key exists already, this method will return an object with the
1885key requested. Note, however: if a new object is instantiated it is
1886not automatically saved.
1887
1888    my $thor = MT::Foo->get_by_key({name => 'Thor'});
1889    $thor->region('Norway');
1890    $thor->gender('Male');
1891    $thor->save;
1892
1893The fact that it returns a new object if one isn't found is to help
1894optimize this pattern:
1895
1896    my $obj = MT::Foo->load({key => $value});
1897    if (!$obj) {
1898        $obj = new MT::Foo;
1899        $obj->key($value);
1900    }
1901
1902This is equivalent to:
1903
1904    my $obj = MT::Foo->get_by_key({key => $value});
1905
1906If you don't appreciate the autoinstantiation behavior of this method,
1907just use the C<load> method instead.
1908
1909More than one term is acceptable in the C<$key_terms> argument. The
1910matching object is the one that matches all of the C<$key_terms>.
1911
1912This method only useful if you know that there is a unique object
1913matching the given key. There need not be a unique constraint on the
1914columns named in the C<$key_hash>; but if not, you should be confident
1915that only one object will match the key.
1916
1917=item * $obj->cache_property($key, $code)
1918
1919Caches the provided key (e.g. entry, trackback) with the return value
1920of the given code reference (which is often an object load call) so
1921that the value does not have to be recomputed each time.
1922
1923=item * $obj->column_def($name)
1924
1925This method returns the value of the given I<$name> C<column_defs>
1926propery.
1927
1928=item * $obj->column_defs()
1929
1930This method returns all the C<column_defs> of the property of the
1931object.
1932
1933=item * $obj->to_hash()
1934
1935TODO - So far I have not divined what this method actually does. Hints?
1936
1937=item * Class->join_on()
1938
1939This method returns the list of used by the join arguments parameter
1940used by the L<MT::App::CMS/listing> method.
1941
1942=item * $obj->properties()
1943
1944TODO - Return the return properties of the object.
1945
1946=item * $obj->to_xml()
1947
1948TODO - Returns the XML representation of the object.
1949This method is defined in MT/BackupRestore.pm - you must first
1950use MT::BackupRestore to use this method.
1951
1952=item * $obj->restore_parent_ids()
1953
1954TODO - Backup file contains parent objects' ids (foreign keys).  However,
1955when parent objcects are restored, their ids will be changed.  This method
1956is to match the old and new ids of parent objects for children objects to be
1957correctly associated.
1958This method is defined in MT/BackupRestore.pm - you must first
1959use MT::BackupRestore to use this method.
1960
1961=item * $obj->parent_names()
1962
1963TODO - Should be overridden by subclasses to return correct hash
1964whose keys are xml element names of the object's parent objects
1965and values are class names of them.
1966This method is defined in MT/BackupRestore.pm - you must first
1967use MT::BackupRestore to use this method.
1968
1969=back
1970
1971=head1 NOTES
1972
1973=head2 Note on object locking
1974
1975When you read objects from the datastore, the object table is locked with a
1976shared lock; when you write to the datastore, the table is locked with an
1977exclusive lock.
1978
1979Thus, note that saving or removing objects in the same loop where you are
1980loading them from an iterator will not work--the reason is that the datastore
1981maintains a shared lock on the object table while objects are being loaded
1982from the iterator, and thus the attempt to gain an exclusive lock when saving
1983or removing an object will cause deadlock.
1984
1985For example, you cannot do the following:
1986
1987    my $iter = MT::Foo->load_iter({ foo => 'bar' });
1988    while (my $foo = $iter->()) {
1989        $foo->remove;
1990    }
1991
1992Instead you should do either this:
1993
1994    my @foo = MT::Foo->load({ foo => 'bar' });
1995    for my $foo (@foo) {
1996        $foo->remove;
1997    }
1998
1999or this:
2000
2001    my $iter = MT::Foo->load_iter({ foo => 'bar' });
2002    my @to_remove;
2003    while (my $foo = $iter->()) {
2004        push @to_remove, $foo
2005            if SOME CONDITION;
2006    }
2007    for my $foo (@to_remove) {
2008        $foo->remove;
2009    }
2010
2011This last example is useful if you will not be removing every I<MT::Foo>
2012object where I<foo> equals C<bar>, because it saves memory--only the
2013I<MT::Foo> objects that you will be deleting are kept in memory at the same
2014time.
2015
2016=head1 CALLBACKS
2017
2018=over 4
2019
2020=item * $obj->add_callback()
2021
2022=back
2023
2024Most MT::Object operations can trigger callbacks to plugin code. Some
2025notable uses of this feature are: to be notified when a database record is
2026modified, or to pre- or post-process the data being flowing to the
2027database.
2028
2029To add a callback, invoke the C<add_callback> method of the I<MT::Object>
2030subclass, as follows:
2031
2032   MT::Foo->add_callback( "pre_save", <priority>,
2033                          <plugin object>, \&callback_function);
2034
2035The first argument is the name of the hook point. Any I<MT::Object>
2036subclass has a pre_ and a post_ hook point for each of the following
2037operations:
2038
2039    load
2040    save
2041    remove
2042    remove_all
2043    (load_iter operations will call the load callbacks)
2044
2045The second argument, E<lt>priorityE<gt>, is the relative order in
2046which the callback should be called. The value should be between 1 and
204710, inclusive. Callbacks with priority 1 will be called before those
2048with 2, 2 before 3, and so on.
2049
2050Plugins which know they need to run first or last can use the priority
2051values 0 and 11. A callback with priority 0 will run before all
2052others, and if two callbacks try to use that value, an error will
2053result. Likewise priority 11 is exclusive, and runs last.
2054
2055How to remember which callback priorities are special? As you know,
2056most guitar amps have a volume knob that goes from 1 to 10. But, like
2057that of certain rock stars, our amp goes up to 11. A callback with
2058priority 11 is the "loudest" or most powerful callback, as it will be
2059called just before the object is saved to the database (in the case of
2060a 'pre' callback), or just before the object is returned (in the case
2061of a 'post' callback). A callback with priority 0 is the "quietest"
2062callback, as following callbacks can completely overwhelm it. This may
2063be a good choice for your plugin, as you may want your plugin to work
2064well with other plugins. Determining the correct priority is a matter
2065of thinking about your plugin in relation to others, and adjusting the
2066priority based on experience so that users get the best use out of the
2067plugin.
2068
2069The E<lt>plugin objectE<gt> is an object of type MT::Plugin which
2070gives some information about the plugin. This is used to include
2071the plugin's name in any error messages.
2072
2073E<lt>callback functionE<gt> is a code referense for a subroutine that
2074will be called. The arguments to this
2075function vary by operation (see I<MT::Callback> for details),
2076but in each case the first parameter is the I<MT::Callback> object
2077itself:
2078
2079  sub my_callback {
2080      my ($cb, ...) = @_;
2081
2082      if ( <error condition> ) {
2083          return $cb->error("Error message");
2084      }
2085  }
2086
2087Strictly speaking, the return value of a callback is ignored. Calling
2088the error() method of the MT::Callback object (C<$cb> in this case)
2089propagates the error message up to the MT activity log.
2090
2091Another way to handle errors is to call C<die>. If a callback dies,
2092I<MT> will warn the error to the activity log, but will continue
2093processing the MT::Object operation: so other callbacks will still
2094run, and the database operation should still occur.
2095
2096=head2 Any-class Object Callbacks
2097
2098If you add a callback to the MT class with a hook point that begins
2099with C<*::>, such as:
2100
2101    MT->add_callback('*::post_save', 7, $my_plugin, \&code_ref);
2102
2103then it will be called whenever post_save callbacks are called.
2104"Any-class" callbacks are called I<after> all class-specific
2105callbacks. Note that C<add_callback> must be called on the C<MT> class,
2106not on a subclass of C<MT::Object>.
2107
2108=over 4
2109
2110=item * $obj->set_callback_routine()
2111
2112This method just calls the set_callback_routine as defined by the
2113MT::ObjectDriver set with the I<set_driver> method.
2114
2115
2116=back
2117
2118=head2 Caveat
2119
2120Be careful how you handle errors. If you transform data as it goes
2121into and out of the database, and it is possible for one of your
2122callbacks to fail, the data may get saved in an undefined state. It
2123may then be difficult or impossible for the user to recover that data.
2124
2125=head1 AUTHOR & COPYRIGHTS
2126
2127Please see the I<MT> manpage for author, copyright, and license information.
2128
2129=cut
Note: See TracBrowser for help on using the browser.