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

Revision 1762, 59.9 kB (checked in by mpaschal, 20 months ago)

Make meta fields blobs by default, since they're mostly data structures
BugzID: 68749

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