root/branches/release-35/lib/MT/Blog.pm @ 1971

Revision 1971, 38.7 kB (checked in by takayama, 20 months ago)

Fixed BugId:75137
* Keep compatibility of attribute name
* added tests

  • 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::Blog;
8
9use strict;
10use base qw( MT::Object );
11
12use MT::FileMgr;
13use MT::Util;
14
15__PACKAGE__->install_properties({
16    column_defs => {
17        'id' => 'integer not null auto_increment',
18        'name' => 'string(255) not null',
19        'description' => 'text',
20        'archive_type' => 'string(255)',
21        'archive_type_preferred' => 'string(25)',
22        'site_path' => 'string(255)',
23        'site_url' => 'string(255)',
24        'days_on_index' => 'integer',
25        'entries_on_index' => 'integer',
26        'file_extension' => 'string(10)',
27        'email_new_comments' => 'boolean',
28        'allow_comment_html' => 'boolean',
29        'autolink_urls' => 'boolean',
30        'sort_order_posts' => 'string(8)',
31        'sort_order_comments' => 'string(8)',
32        'allow_comments_default' => 'boolean',
33        'server_offset' => 'float',
34        'convert_paras' => 'string(30)',
35        'convert_paras_comments' => 'string(30)',
36        'allow_pings_default' => 'boolean',
37        'status_default' => 'smallint',
38        'allow_anon_comments' => 'boolean',
39        'words_in_excerpt' => 'smallint',
40        'moderate_unreg_comments' => 'boolean',
41        'moderate_pings' => 'boolean',
42        'allow_unreg_comments' => 'boolean',
43        'allow_reg_comments' => 'boolean',
44        'allow_pings' => 'boolean',
45        'manual_approve_commenters' => 'boolean',
46        'require_comment_emails' => 'boolean',
47        'junk_folder_expiry' => 'integer',
48        'ping_weblogs' => 'boolean',
49        'mt_update_key' => 'string(30)',
50        'language' => 'string(5)',
51        'welcome_msg' => 'text',
52        'google_api_key' => 'string(32)',
53        'email_new_pings' => 'boolean',
54        'ping_blogs' => 'boolean',
55        'ping_technorati' => 'boolean',
56        'ping_google' => 'boolean',
57        'ping_others' => 'text',
58        'autodiscover_links' => 'boolean',
59        'sanitize_spec' => 'string(255)',
60        'cc_license' => 'string(255)',
61        'is_dynamic' => 'boolean',
62        'remote_auth_token' => 'string(50)',
63        'children_modified_on' => 'datetime',
64        'custom_dynamic_templates' => 'string(25)',
65        'junk_score_threshold' => 'float',
66        'internal_autodiscovery' => 'boolean',
67        'basename_limit' => 'smallint',
68        'use_comment_confirmation' => 'boolean',
69        'allow_commenter_regist' => 'boolean',
70        ## Have to keep these around for use in mt-upgrade.cgi.
71        'archive_url' => 'string(255)',
72        'archive_path' => 'string(255)',
73        'old_style_archive_links' => 'boolean',
74        'archive_tmpl_daily' => 'string(255)',
75        'archive_tmpl_weekly' => 'string(255)',
76        'archive_tmpl_monthly' => 'string(255)',
77        'archive_tmpl_category' => 'string(255)',
78        'archive_tmpl_individual' => 'string(255)',
79
80        # meta properties
81        'image_default_wrap_text' => 'integer meta',
82        'image_default_align' => 'string meta',
83        'image_default_thumb' => 'integer meta',
84        'image_default_width' => 'integer meta',
85        'image_default_wunits' => 'integer meta',
86        'image_default_constrain' => 'integer meta',
87        'image_default_popup' => 'integer meta',
88        'commenter_authenticators' => 'string meta',
89        'require_typekey_emails' => 'integer meta',
90        'nofollow_urls' => 'integer meta',
91        'follow_auth_links' => 'integer meta',
92        'update_pings' => 'integer meta',
93        'captcha_provider' => 'string meta',
94        'publish_queue' => 'integer meta',
95        'nwc_smart_replace' => 'integer meta',
96        'nwc_replace_field' => 'string meta',
97        'template_set' => 'string meta',
98        'page_layout' => 'string meta',
99        'include_system' => 'string meta',
100        'include_cache' => 'integer meta',
101    },
102    meta => 1,
103    audit => 1,
104    indexes => {
105        name => 1,
106    },
107    defaults => {
108        'custom_dynamic_templates' => 'none',
109    },
110    child_classes => ['MT::Entry', 'MT::Page', 'MT::Template', 'MT::Asset',
111                      'MT::Category', 'MT::Folder', 'MT::Notification', 'MT::Log',
112                      'MT::ObjectTag', 'MT::Association', 'MT::Comment',
113                      'MT::TBPing', 'MT::Trackback', 'MT::TemplateMap',
114                      'MT::Touch'],
115    datasource => 'blog',
116    primary_key => 'id',
117});
118
119# Image upload defaults.
120sub ALIGN () { 'none' }
121sub UNITS () { 'pixels' }
122
123sub class_label {
124    MT->translate("Blog");
125}
126
127sub class_label_plural {
128    MT->translate("Blogs");
129}
130
131{
132my $default_text_format;
133sub set_defaults {
134    my $blog = shift;
135    unless ($default_text_format) {
136        if (my $allowed = MT->config('AllowedTextFilters')) {
137            $allowed =~ s/\s*,.*//;
138            $default_text_format = $allowed; # choose first allowed format
139        } else {
140            $default_text_format = 'richtext'; # MT system default
141        }
142        my $filters = MT->registry("text_filters");
143        # If the 'richtext' filter exists,
144        # and is uncondition or it meets the condition, use
145        # it as the blog default text format.
146        if (!($filters->{$default_text_format} && (!$filters->{$default_text_format}{condition} || $filters->{$default_text_format}{condition}->('blog')))) {
147            $default_text_format = '__default__';
148        }
149    }
150    $blog->set_values_internal({
151        days_on_index => 0,
152        entries_on_index => 10,
153        words_in_excerpt => 40,
154        sort_order_posts => 'descend',
155        language => MT->config('DefaultLanguage'),
156        sort_order_comments => 'ascend',
157        file_extension => 'html',
158        convert_paras => $default_text_format,
159        allow_unreg_comments => 0,
160        allow_reg_comments => 1,
161        allow_pings => 1,
162        moderate_unreg_comments => MT::Blog::MODERATE_UNTRSTD(),
163        moderate_pings => 1,
164        require_comment_emails => 1,
165        allow_comments_default => 1,
166        allow_comment_html => 1,
167        autolink_urls => 1, 
168        allow_pings_default => 1,
169        require_comment_emails => 0,
170        convert_paras_comments => 1,
171        email_new_pings => 1,
172        email_new_comments => 1,
173        allow_commenter_regist => 1,
174        use_comment_confirmation => 1,
175        sanitize_spec => 0,
176        ping_weblogs => 0,
177        ping_blogs => 0,
178        ping_technorati => 0,
179        ping_google => 0,
180        archive_type => 'Individual,Monthly,Category,Category-Monthly,Page',
181        archive_type_preferred => 'Individual',
182        status_default => 2,
183        junk_score_threshold => 0,
184        junk_folder_expiry => 14, # 14 days
185        custom_dynamic_templates => 'none',
186        internal_autodiscovery => 0,
187        basename_limit => 100,
188        server_offset => MT->config('DefaultTimezone') || 0,
189        # something far in the future to force dynamic side to read it.
190        children_modified_on => '20101231120000',
191    });
192    return $blog;
193}
194}
195
196sub create_default_blog {
197    my $class = shift;
198    my ($blog_name, $blog_template) = @_;
199    $blog_name ||= MT->translate("First Blog");
200    $class = ref $class if ref $class;
201
202    my $blog = new $class;
203    $blog->name($blog_name);
204
205    # Enable nofollow options
206    $blog->nofollow_urls(1);
207    $blog->follow_auth_links(1);
208   
209    # Enable default commenter authentication
210    $blog->commenter_authenticators(MT->config('DefaultCommenterAuth'));
211
212    # set default page layout
213    $blog->page_layout('layout-wtt');
214
215    $blog->save or return $class->error($blog->errstr);
216    $blog->create_default_templates($blog_template || 'mt_blog')
217        or return $class->error($blog->errstr);
218    return $blog;
219}
220
221sub create_default_templates {
222    my $blog = shift;
223
224    require MT::DefaultTemplates;
225    my $tmpl_list = MT::DefaultTemplates->templates( @_ );
226    return $blog->error(MT->translate("No default templates were found."))
227        if !$tmpl_list || (ref($tmpl_list) ne 'ARRAY') || (!@$tmpl_list);
228
229    require MT::Template;
230    my @arch_tmpl;
231    for my $val (@$tmpl_list) {
232        next if $val->{global};
233
234        my $obj = MT::Template->new;
235        my $p = $val->{plugin} || 'MT'; # component and/or MT package for translate
236        local $val->{name} = $val->{name}; # name field is translated in "templates" call
237        local $val->{text} = $p->translate_templatized($val->{text});
238        $obj->build_dynamic(0);
239        foreach my $v (keys %$val) {
240            $obj->column($v, $val->{$v}) if $obj->has_column($v);
241        }
242        $obj->blog_id($blog->id);
243        if (my $pub_opts = $val->{publishing}) {
244            $obj->include_with_ssi(1) if $pub_opts->{include_with_ssi};
245        }
246        $obj->save;
247        if ($val->{mappings}) {
248            push @arch_tmpl, {
249                template => $obj,
250                mappings => $val->{mappings},
251                exists($val->{preferred}) ? (preferred => $val->{preferred}) : ()
252            };
253        }
254    }
255
256    if (@arch_tmpl) {
257        require MT::TemplateMap;
258        for my $map_set (@arch_tmpl) {
259            my $tmpl = $map_set->{template};
260            my $mappings = $map_set->{mappings};
261            foreach my $map_key (keys %$mappings) {
262                my $m = $mappings->{$map_key};
263                my $at = $m->{archive_type};
264                # my $preferred = $mappings->{$map_key}{preferred};
265                my $map = MT::TemplateMap->new;
266                $map->archive_type($at);
267                if ( exists $m->{preferred} ) {
268                    $map->is_preferred($m->{preferred});
269                }
270                else {
271                    $map->is_preferred(1);
272                }
273                $map->template_id($tmpl->id);
274                $map->file_template($m->{file_template}) if $m->{file_template};
275                $map->blog_id($tmpl->blog_id);
276                $map->save;
277            }
278        }
279    }
280
281    MT->run_callbacks(
282        ref($blog). '::post_create_default_templates',
283        $blog, 
284        $tmpl_list
285    );
286
287    return $blog;
288}
289
290# As of MT 4, we always manage fileinfo records.
291sub needs_fileinfo {
292    return 1;
293}
294
295sub current_timestamp {
296    my $blog = shift;
297    require MT::Util;
298    my @ts = MT::Util::offset_time_list(time, $blog->id);
299    return sprintf '%04d%02d%02d%02d%02d%02d',
300        $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
301}
302
303sub site_url {
304    my $blog = shift;
305    if (!@_ && $blog->is_dynamic) {
306        my $cfg = MT->config;
307        my $path = $cfg->CGIPath;
308        $path .= '/' unless $path =~ m!/$!;
309        return $path . $cfg->ViewScript . '/' . $blog->id;
310    } else {
311        return $blog->SUPER::site_url(@_);
312    }
313}
314
315sub archive_url {
316    my $blog = shift;
317    if (!@_ && $blog->is_dynamic) {
318        $blog->site_url;
319    } else {
320        $blog->SUPER::archive_url(@_) || $blog->site_url;
321    }
322}
323
324sub archive_path {
325    my $blog = shift;
326    $blog->SUPER::archive_path(@_) || $blog->site_path;
327}
328
329sub comment_text_filters {
330    my $blog = shift;
331    my $filters = $blog->convert_paras_comments;
332    return [] unless $filters;
333    if ($filters eq '1') {
334        return [ '__default__' ];
335    } else {
336        return [ split /\s*,\s*/, $filters ];
337    }
338}
339
340sub cc_license_url {
341    my $cc = $_[0]->cc_license or return '';
342    MT::Util::cc_url($cc);
343}
344
345sub email_all_comments {
346    return $_[0]->email_new_comments == 1;
347}
348
349sub email_attn_reqd_comments {
350    return $_[0]->email_new_comments == 2;
351}
352
353sub email_all_pings {
354    return $_[0]->email_new_pings == 1;
355}
356
357sub email_attn_reqd_pings {
358    return $_[0]->email_new_pings == 2;
359}
360
361sub MODERATE_NONE ()    { 0 }
362sub MODERATE_ALL ()     { 1 }
363sub MODERATE_UNTRSTD () { 2 }
364sub MODERATE_UNAUTHD () { 3 }
365
366sub publish_trusted_commenters {
367    !($_[0]->moderate_unreg_comments == MODERATE_ALL);
368}
369
370sub publish_authd_untrusted_commenters {
371    return $_[0]->moderate_unreg_comments == MODERATE_UNAUTHD
372        || $_[0]->moderate_unreg_comments == MODERATE_NONE;
373}
374
375sub publish_unauthd_commenters {
376    $_[0]->moderate_unreg_comments == MODERATE_NONE;
377}
378
379sub include_path_parts {
380    my $blog = shift;
381    my ($param) = @_;
382
383    my $filestem = MT::Util::dirify($param->{name}) || 'template_'.$param->{id};
384    my $filename = join q{.}, $filestem, $blog->file_extension;
385    my $path = $param->{path} || '';
386    my @path;
387    if ($path =~ s!^/!!) {
388        # absolute
389        @path = split q{/}, $path;
390    } else {
391        # relative
392        push @path, MT->config('IncludesDir');
393        push @path, split q{/}, $path;
394    }
395    return ($filename, @path);
396}
397
398sub include_path {
399    my $blog = shift;
400
401    my ($filename, @path) = $blog->include_path_parts(@_);
402    my $extra_path = File::Spec->catdir(@path);
403    my $full_path = File::Spec->catdir($blog->site_path, $extra_path);
404    my $file_path = File::Spec->catfile($full_path, $filename);
405    return wantarray ? ($file_path, $full_path, $filename) : $file_path;
406}
407
408sub include_url {
409    my $blog = shift;
410
411    my ($filename, @path) = $blog->include_path_parts(@_);
412    my $url = join q{/}, $blog->site_url, @path, $filename;
413    return $url;
414}
415
416sub include_statement {
417    my $blog = shift;
418
419    my $system = $blog->include_system or return;
420
421    my ($statement, $include);
422    if ($system eq 'shtml') {
423        $statement = q{<!--#include virtual="%s" -->};
424
425        my ($filename, @path) = $blog->include_path_parts(@_);
426        my $site_url = $blog->site_url;
427        $site_url =~ s{ \A \w+ :// [^/]+ }{}xms;
428        $site_url =~ s{ / \z }{}xms;
429        $include = join q{/}, $site_url, @path, $filename;
430    }
431    else {
432        $include = $blog->include_path(@_);
433        $statement = $system eq 'php'   ? q{<?php include("%s") ?>}
434                   : $system eq 'jsp'   ? q{<%@ include file="%s" %>}
435                   : $system eq 'asp'   ? '<!--#include file="%s" -->'
436                   :                      return
437                   ;
438    }
439    return sprintf $statement, MT::Util::encode_php($include, q{qq});
440}
441
442sub file_mgr {
443    my $blog = shift;
444    unless (exists $blog->{__file_mgr}) {
445## xxx need to add remote_host, remote_user, remote_pwd fields
446## then pull params from there; if remote_host is defined, we
447## assume we are using FTP?
448        $blog->{__file_mgr} = MT::FileMgr->new('Local');
449    }
450    $blog->{__file_mgr};
451}
452
453sub remove {
454    my $blog = shift;
455    $blog->remove_children({ key => 'blog_id'});
456    my $res = $blog->SUPER::remove(@_);
457    if ((ref $blog) && $res) {
458        require MT::Permission;
459        MT::Permission->remove({ blog_id => $blog->id });
460    }
461    $res;
462}
463
464# deprecated: use $blog->remote_auth_token instead
465sub effective_remote_auth_token {
466    my $blog = shift;
467    if (scalar @_) {
468        return $blog->remote_auth_token(@_);
469    }
470    if ($blog->remote_auth_token()) {
471        return $blog->remote_auth_token();
472    }
473    undef;
474}
475
476sub has_archive_type {
477    my $blog = shift;
478    my ($type) = @_;
479    my %at = map { lc $_ => 1 } split(/,/, $blog->archive_type);
480    return exists $at{lc $type} ? 1 : 0;
481}
482
483sub accepts_registered_comments {
484    $_[0]->allow_reg_comments && $_[0]->commenter_authenticators;
485}
486
487sub accepts_comments {
488    $_[0]->accepts_registered_comments || $_[0]->allow_unreg_comments;
489}
490
491sub count_static_templates {
492    my $blog = shift;
493    my ($archive_type) = @_;
494    my $result = 0;
495    require MT::TemplateMap;
496    my @maps = MT::TemplateMap->load({blog_id => $blog->id,
497                                      archive_type => $archive_type});
498    return 0 unless @maps;
499    require MT::Template;
500    foreach my $map (@maps) { 
501        my $tmpl = MT::Template->load($map->template_id)
502            or return 0;
503        $result++ if !$tmpl->build_dynamic;
504    }
505    #$result ||= 1 if ($blog->custom_dynamic_templates || '') ne 'custom';
506    return $result;
507}
508
509sub touch {
510    my $blog = shift;
511    my ( @types ) = @_;
512    my ($s,$m,$h,$d,$mo,$y) = localtime(time);
513    my $mod_time = sprintf("%04d%02d%02d%02d%02d%02d",
514                           1900+$y, $mo+1, $d, $h, $m, $s);
515    require MT::Touch;
516    MT::Touch->touch( $blog->id, @types );
517    $blog->children_modified_on($mod_time);
518    $mod_time;
519}
520
521sub clone {
522    my $blog = shift;
523    my ($param) = @_;
524    if ($param && $param->{Children}) {
525        $blog->clone_with_children(@_);
526    } else {
527        $blog->SUPER::clone(@_);
528    }
529}
530
531sub clone_with_children {
532    my $blog = shift;
533    my ($params) = @_;
534    my $callback = $params->{Callback} || sub {};
535    my $classes = $params->{Classes};
536    my $blog_name = $params->{BlogName};
537    delete $$params{Children} if ($params->{Children});
538    my $old_blog_id = $blog->id;
539
540    # we must clone:
541    #    Blog record
542    #    Entry records
543    #       - Comment records
544    #       - TrackBack records
545    #       - TBPing records
546    #       - ObjectTag records (if running 3.3)
547    #    Category records
548    #    Placement records
549    #    Template records
550    #    Permission records
551    #    IPBanList records???
552    #    Notification records???
553
554    my $new_blog_id;
555    my (%entry_map, %cat_map, %tb_map, %tmpl_map, $counter, $iter);
556
557    # Cloning blog
558    my $new_blog = $blog->clone($params);
559    $new_blog->name(MT->translate($blog_name ? $blog_name : "Clone of [_1]", $blog->name));
560    delete $new_blog->{column_values}->{id};
561    delete $new_blog->{changed_cols}->{id};
562    $new_blog->save or die $new_blog->errstr;
563    $new_blog_id = $new_blog->id;
564    $callback->(MT->translate("Cloned blog... new id is [_1].",
565        $new_blog_id));
566
567    if ((!exists $classes->{'MT::Permission'}) || $classes->{'MT::Permission'}) {
568        # Cloning PERMISSIONS records
569        $counter = 0;
570        my $state = MT->translate("Cloning permissions for blog:");
571        $callback->($state, "perms");
572        require MT::Permission;
573        $iter = MT::Permission->load_iter({blog_id => $old_blog_id});
574        while (my $perm = $iter->()) {
575            $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'perms')
576                if $counter && ($counter % 100 == 0);
577            $counter++;
578            delete $perm->{column_values}->{id};
579            delete $perm->{changed_cols}->{id};
580            $perm->blog_id($new_blog_id);
581            $perm->save or die $perm->errstr;
582        }
583        $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'perms');
584    }
585
586    if ((!exists $classes->{'MT::Association'}) || $classes->{'MT::Association'}) {
587        # Cloning association records
588        $counter = 0;
589        my $state = MT->translate("Cloning associations for blog:");
590        $callback->($state, "assoc");
591        require MT::Association;
592        $iter = MT::Association->load_iter({blog_id => $old_blog_id});
593        while (my $assoc = $iter->()) {
594            $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'assoc')
595                if $counter && ($counter % 100 == 0);
596            $counter++;
597            delete $assoc->{column_values}->{id};
598            delete $assoc->{changed_cols}->{id};
599            $assoc->blog_id($new_blog_id);
600            $assoc->save or die $assoc->errstr;
601        }
602        $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'assoc');
603    }
604
605    # include/exclude class logic
606    # if user has not specified 'Classes' element, clone everything
607    # if user has specified Classes, but a particular class is not
608    # identified, clone it (forward compatibility). if a class is
609    # specified and the flag is '1', clone it. if a class is specified
610    # but the flag is '0', skip it.
611
612    # MT::Entry -> MT::Category, MT::Comment, MT::Tracback, MT::TBPing
613    # MT::Page -> MT::Folder, MT::Comment, MT::Trackback, MT::TBPing
614
615    if ((!exists $classes->{'MT::Entry'}) || $classes->{'MT::Entry'}) {
616        # Cloning ENTRY records
617        my $state = MT->translate("Cloning entries for blog...");
618        $callback->($state, "entries");
619        $counter = 0;
620        require MT::Entry;
621        $iter = MT::Entry->load_iter({blog_id => $old_blog_id, class => '*'});
622        while (my $entry = $iter->()) {
623            $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'entries')
624                if $counter && ($counter % 100 == 0);
625            $counter++;
626            my $entry_id = $entry->id;
627            delete $entry->{column_values}->{id};
628            delete $entry->{changed_cols}->{id};
629            $entry->blog_id($new_blog_id);
630            $entry->save or die $entry->errstr;
631            $entry_map{$entry_id} = $entry->id;
632        }
633        $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'entries');
634
635        if ((!exists $classes->{'MT::Category'}) || $classes->{'MT::Category'}) {
636            # Cloning CATEGORY records
637            my $state = MT->translate("Cloning categories for blog...");
638            $callback->($state, "cats");
639            $counter = 0;
640            require MT::Category;
641            $iter = MT::Category->load_iter({ blog_id => $old_blog_id, class => '*' });
642            my %cat_parents;
643            while (my $cat = $iter->()) {
644                $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'cats')
645                    if $counter && ($counter % 100 == 0);
646                $counter++;
647                my $cat_id = $cat->id;
648                my $old_parent = $cat->parent;
649                delete $cat->{column_values}->{id};
650                delete $cat->{changed_cols}->{id};
651                $cat->blog_id($new_blog_id);
652                # temporarily wipe the parent association
653                # to avoid constraint issues.
654                $cat->parent(0);
655                $cat->save or die $cat->errstr;
656                $cat_map{$cat_id} = $cat->id;
657                if ($old_parent) {
658                    $cat_parents{$cat->id} = $old_parent;
659                }
660            }
661            # reassign the new category parents
662            foreach (keys %cat_parents) {
663                my $cat = MT::Category->load($_);
664                if ($cat) {
665                    $cat->parent($cat_map{$cat_parents{$cat->id}});
666                    $cat->save or die $cat->errstr;
667                }
668            }
669            $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'cats');
670
671            # Placements are automatically cloned if categories are
672            # cloned.
673            $state = MT->translate("Cloning entry placements for blog...");
674            $callback->($state, "places");
675            require MT::Placement;
676            $iter = MT::Placement->load_iter({ blog_id => $old_blog_id });
677            $counter = 0;
678            while (my $place = $iter->()) {
679                $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'places')
680                    if $counter && ($counter % 100 == 0);
681                $counter++;
682                delete $place->{column_values}->{id};
683                delete $place->{changed_cols}->{id};
684                $place->blog_id($new_blog_id);
685                $place->category_id($cat_map{$place->category_id});
686                $place->entry_id($entry_map{$place->entry_id});
687                $place->save or die $place->errstr;
688            }
689            $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'places');
690        }
691
692        if ((!exists $classes->{'MT::Comment'}) || $classes->{'MT::Comment'}) {
693            # Comments can only be cloned if entries are cloned.
694            my $state = MT->translate("Cloning comments for blog...");
695            $callback->($state, "comments");
696            require MT::Comment;
697            $iter = MT::Comment->load_iter({ blog_id => $old_blog_id });
698            $counter = 0;
699            while (my $comment = $iter->()) {
700                $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'comments')
701                    if $counter && ($counter % 100 == 0);
702                $counter++;
703                delete $comment->{column_values}->{id};
704                delete $comment->{changed_cols}->{id};
705                $comment->entry_id($entry_map{$comment->entry_id});
706                $comment->blog_id($new_blog_id);
707                $comment->save or die $comment->errstr;
708            }
709            $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'comments');
710        }
711
712        if ((!exists $classes->{'MT::ObjectTag'}) || $classes->{'MT::ObjectTag'}) {
713            # conditionally do MT::ObjectTag since it is only
714            # available with MT 3.3.
715            if ($MT::VERSION >= 3.3) {
716                my $state = MT->translate("Cloning entry tags for blog...");
717                $callback->($state, "tags");
718                require MT::ObjectTag;
719                $iter = MT::ObjectTag->load_iter({ blog_id => $old_blog_id, object_datasource => 'entry' });
720                $counter = 0;
721                while (my $entry_tag = $iter->()) {
722                    $callback->($state . " " . MT->translate("[_1] records processed...", $counter), "tags")
723                        if $counter && ($counter % 100 == 0);
724                    $counter++;
725                    delete $entry_tag->{column_values}->{id};
726                    delete $entry_tag->{changed_cols}->{id};
727                    $entry_tag->blog_id($new_blog_id);
728                    $entry_tag->object_id($entry_map{$entry_tag->object_id});
729                    $entry_tag->save or die $entry_tag->errstr;
730                }
731                $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'tags');
732            }
733        }
734    }
735
736    if ((!exists $classes->{'MT::Trackback'}) || $classes->{'MT::Trackback'}) {
737        my $state = MT->translate("Cloning TrackBacks for blog...");
738        $callback->($state, "tbs");
739        require MT::Trackback;
740        $iter = MT::Trackback->load_iter({ blog_id => $old_blog_id });
741        $counter = 0;
742        while (my $tb = $iter->()) {
743            next unless ($tb->entry_id && $entry_map{$tb->entry_id}) ||
744                ($tb->category_id && $cat_map{$tb->category_id});
745
746            $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'tbs')
747                if $counter && ($counter % 100 == 0);
748            $counter++;
749            my $tb_id = $tb->id;
750            delete $tb->{column_values}->{id};
751            delete $tb->{changed_cols}->{id};
752
753            if ($tb->category_id) {
754                if (my $cid = $cat_map{$tb->category_id}) {
755                    my $cat_tb = MT::Trackback->load(
756                        { category_id => $cid }
757                    );
758                    if ($cat_tb) {
759                        my $changed;
760                        if ($tb->passphrase) {
761                            $cat_tb->passphrase($tb->passphrase);
762                            $changed = 1;
763                        }
764                        if ($tb->is_disabled) {
765                            $cat_tb->is_disabled(1);
766                            $changed = 1;
767                        }
768                        $cat_tb->save if $changed;
769                        $tb_map{$tb_id} = $cat_tb->id;
770                        next;
771                    }
772                }
773            }
774            elsif ($tb->entry_id) {
775                if (my $eid = $entry_map{$tb->entry_id}) {
776                    my $entry_tb = MT::Entry->load($eid)->trackback;
777                    if ($entry_tb) {
778                        my $changed;
779                        if ($tb->passphrase) {
780                            $entry_tb->passphrase($tb->passphrase);
781                            $changed = 1;
782                        }
783                        if ($tb->is_disabled) {
784                            $entry_tb->is_disabled(1);
785                            $changed = 1;
786                        }
787                        $entry_tb->save if $changed;
788                        $tb_map{$tb_id} = $entry_tb->id;
789                        next;
790                    }
791                }
792            }
793
794            # A trackback wasn't created when saving the entry/category,
795            # (perhaps trackbacks are now disabled for the entry/category?)
796            # so create one now
797            $tb->entry_id($entry_map{$tb->entry_id})
798                if $tb->entry_id && $entry_map{$tb->entry_id};
799            $tb->category_id($cat_map{$tb->category_id})
800                if $tb->category_id && $cat_map{$tb->category_id};
801            $tb->blog_id($new_blog_id);
802            $tb->save or die $tb->errstr;
803            $tb_map{$tb_id} = $tb->id;
804        }
805        $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'tbs');
806
807        if ((!exists $classes->{'MT::TBPing'}) || $classes->{'MT::TBPing'}) {
808            my $state = MT->translate("Cloning TrackBack pings for blog...");
809            $callback->($state, "pings");
810            require MT::TBPing;
811            $iter = MT::TBPing->load_iter({ blog_id => $old_blog_id });
812            $counter = 0;
813            while (my $ping = $iter->()) {
814                next unless $tb_map{$ping->tb_id};
815                $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'pings')
816                    if $counter && ($counter % 100 == 0);
817                $counter++;
818                delete $ping->{column_values}->{id};
819                delete $ping->{changed_cols}->{id};
820                $ping->tb_id($tb_map{$ping->tb_id});
821                $ping->blog_id($new_blog_id);
822                $ping->save or die $ping->errstr;
823            }
824            $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'pings');
825        }
826    }
827
828    if ((!exists $classes->{'MT::Template'}) || $classes->{'MT::Template'}) {
829        my $state = MT->translate("Cloning templates for blog...");
830        $callback->($state, "tmpls");
831        require MT::Template;
832        $iter = MT::Template->load_iter({ blog_id => $old_blog_id });
833        $counter = 0;
834        while (my $tmpl = $iter->()) {
835            $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'tmpls')
836                if $counter && ($counter % 100 == 0);
837            my $tmpl_id = $tmpl->id;
838            $counter++;
839            delete $tmpl->{column_values}->{id};
840            delete $tmpl->{changed_cols}->{id};
841            # linked_file won't be cloned for now because
842            # new blog does not have site_path - breaks relative path
843            delete $tmpl->{column_values}->{linked_file};
844            delete $tmpl->{column_values}->{linked_file_mtime};
845            delete $tmpl->{column_values}->{linked_file_size};
846            $tmpl->blog_id($new_blog_id);
847            $tmpl->save or die $tmpl->errstr;
848            $tmpl_map{$tmpl_id} = $tmpl->id;
849        }
850        $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'tmpls');
851
852        $state = MT->translate("Cloning template maps for blog...");
853        $callback->($state, "tmplmaps");
854        require MT::TemplateMap;
855        $iter = MT::TemplateMap->load_iter({ blog_id => $old_blog_id });
856        $counter = 0;
857        while (my $map = $iter->()) {
858            $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'tmplmaps')
859                if $counter && ($counter % 100 == 0);
860            $counter++;
861            delete $map->{column_values}->{id};
862            delete $map->{changed_cols}->{id};
863            $map->template_id($tmpl_map{$map->template_id});
864            $map->blog_id($new_blog_id);
865            $map->save or die $map->errstr;
866        }
867        $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'tmplmaps');
868    }
869
870    MT->run_callbacks(ref($blog). '::post_clone',
871        OldBlogId => $blog->id, old_blog_id => $blog->id,
872        NewBlogId => $new_blog->id, new_blog_id => $new_blog->id,
873        OldObject => $blog, old_object => $blog,
874        NewObject => $new_blog, new_object => $new_blog,
875        EntryMap => \%entry_map, entry_map => \%entry_map,
876        CategoryMap => \%cat_map, category_map => \%cat_map,
877        TrackbackMap => \%tb_map, trackback_map => \%tb_map,
878        TemplateMap => \%tmpl_map, template_map => \%tmpl_map,
879        Callback => $callback, callback => $callback,
880    );
881    $new_blog;
882}
883
884sub smart_replace {
885    my $blog = shift;
886    if (@_) {
887        $blog->nwc_smart_replace(@_);
888        return;
889    }
890    my $val = $blog->nwc_smart_replace;
891    return defined($val) ? $val : MT->config->NwcSmartReplace;
892}
893
894sub smart_replace_fields {
895    my $blog = shift;
896    if (@_) {
897        $blog->nwc_replace_field(@_);
898        return;
899    }
900    my $val = $blog->nwc_replace_field;
901    return defined($val) ? $val : MT->config->NwcReplaceField;
902}
903
904#trans('blog')
905#trans('blogs')
906
9071;
908__END__
909
910=head1 NAME
911
912MT::Blog - Movable Type blog record
913
914=head1 SYNOPSIS
915
916    use MT::Blog;
917    my $blog = MT::Blog->load($blog_id);
918    $blog->name('Some new name');
919    $blog->save
920        or die $blog->errstr;
921
922=head1 DESCRIPTION
923
924An I<MT::Blog> object represents a blog in the Movable Type system. It
925contains all of the settings, preferences, and configuration for a particular
926blog. It does not contain any per-author permissions settings--for those,
927look at the I<MT::Permission> object.
928
929=head1 USAGE
930
931As a subclass of I<MT::Object>, I<MT::Blog> inherits all of the
932data-management and -storage methods from that class; thus you should look
933at the I<MT::Object> documentation for details about creating a new object,
934loading an existing object, saving an object, etc.
935
936The following methods are unique to the I<MT::Blog> interface:
937
938=head2 $blog->file_mgr
939
940Returns the I<MT::FileMgr> object specific to this particular blog.
941
942=head1 DATA ACCESS METHODS
943
944The I<MT::Blog> object holds the following pieces of data. These fields can
945be accessed and set using the standard data access methods described in the
946I<MT::Object> documentation.
947
948=over 4
949
950=item * id
951
952The numeric ID of the blog.
953
954=item * name
955
956The name of the blog.
957
958=item * description
959
960The blog description.
961
962=item * site_path
963
964The path to the directory containing the blog's output index templates.
965
966=item * site_url
967
968The URL corresponding to the I<site_path>.
969
970=item * archive_path
971
972The path to the directory where the blog's archives are stored.
973
974=item * archive_url
975
976The URL corresponding to the I<archive_path>.
977
978=item * server_offset
979
980A slight misnomer, this is actually the timezone that the B<user> has
981selected; the value is the offset from GMT.
982
983=item * archive_type
984
985A comma-separated list of archive types used in this particular blog, where
986an archive type is one of the following: C<Individual>, C<Daily>, C<Weekly>,
987C<Monthly>, or C<Category>. For example, a blog's I<archive_type> would be
988C<Individual,Monthly> if the blog were using C<Individual> and C<Monthly>
989archives.
990
991=item * archive_type_preferred
992
993The "preferred" archive type, which is used when constructing a link to the
994archive page for a particular archive--if multiple archive types are selected,
995for example, the link can only point to one of those archives. The preferred
996archive type (which should be one of the archive types set in I<archive_type>,
997above) specifies to which archive this link should point (among other things).
998
999=item * days_on_index
1000
1001The number of days to be displayed on the index.
1002
1003=item * file_extension
1004
1005The file extension to be used for archive pages.
1006
1007=item * email_new_comments
1008
1009A boolean flag specifying whether authors should be notified of new comments
1010posted on entries they have written.
1011
1012=item * allow_comment_html
1013
1014A boolean flag specifying whether HTML should be allowed in comments. If it
1015is not allowed, it is automatically stripped before building the page (note
1016that the content stored in the database is B<not> stripped).
1017
1018=item * autolink_urls
1019
1020A boolean flag specifying whether URLs in comments should be turned into
1021links. Note that this setting is only taken into account if
1022I<allow_comment_html> is turned off.
1023
1024=item * sort_order_posts
1025
1026The default sort order for entries. Valid values are either C<ascend> or
1027C<descend>.
1028
1029=item * sort_order_comments
1030
1031The default sort order for comments. Valid values are either C<ascend> or
1032C<descend>.
1033
1034=item * allow_comments_default
1035
1036The default value for the I<allow_comments> field in the I<MT::Entry> object.
1037
1038=item * convert_paras
1039
1040A comma-separated list of text filters to apply to each entry when it
1041is built.
1042
1043=item * convert_paras_comments
1044
1045A comma-separated list of text filters to apply to each comment when it
1046is built.
1047
1048=item * status_default
1049
1050The default value for the I<status> field in the I<MT::Entry> object.
1051
1052=item * allow_anon_comments
1053
1054A boolean flag specifying whether anonymous comments (those posted without
1055a name or an email address) are allowed.
1056
1057=item * allow_unreg_comments
1058
1059A boolean flag specifying whether unregistered comments (those posted
1060without a validated email/password pair) are allowed.
1061
1062=item * words_in_excerpt
1063
1064The number of words in an auto-generated excerpt.
1065
1066=item * ping_weblogs
1067
1068A boolean flag specifying whether the system should send an XML-RPC ping to
1069I<weblogs.com> after an entry is saved.
1070
1071=item * mt_update_key
1072
1073The Movable Type Recently Updated Key to be sent to I<movabletype.org> after
1074an entry is saved.
1075
1076=item * language
1077
1078The language for date and time display for this particular blog.
1079
1080=item * welcome_msg
1081
1082The welcome message to be displayed on the main Editing Menu for this blog.
1083Should contain all desired HTML formatting.
1084
1085=back
1086
1087=head1 METHODS
1088
1089=over 4
1090
1091=item * clone( [ \%parameters ] )
1092
1093MT::Blog provides a clone method that supports cloning of all known child
1094records related to the MT::Blog object. To invoke this behavior, you
1095simply specify a parameter hash with a 'Children' key set.
1096
1097    # Clones blog and all data related to this blog within the database.
1098    my $new_blog = $original_blog->clone({ Children => 1 });
1099
1100You may further specify what kind of records are cloned in the process
1101of cloning child objects. Use the 'Classes' parameter to specifically
1102exclude particular classes:
1103
1104    # Clones everything except comments and trackback pings
1105    my $new_blog = $original_blog->clone({
1106        Children => 1,
1107        Classes => { 'MT::Comments' => 0, 'MT::TBPing' => 0 }
1108    });
1109
1110Note: Certain exclusions will prevent the clone process from including
1111other classes. For instance, if you exclude MT::Trackback, all MT::TBPing
1112objects are automatically excluded.
1113
1114=back
1115
1116=head1 DATA LOOKUP
1117
1118In addition to numeric ID lookup, you can look up or sort records by any
1119combination of the following fields. See the I<load> documentation in
1120I<MT::Object> for more information.
1121
1122=over 4
1123
1124=item * name
1125
1126=back
1127
1128=head1 NOTES
1129
1130=over 4
1131
1132=item *
1133
1134When you remove a blog using I<MT::Blog::remove>, in addition to removing the
1135blog record, all of the entries, notifications, permissions, comments,
1136templates, and categories in that blog will also be removed.
1137
1138=item *
1139
1140Because the system needs to load I<MT::Blog> objects from disk relatively
1141often during the duration of one request, I<MT::Blog> objects are cached by
1142the I<MT::Blog::load> object so that each blog only need be loaded once. The
1143I<MT::Blog> objects are cached in the I<MT::Request> singleton object; note
1144that this caching B<only occurs> if the blogs are loaded by numeric ID.
1145
1146=back
1147
1148=head1 AUTHOR & COPYRIGHTS
1149
1150Please see the I<MT> manpage for author, copyright, and license information.
1151
1152=cut
Note: See TracBrowser for help on using the browser.