root/branches/release-32/lib/MT/Blog.pm @ 1662

Revision 1662, 38.2 kB (checked in by bchoate, 20 months ago)

New package for recording last modification times per blog, per type to aid caching mechanism. BugId:74211,74210

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