root/branches/release-35/lib/MT/Object.pm @ 1943

Revision 1943, 64.5 kB (checked in by bchoate, 20 months ago)

Decreased the type column to permit indexing id+type+vchar_indexed under mysql/utf8 character set.

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