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

Revision 1927, 38.4 kB (checked in by mpaschal, 20 months ago)

Land the new implementation of metadata based on narrow tables
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::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' => 'integer 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 ($name) = @_;
382
383    my $filestem = MT::Util::dirify($name);
384    my $filename = join q{.}, $filestem, $blog->file_extension;
385    return (MT->config('IncludesDir'), substr($filestem, 0, 3), $filename);
386}
387
388sub include_path {
389    my $blog = shift;
390    my ($name) = @_;
391
392    my @parts = $blog->include_path_parts($name);
393    my $filename = pop @parts;
394    my $path = File::Spec->catdir($blog->site_path, @parts);
395    my $file_path = File::Spec->catfile($path, $filename);
396    return wantarray ? ($path, $file_path) : $file_path;
397}
398
399sub include_url {
400    my $blog = shift;
401    my ($name) = @_;
402
403    my @parts = $blog->include_path_parts();
404    my $url = join q{/}, $blog->site_url, @parts;
405    return $url;
406}
407
408sub include_statement {
409    my $blog = shift;
410    my ($name) = @_;
411
412    my $system = $blog->include_system or return;
413
414    my ($statement, $include);
415    if ($system eq 'shtml') {
416        $statement = q{<!--#include virtual="%s" -->};
417
418        my $site_url = $blog->site_url;
419        $site_url =~ s{ \A \w+ :// [^/]+ }{}xms;
420        $site_url =~ s{ / \z }{}xms;
421        $include = join q{/}, $site_url, $blog->include_path_parts($name);
422    }
423    else {
424        $include = $blog->include_path($name);
425        $statement = $system eq 'php'   ? q{<?php include("%s") ?>}
426                   : $system eq 'jsp'   ? q{<%@ include file="%s" %>}
427                   : $system eq 'asp'   ? '<!--#include file="%s" -->'
428                   :                      return
429                   ;
430    }
431    return sprintf $statement, MT::Util::encode_php($include, q{qq});
432}
433
434sub file_mgr {
435    my $blog = shift;
436    unless (exists $blog->{__file_mgr}) {
437## xxx need to add remote_host, remote_user, remote_pwd fields
438## then pull params from there; if remote_host is defined, we
439## assume we are using FTP?
440        $blog->{__file_mgr} = MT::FileMgr->new('Local');
441    }
442    $blog->{__file_mgr};
443}
444
445sub remove {
446    my $blog = shift;
447    $blog->remove_children({ key => 'blog_id'});
448    my $res = $blog->SUPER::remove(@_);
449    if ((ref $blog) && $res) {
450        require MT::Permission;
451        MT::Permission->remove({ blog_id => $blog->id });
452    }
453    $res;
454}
455
456# deprecated: use $blog->remote_auth_token instead
457sub effective_remote_auth_token {
458    my $blog = shift;
459    if (scalar @_) {
460        return $blog->remote_auth_token(@_);
461    }
462    if ($blog->remote_auth_token()) {
463        return $blog->remote_auth_token();
464    }
465    undef;
466}
467
468sub has_archive_type {
469    my $blog = shift;
470    my ($type) = @_;
471    my %at = map { lc $_ => 1 } split(/,/, $blog->archive_type);
472    return exists $at{lc $type} ? 1 : 0;
473}
474
475sub accepts_registered_comments {
476    $_[0]->allow_reg_comments && $_[0]->commenter_authenticators;
477}
478
479sub accepts_comments {
480    $_[0]->accepts_registered_comments || $_[0]->allow_unreg_comments;
481}
482
483sub count_static_templates {
484    my $blog = shift;
485    my ($archive_type) = @_;
486    my $result = 0;
487    require MT::TemplateMap;
488    my @maps = MT::TemplateMap->load({blog_id => $blog->id,
489                                      archive_type => $archive_type});
490    return 0 unless @maps;
491    require MT::Template;
492    foreach my $map (@maps) { 
493        my $tmpl = MT::Template->load($map->template_id)
494            or return 0;
495        $result++ if !$tmpl->build_dynamic;
496    }
497    #$result ||= 1 if ($blog->custom_dynamic_templates || '') ne 'custom';
498    return $result;
499}
500
501sub touch {
502    my $blog = shift;
503    my ( @types ) = @_;
504    my ($s,$m,$h,$d,$mo,$y) = localtime(time);
505    my $mod_time = sprintf("%04d%02d%02d%02d%02d%02d",
506                           1900+$y, $mo+1, $d, $h, $m, $s);
507    require MT::Touch;
508    MT::Touch->touch( $blog->id, @types );
509    $blog->children_modified_on($mod_time);
510    $mod_time;
511}
512
513sub clone {
514    my $blog = shift;
515    my ($param) = @_;
516    if ($param && $param->{Children}) {
517        $blog->clone_with_children(@_);
518    } else {
519        $blog->SUPER::clone(@_);
520    }
521}
522
523sub clone_with_children {
524    my $blog = shift;
525    my ($params) = @_;
526    my $callback = $params->{Callback} || sub {};
527    my $classes = $params->{Classes};
528    my $blog_name = $params->{BlogName};
529    delete $$params{Children} if ($params->{Children});
530    my $old_blog_id = $blog->id;
531
532    # we must clone:
533    #    Blog record
534    #    Entry records
535    #       - Comment records
536    #       - TrackBack records
537    #       - TBPing records
538    #       - ObjectTag records (if running 3.3)
539    #    Category records
540    #    Placement records
541    #    Template records
542    #    Permission records
543    #    IPBanList records???
544    #    Notification records???
545
546    my $new_blog_id;
547    my (%entry_map, %cat_map, %tb_map, %tmpl_map, $counter, $iter);
548
549    # Cloning blog
550    my $new_blog = $blog->clone($params);
551    $new_blog->name(MT->translate($blog_name ? $blog_name : "Clone of [_1]", $blog->name));
552    delete $new_blog->{column_values}->{id};
553    delete $new_blog->{changed_cols}->{id};
554    $new_blog->save or die $new_blog->errstr;
555    $new_blog_id = $new_blog->id;
556    $callback->(MT->translate("Cloned blog... new id is [_1].",
557        $new_blog_id));
558
559    if ((!exists $classes->{'MT::Permission'}) || $classes->{'MT::Permission'}) {
560        # Cloning PERMISSIONS records
561        $counter = 0;
562        my $state = MT->translate("Cloning permissions for blog:");
563        $callback->($state, "perms");
564        require MT::Permission;
565        $iter = MT::Permission->load_iter({blog_id => $old_blog_id});
566        while (my $perm = $iter->()) {
567            $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'perms')
568                if $counter && ($counter % 100 == 0);
569            $counter++;
570            delete $perm->{column_values}->{id};
571            delete $perm->{changed_cols}->{id};
572            $perm->blog_id($new_blog_id);
573            $perm->save or die $perm->errstr;
574        }
575        $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'perms');
576    }
577
578    if ((!exists $classes->{'MT::Association'}) || $classes->{'MT::Association'}) {
579        # Cloning association records
580        $counter = 0;
581        my $state = MT->translate("Cloning associations for blog:");
582        $callback->($state, "assoc");
583        require MT::Association;
584        $iter = MT::Association->load_iter({blog_id => $old_blog_id});
585        while (my $assoc = $iter->()) {
586            $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'assoc')
587                if $counter && ($counter % 100 == 0);
588            $counter++;
589            delete $assoc->{column_values}->{id};
590            delete $assoc->{changed_cols}->{id};
591            $assoc->blog_id($new_blog_id);
592            $assoc->save or die $assoc->errstr;
593        }
594        $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'assoc');
595    }
596
597    # include/exclude class logic
598    # if user has not specified 'Classes' element, clone everything
599    # if user has specified Classes, but a particular class is not
600    # identified, clone it (forward compatibility). if a class is
601    # specified and the flag is '1', clone it. if a class is specified
602    # but the flag is '0', skip it.
603
604    # MT::Entry -> MT::Category, MT::Comment, MT::Tracback, MT::TBPing
605    # MT::Page -> MT::Folder, MT::Comment, MT::Trackback, MT::TBPing
606
607    if ((!exists $classes->{'MT::Entry'}) || $classes->{'MT::Entry'}) {
608        # Cloning ENTRY records
609        my $state = MT->translate("Cloning entries for blog...");
610        $callback->($state, "entries");
611        $counter = 0;
612        require MT::Entry;
613        $iter = MT::Entry->load_iter({blog_id => $old_blog_id, class => '*'});
614        while (my $entry = $iter->()) {
615            $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'entries')
616                if $counter && ($counter % 100 == 0);
617            $counter++;
618            my $entry_id = $entry->id;
619            delete $entry->{column_values}->{id};
620            delete $entry->{changed_cols}->{id};
621            $entry->blog_id($new_blog_id);
622            $entry->save or die $entry->errstr;
623            $entry_map{$entry_id} = $entry->id;
624        }
625        $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'entries');
626
627        if ((!exists $classes->{'MT::Category'}) || $classes->{'MT::Category'}) {
628            # Cloning CATEGORY records
629            my $state = MT->translate("Cloning categories for blog...");
630            $callback->($state, "cats");
631            $counter = 0;
632            require MT::Category;
633            $iter = MT::Category->load_iter({ blog_id => $old_blog_id, class => '*' });
634            my %cat_parents;
635            while (my $cat = $iter->()) {
636                $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'cats')
637                    if $counter && ($counter % 100 == 0);
638                $counter++;
639                my $cat_id = $cat->id;
640                my $old_parent = $cat->parent;
641                delete $cat->{column_values}->{id};
642                delete $cat->{changed_cols}->{id};
643                $cat->blog_id($new_blog_id);
644                # temporarily wipe the parent association
645                # to avoid constraint issues.
646                $cat->parent(0);
647                $cat->save or die $cat->errstr;
648                $cat_map{$cat_id} = $cat->id;
649                if ($old_parent) {
650                    $cat_parents{$cat->id} = $old_parent;
651                }
652            }
653            # reassign the new category parents
654            foreach (keys %cat_parents) {
655                my $cat = MT::Category->load($_);
656                if ($cat) {
657                    $cat->parent($cat_map{$cat_parents{$cat->id}});
658                    $cat->save or die $cat->errstr;
659                }
660            }
661            $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'cats');
662
663            # Placements are automatically cloned if categories are
664            # cloned.
665            $state = MT->translate("Cloning entry placements for blog...");
666            $callback->($state, "places");
667            require MT::Placement;
668            $iter = MT::Placement->load_iter({ blog_id => $old_blog_id });
669            $counter = 0;
670            while (my $place = $iter->()) {
671                $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'places')
672                    if $counter && ($counter % 100 == 0);
673                $counter++;
674                delete $place->{column_values}->{id};
675                delete $place->{changed_cols}->{id};
676                $place->blog_id($new_blog_id);
677                $place->category_id($cat_map{$place->category_id});
678                $place->entry_id($entry_map{$place->entry_id});
679                $place->save or die $place->errstr;
680            }
681            $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'places');
682        }
683
684        if ((!exists $classes->{'MT::Comment'}) || $classes->{'MT::Comment'}) {
685            # Comments can only be cloned if entries are cloned.
686            my $state = MT->translate("Cloning comments for blog...");
687            $callback->($state, "comments");
688            require MT::Comment;
689            $iter = MT::Comment->load_iter({ blog_id => $old_blog_id });
690            $counter = 0;
691            while (my $comment = $iter->()) {
692                $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'comments')
693                    if $counter && ($counter % 100 == 0);
694                $counter++;
695                delete $comment->{column_values}->{id};
696                delete $comment->{changed_cols}->{id};
697                $comment->entry_id($entry_map{$comment->entry_id});
698                $comment->blog_id($new_blog_id);
699                $comment->save or die $comment->errstr;
700            }
701            $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'comments');
702        }
703
704        if ((!exists $classes->{'MT::ObjectTag'}) || $classes->{'MT::ObjectTag'}) {
705            # conditionally do MT::ObjectTag since it is only
706            # available with MT 3.3.
707            if ($MT::VERSION >= 3.3) {
708                my $state = MT->translate("Cloning entry tags for blog...");
709                $callback->($state, "tags");
710                require MT::ObjectTag;
711                $iter = MT::ObjectTag->load_iter({ blog_id => $old_blog_id, object_datasource => 'entry' });
712                $counter = 0;
713                while (my $entry_tag = $iter->()) {
714                    $callback->($state . " " . MT->translate("[_1] records processed...", $counter), "tags")
715                        if $counter && ($counter % 100 == 0);
716                    $counter++;
717                    delete $entry_tag->{column_values}->{id};
718                    delete $entry_tag->{changed_cols}->{id};
719                    $entry_tag->blog_id($new_blog_id);
720                    $entry_tag->object_id($entry_map{$entry_tag->object_id});
721                    $entry_tag->save or die $entry_tag->errstr;
722                }
723                $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'tags');
724            }
725        }
726    }
727
728    if ((!exists $classes->{'MT::Trackback'}) || $classes->{'MT::Trackback'}) {
729        my $state = MT->translate("Cloning TrackBacks for blog...");
730        $callback->($state, "tbs");
731        require MT::Trackback;
732        $iter = MT::Trackback->load_iter({ blog_id => $old_blog_id });
733        $counter = 0;
734        while (my $tb = $iter->()) {
735            next unless ($tb->entry_id && $entry_map{$tb->entry_id}) ||
736                ($tb->category_id && $cat_map{$tb->category_id});
737
738            $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'tbs')
739                if $counter && ($counter % 100 == 0);
740            $counter++;
741            my $tb_id = $tb->id;
742            delete $tb->{column_values}->{id};
743            delete $tb->{changed_cols}->{id};
744
745            if ($tb->category_id) {
746                if (my $cid = $cat_map{$tb->category_id}) {
747                    my $cat_tb = MT::Trackback->load(
748                        { category_id => $cid }
749                    );
750                    if ($cat_tb) {
751                        my $changed;
752                        if ($tb->passphrase) {
753                            $cat_tb->passphrase($tb->passphrase);
754                            $changed = 1;
755                        }
756                        if ($tb->is_disabled) {
757                            $cat_tb->is_disabled(1);
758                            $changed = 1;
759                        }
760                        $cat_tb->save if $changed;
761                        $tb_map{$tb_id} = $cat_tb->id;
762                        next;
763                    }
764                }
765            }
766            elsif ($tb->entry_id) {
767                if (my $eid = $entry_map{$tb->entry_id}) {
768                    my $entry_tb = MT::Entry->load($eid)->trackback;
769                    if ($entry_tb) {
770                        my $changed;
771                        if ($tb->passphrase) {
772                            $entry_tb->passphrase($tb->passphrase);
773                            $changed = 1;
774                        }
775                        if ($tb->is_disabled) {
776                            $entry_tb->is_disabled(1);
777                            $changed = 1;
778                        }
779                        $entry_tb->save if $changed;
780                        $tb_map{$tb_id} = $entry_tb->id;
781                        next;
782                    }
783                }
784            }
785
786            # A trackback wasn't created when saving the entry/category,
787            # (perhaps trackbacks are now disabled for the entry/category?)
788            # so create one now
789            $tb->entry_id($entry_map{$tb->entry_id})
790                if $tb->entry_id && $entry_map{$tb->entry_id};
791            $tb->category_id($cat_map{$tb->category_id})
792                if $tb->category_id && $cat_map{$tb->category_id};
793            $tb->blog_id($new_blog_id);
794            $tb->save or die $tb->errstr;
795            $tb_map{$tb_id} = $tb->id;
796        }
797        $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'tbs');
798
799        if ((!exists $classes->{'MT::TBPing'}) || $classes->{'MT::TBPing'}) {
800            my $state = MT->translate("Cloning TrackBack pings for blog...");
801            $callback->($state, "pings");
802            require MT::TBPing;
803            $iter = MT::TBPing->load_iter({ blog_id => $old_blog_id });
804            $counter = 0;
805            while (my $ping = $iter->()) {
806                next unless $tb_map{$ping->tb_id};
807                $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'pings')
808                    if $counter && ($counter % 100 == 0);
809                $counter++;
810                delete $ping->{column_values}->{id};
811                delete $ping->{changed_cols}->{id};
812                $ping->tb_id($tb_map{$ping->tb_id});
813                $ping->blog_id($new_blog_id);
814                $ping->save or die $ping->errstr;
815            }
816            $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'pings');
817        }
818    }
819
820    if ((!exists $classes->{'MT::Template'}) || $classes->{'MT::Template'}) {
821        my $state = MT->translate("Cloning templates for blog...");
822        $callback->($state, "tmpls");
823        require MT::Template;
824        $iter = MT::Template->load_iter({ blog_id => $old_blog_id });
825        $counter = 0;
826        while (my $tmpl = $iter->()) {
827            $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'tmpls')
828                if $counter && ($counter % 100 == 0);
829            my $tmpl_id = $tmpl->id;
830            $counter++;
831            delete $tmpl->{column_values}->{id};
832            delete $tmpl->{changed_cols}->{id};
833            # linked_file won't be cloned for now because
834            # new blog does not have site_path - breaks relative path
835            delete $tmpl->{column_values}->{linked_file};
836            delete $tmpl->{column_values}->{linked_file_mtime};
837            delete $tmpl->{column_values}->{linked_file_size};
838            $tmpl->blog_id($new_blog_id);
839            $tmpl->save or die $tmpl->errstr;
840            $tmpl_map{$tmpl_id} = $tmpl->id;
841        }
842        $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'tmpls');
843
844        $state = MT->translate("Cloning template maps for blog...");
845        $callback->($state, "tmplmaps");
846        require MT::TemplateMap;
847        $iter = MT::TemplateMap->load_iter({ blog_id => $old_blog_id });
848        $counter = 0;
849        while (my $map = $iter->()) {
850            $callback->($state . " " . MT->translate("[_1] records processed...", $counter), 'tmplmaps')
851                if $counter && ($counter % 100 == 0);
852            $counter++;
853            delete $map->{column_values}->{id};
854            delete $map->{changed_cols}->{id};
855            $map->template_id($tmpl_map{$map->template_id});
856            $map->blog_id($new_blog_id);
857            $map->save or die $map->errstr;
858        }
859        $callback->($state . " " . MT->translate("[_1] records processed.", $counter), 'tmplmaps');
860    }
861
862    MT->run_callbacks(ref($blog). '::post_clone',
863        OldBlogId => $blog->id, old_blog_id => $blog->id,
864        NewBlogId => $new_blog->id, new_blog_id => $new_blog->id,
865        OldObject => $blog, old_object => $blog,
866        NewObject => $new_blog, new_object => $new_blog,
867        EntryMap => \%entry_map, entry_map => \%entry_map,
868        CategoryMap => \%cat_map, category_map => \%cat_map,
869        TrackbackMap => \%tb_map, trackback_map => \%tb_map,
870        TemplateMap => \%tmpl_map, template_map => \%tmpl_map,
871        Callback => $callback, callback => $callback,
872    );
873    $new_blog;
874}
875
876sub smart_replace {
877    my $blog = shift;
878    if (@_) {
879        $blog->nwc_smart_replace(@_);
880        return;
881    }
882    my $val = $blog->nwc_smart_replace;
883    return defined($val) ? $val : MT->config->NwcSmartReplace;
884}
885
886sub smart_replace_fields {
887    my $blog = shift;
888    if (@_) {
889        $blog->nwc_replace_field(@_);
890        return;
891    }
892    my $val = $blog->nwc_replace_field;
893    return defined($val) ? $val : MT->config->NwcReplaceField;
894}
895
896#trans('blog')
897#trans('blogs')
898
8991;
900__END__
901
902=head1 NAME
903
904MT::Blog - Movable Type blog record
905
906=head1 SYNOPSIS
907
908    use MT::Blog;
909    my $blog = MT::Blog->load($blog_id);
910    $blog->name('Some new name');
911    $blog->save
912        or die $blog->errstr;
913
914=head1 DESCRIPTION
915
916An I<MT::Blog> object represents a blog in the Movable Type system. It
917contains all of the settings, preferences, and configuration for a particular
918blog. It does not contain any per-author permissions settings--for those,
919look at the I<MT::Permission> object.
920
921=head1 USAGE
922
923As a subclass of I<MT::Object>, I<MT::Blog> inherits all of the
924data-management and -storage methods from that class; thus you should look
925at the I<MT::Object> documentation for details about creating a new object,
926loading an existing object, saving an object, etc.
927
928The following methods are unique to the I<MT::Blog> interface:
929
930=head2 $blog->file_mgr
931
932Returns the I<MT::FileMgr> object specific to this particular blog.
933
934=head1 DATA ACCESS METHODS
935
936The I<MT::Blog> object holds the following pieces of data. These fields can
937be accessed and set using the standard data access methods described in the
938I<MT::Object> documentation.
939
940=over 4
941
942=item * id
943
944The numeric ID of the blog.
945
946=item * name
947
948The name of the blog.
949
950=item * description
951
952The blog description.
953
954=item * site_path
955
956The path to the directory containing the blog's output index templates.
957
958=item * site_url
959
960The URL corresponding to the I<site_path>.
961
962=item * archive_path
963
964The path to the directory where the blog's archives are stored.
965
966=item * archive_url
967
968The URL corresponding to the I<archive_path>.
969
970=item * server_offset
971
972A slight misnomer, this is actually the timezone that the B<user> has
973selected; the value is the offset from GMT.
974
975=item * archive_type
976
977A comma-separated list of archive types used in this particular blog, where
978an archive type is one of the following: C<Individual>, C<Daily>, C<Weekly>,
979C<Monthly>, or C<Category>. For example, a blog's I<archive_type> would be
980C<Individual,Monthly> if the blog were using C<Individual> and C<Monthly>
981archives.
982
983=item * archive_type_preferred
984
985The "preferred" archive type, which is used when constructing a link to the
986archive page for a particular archive--if multiple archive types are selected,
987for example, the link can only point to one of those archives. The preferred
988archive type (which should be one of the archive types set in I<archive_type>,
989above) specifies to which archive this link should point (among other things).
990
991=item * days_on_index
992
993The number of days to be displayed on the index.
994
995=item * file_extension
996
997The file extension to be used for archive pages.
998
999=item * email_new_comments
1000
1001A boolean flag specifying whether authors should be notified of new comments
1002posted on entries they have written.
1003
1004=item * allow_comment_html
1005
1006A boolean flag specifying whether HTML should be allowed in comments. If it
1007is not allowed, it is automatically stripped before building the page (note
1008that the content stored in the database is B<not> stripped).
1009
1010=item * autolink_urls
1011
1012A boolean flag specifying whether URLs in comments should be turned into
1013links. Note that this setting is only taken into account if
1014I<allow_comment_html> is turned off.
1015
1016=item * sort_order_posts
1017
1018The default sort order for entries. Valid values are either C<ascend> or
1019C<descend>.
1020
1021=item * sort_order_comments
1022
1023The default sort order for comments. Valid values are either C<ascend> or
1024C<descend>.
1025
1026=item * allow_comments_default
1027
1028The default value for the I<allow_comments> field in the I<MT::Entry> object.
1029
1030=item * convert_paras
1031
1032A comma-separated list of text filters to apply to each entry when it
1033is built.
1034
1035=item * convert_paras_comments
1036
1037A comma-separated list of text filters to apply to each comment when it
1038is built.
1039
1040=item * status_default
1041
1042The default value for the I<status> field in the I<MT::Entry> object.
1043
1044=item * allow_anon_comments
1045
1046A boolean flag specifying whether anonymous comments (those posted without
1047a name or an email address) are allowed.
1048
1049=item * allow_unreg_comments
1050
1051A boolean flag specifying whether unregistered comments (those posted
1052without a validated email/password pair) are allowed.
1053
1054=item * words_in_excerpt
1055
1056The number of words in an auto-generated excerpt.
1057
1058=item * ping_weblogs
1059
1060A boolean flag specifying whether the system should send an XML-RPC ping to
1061I<weblogs.com> after an entry is saved.
1062
1063=item * mt_update_key
1064
1065The Movable Type Recently Updated Key to be sent to I<movabletype.org> after
1066an entry is saved.
1067
1068=item * language
1069
1070The language for date and time display for this particular blog.
1071
1072=item * welcome_msg
1073
1074The welcome message to be displayed on the main Editing Menu for this blog.
1075Should contain all desired HTML formatting.
1076
1077=back
1078
1079=head1 METHODS
1080
1081=over 4
1082
1083=item * clone( [ \%parameters ] )
1084
1085MT::Blog provides a clone method that supports cloning of all known child
1086records related to the MT::Blog object. To invoke this behavior, you
1087simply specify a parameter hash with a 'Children' key set.
1088
1089    # Clones blog and all data related to this blog within the database.
1090    my $new_blog = $original_blog->clone({ Children => 1 });
1091
1092You may further specify what kind of records are cloned in the process
1093of cloning child objects. Use the 'Classes' parameter to specifically
1094exclude particular classes:
1095
1096    # Clones everything except comments and trackback pings
1097    my $new_blog = $original_blog->clone({
1098        Children => 1,
1099        Classes => { 'MT::Comments' => 0, 'MT::TBPing' => 0 }
1100    });
1101
1102Note: Certain exclusions will prevent the clone process from including
1103other classes. For instance, if you exclude MT::Trackback, all MT::TBPing
1104objects are automatically excluded.
1105
1106=back
1107
1108=head1 DATA LOOKUP
1109
1110In addition to numeric ID lookup, you can look up or sort records by any
1111combination of the following fields. See the I<load> documentation in
1112I<MT::Object> for more information.
1113
1114=over 4
1115
1116=item * name
1117
1118=back
1119
1120=head1 NOTES
1121
1122=over 4
1123
1124=item *
1125
1126When you remove a blog using I<MT::Blog::remove>, in addition to removing the
1127blog record, all of the entries, notifications, permissions, comments,
1128templates, and categories in that blog will also be removed.
1129
1130=item *
1131
1132Because the system needs to load I<MT::Blog> objects from disk relatively
1133often during the duration of one request, I<MT::Blog> objects are cached by
1134the I<MT::Blog::load> object so that each blog only need be loaded once. The
1135I<MT::Blog> objects are cached in the I<MT::Request> singleton object; note
1136that this caching B<only occurs> if the blogs are loaded by numeric ID.
1137
1138=back
1139
1140=head1 AUTHOR & COPYRIGHTS
1141
1142Please see the I<MT> manpage for author, copyright, and license information.
1143
1144=cut
Note: See TracBrowser for help on using the browser.