root/branches/release-34/lib/MT/Blog.pm @ 1868

Revision 1868, 38.0 kB (checked in by bchoate, 20 months ago)

High-def basenames. BugId:79282

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