root/branches/release-30/lib/MT/Object.pm @ 1416

Revision 1416, 58.6 kB (checked in by bchoate, 21 months ago)

Fixed variable name.

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