root/branches/release-36/lib/MT/Object.pm @ 2112

Revision 2112, 65.4 kB (checked in by bchoate, 19 months ago)

Relay error message when failing to save object using set_by_key method. BugId:70269

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