root/branches/release-40/lib/MT/Blog.pm @ 2559

Revision 2559, 39.6 kB (checked in by bchoate, 18 months ago)

Fixed meta declaration for image_default_wunits metadata field. BugId:80112

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