File Coverage

blib/lib/MT/Import/Base.pm
Criterion Covered Total %
statement 21 263 7.9
branch 0 58 0.0
condition 0 8 0.0
subroutine 7 24 29.1
pod 12 16 75.0
total 40 369 10.8


line stmt bran cond sub pod time code
1             # -*-cperl-*-
2              
3 1     1   1569 use strict;
  1         2  
  1         69  
4             package MT::Import::Base;
5              
6             $MT::Import::Base::VERSION = '1.01';
7              
8             =head1 NAME
9              
10             MT::Import::Base - base class for importing "stuff" into Movable Type.
11              
12             =head1 SYNOPSIS
13              
14             package MT::Import::Fubar;
15             use base qw (MT::Import::Fubar);
16              
17             =head1 DESCRIPTION
18              
19             Base class for importing "stuff" into Movable Type.
20              
21             =cut
22              
23 1     1   1241 use Date::Parse;
  1         10014  
  1         154  
24 1     1   1236 use Date::Format;
  1         3407  
  1         85  
25              
26 1     1   9 use File::Path;
  1         2  
  1         60  
27 1     1   6 use File::Spec;
  1         1  
  1         27  
28              
29 1     1   1234 use Log::Dispatch;
  1         31489  
  1         33  
30 1     1   1050 use Log::Dispatch::Screen;
  1         2757  
  1         3811  
31              
32             =head1 PACKAGE METHODS
33              
34             =cut
35              
36             =head2 __PACKAGE__->new($cfg)
37              
38             Options are passed to MT::Import::Base using a Config::Simple object
39             or a valid Config::Simple config file. Options are grouped by "block".
40              
41             =head2 importer
42              
43             =over 4
44              
45             =item * B
46              
47             Boolean.
48              
49             Enable verbose logging for both this package and I
50              
51             =item * B
52              
53             Boolean.
54              
55             Force an entry to be reindexed, including any trackback pings and
56             attachments.
57              
58             Default is I
59              
60             =back
61              
62             =head2 mt
63              
64             =over 4
65              
66             =item * B
67              
68             String. I
69              
70             The path to your Movable Type installation.
71              
72             =item * B
73              
74             Int. I
75              
76             The numberic ID of the Movable Type weblog you are posting to.
77              
78             =item * B
79              
80             Int. I
81              
82             The numberic ID of a Movable Type author with permissions to add
83             new authors to the Movable Type weblog you are posting to.
84              
85             =item * B
86              
87             String.
88              
89             The password to assign to any new authors you add to the Movable Type
90             weblog you are posting to.
91              
92             Default is "I".
93              
94             =item * B
95              
96             Int.
97              
98             The permissions set to grant any new authors you add to the Movable Type
99             weblog you are posting to.
100              
101             Default is I<514>, or the ability to add new categories.
102              
103             =back
104              
105             =cut
106              
107             sub new {
108 0     0 1   my $pkg = shift;
109            
110 0           my $self = bless {}, $pkg;
111              
112 0 0         if (! $self->init(@_)) {
113 0           return undef;
114             }
115              
116 0           return $self;
117             }
118              
119             sub init {
120 0     0 0   my $self = shift;
121 0           my $opts = shift;
122              
123 0 0         if (UNIVERSAL::isa($opts,"Config::Simple")) {
124 0           $self->{cfg} = $opts;
125             }
126            
127             else {
128 0           my $cfg = undef;
129            
130 0           eval {
131 0           $cfg = Config::Simple->new($opts);
132             };
133            
134 0 0         if ($@) {
135 0           warn "$opts : $@";
136 0           return 0;
137             }
138              
139 0           $self->{cfg} = $cfg;
140             }
141            
142             #
143            
144 0           my $root = $self->{cfg}->param("mt.root");
145              
146 0 0         if (! -d $root) {
147 0           warn "MT root ($root) is not a directory";
148 0           return 0;
149             }
150            
151 0           my $blog_id = $self->{cfg}->param("mt.blog_id");
152              
153 0 0 0       if (($blog_id !~ /^\d+$/) || ($blog_id < 1)) {
154 0           warn "Blog ID ($blog_id) is not a positive integer";
155 0           return 0;
156             }
157              
158 0           my $blog_owner = $self->{cfg}->param("mt.blog_ownerid");
159              
160 0 0 0       if (($blog_owner !~ /^\d+$/) || ($blog_owner < 1)) {
161 0           warn "Blog owner ID ($blog_owner) is not a positive integer";
162 0           return 0;
163             }
164              
165             #
166              
167 0           push @INC, File::Spec->catdir($root,"lib");
168 0           push @INC, File::Spec->catdir($root,"extlib");
169              
170 0           eval {
171 0           require MT;
172 0           require MT::App::CMS;
173            
174 0           require MT::Entry;
175 0           require MT::Author;
176 0           require MT::Category;
177 0           require MT::Permission;
178 0           require MT::Placement;
179 0           require MT::TBPing;
180 0           require MT::Trackback;
181            
182 0           MT::Author->import(":constants");
183             };
184            
185 0 0         if ($@) {
186 0           warn "Failed to load a MT dependency, $@";
187 0           return 0;
188             }
189              
190             #
191              
192 0           my $app = MT::App::CMS->new(Config => File::Spec->catfile($root,"mt.cfg"),
193             Directory => $root);
194            
195 0 0         if (! $app) {
196 0           warn "Failed to create MT application object, ".MT::App::CMS->errstr;
197 0           return 0;
198             }
199            
200 0           $self->{'__app'} = $app;
201            
202             #
203              
204             my $log_fmt = sub {
205 0     0     my %args = @_;
206            
207 0           my $msg = $args{'message'};
208 0           chomp $msg;
209            
210 0           my ($ln,$sub) = (caller(4))[2,3];
211 0           $sub =~ s/.*:://;
212            
213 0           return sprintf("[%s][%s, ln%d] %s\n",
214             $args{'level'},$sub,$ln,$msg);
215 0           };
216            
217 0           my $logger = Log::Dispatch->new(callbacks=>$log_fmt);
218 0           my $error = Log::Dispatch::Screen->new(name => '__error',
219             min_level => 'error',
220             stderr => 1);
221              
222 0           $logger->add($error);
223 0           $self->{'__logger'} = $logger;
224              
225 0           $self->verbose($self->{cfg}->param("importer.verbose"));
226              
227             #
228            
229 0           $self->{'__imported'} = [];
230              
231             #
232              
233 0           return 1;
234             }
235              
236             =head1 OBJECT METHODS
237              
238             =cut
239              
240             =head2 $obj->verbose($bool)
241              
242             Returns true or false, indicating whether or not I events
243             would be logged.
244              
245             =cut
246              
247             sub verbose {
248 0     0 1   my $self = shift;
249 0           my $bool = shift;
250              
251             #
252              
253 0 0         if (defined($bool)) {
254              
255 0           $self->log()->remove('__verbose');
256              
257 0 0         if ($bool) {
258 0           my $stdout = Log::Dispatch::Screen->new(name => '__verbose',
259             min_level => 'debug');
260 0           $self->log()->add($stdout);
261             }
262             }
263              
264             #
265              
266 0           return $self->log()->would_log('debug');
267             }
268              
269             =head2 $obj->log()
270              
271             Returns a I object.
272              
273             =cut
274              
275             sub log {
276 0     0 1   my $self = shift;
277 0           return $self->{'__logger'};
278             }
279              
280             =head2 $obj->imported($id)
281              
282             If I<$id> is defined, stores the ID in the object's internal
283             cache of entry's that have been imported.
284              
285             Otherwise, the method returns a list or array reference of
286             imported entries depending on whether or not the method was
287             called in a I context.
288              
289             =cut
290              
291             sub imported {
292 0     0 1   my $self = shift;
293 0           my $id = shift;
294            
295 0 0         if (! $id) {
296 0 0         return (wantarray) ? @{$self->{'__imported'}} : $self->{'__imported'};
  0            
297             }
298            
299 0 0         if (grep /^$id$/,@{$self->{'__imported'}}) {
  0            
300 0           return 1;
301             }
302            
303 0           push @{$self->{'__imported'}},$id;
  0            
304 0           return 1;
305             }
306              
307             =head2 $obj->rebuild()
308              
309             Rebuild all of the entries returned by the object's I
310             method. Indexes are rebuilt afterwards.
311              
312             Returns true or false.
313              
314             =cut
315              
316             sub rebuild {
317 0     0 1   my $self = shift;
318            
319 0           foreach my $id ($self->imported()) {
320 0           $self->rebuild_entry($id);
321             }
322            
323             #
324            
325 0           $self->rebuild_indexes();
326 0           return 1;
327             }
328              
329             =head2 $obj->rebuild_indexes()
330              
331             Rebuild all of the indexes for the blog defined B.
332              
333             Returns true or false.
334              
335             =cut
336              
337             sub rebuild_indexes {
338 0     0 1   my $self = shift;
339 0           $self->{'__app'}->rebuild_indexes(BlogID => $self->blog_id());
340 0           return 1;
341             }
342              
343             =head2 $obj->rebuild_entry($id)
344              
345             Rebuild an individual entry. If the entry has neighbouring entries,
346             they will be added to the object's internal "imported" list.
347              
348             Returns true or false.
349              
350             =cut
351              
352             sub rebuild_entry {
353 0     0 1   my $self = shift;
354 0           my $id = shift;
355            
356 0           my $entry = MT::Entry->load($id);
357 0           my $next = undef;
358 0           my $prev = undef;
359            
360 0 0         if ($next = $entry->next()) {
361 0           $next = $next->id();
362            
363 0           $self->imported($next);
364             }
365            
366 0 0         if ($prev = $entry->previous()) {
367 0           $prev = $prev->id();
368 0           $self->imported($prev);
369             }
370            
371 0           my $ok = $self->{'__app'}->rebuild_entry(Entry => $id,
372             BuildDependencies => 0,
373             OldPrevious => $next,
374             OldNext => $prev);
375              
376             #
377            
378 0 0         if (! $ok) {
379 0           $self->log()->error("failed to rebuild entry '$id', $!");
380 0           return 0;
381             }
382            
383 0           $self->log()->info(sprintf("rebuilt entry %d (%s)\n",$id,$entry->title()));
384 0           return 1;
385             }
386              
387             =head2 $obj->mk_category($label,$parent_id,$author_id)
388              
389             If it does not already exist for the blog defined by B creates
390             a new Movable Type category for I<$label>.
391              
392             I<$parent_id> is the numeric ID for another MT category and is not required.
393              
394             Returns a I object on success or undef if there was
395             an error.
396              
397             =cut
398              
399             sub mk_category {
400 0     0 1   my $self = shift;
401 0           my $label = shift;
402 0           my $parent = shift;
403 0           my $auth_id = shift;
404            
405             #
406            
407 0           $label =~ s/^\s+//;
408 0           $label =~ s/\s+$//;
409            
410 0           $self->log()->debug("make category $label for ($parent)");
411              
412 0           my $cat = MT::Category->load({label => $label,
413             parent => $parent,
414             blog_id => $self->blog_id()});
415              
416 0 0         if (! $cat) {
417 0           $cat = MT::Category->new();
418 0           $cat->blog_id($self->blog_id());
419 0           $cat->label($label);
420 0           $cat->parent($parent);
421 0           $cat->author_id($auth_id);
422            
423 0 0         if (! $cat->save()) {
424 0           $self->log()->error("failed to add category $label ($parent), $!");
425             }
426             }
427              
428 0           $self->log()->debug(sprintf("mk category '%s' (%s:%s)",$cat->label(),$cat->id(),$cat->parent()));
429 0           return $cat;
430             }
431              
432             =head2 $obj->mk_author($name,$email)
433              
434             If it does not already exist for the blog defined by B creates
435             a new Movable Type author for I<$name>.
436              
437             Leading and trailing space will be trimmed from I<$name>.
438              
439             Returns a I object on success or undef if there was
440             an error.
441              
442             =cut
443              
444             sub mk_author {
445 0     0 1   my $self = shift;
446 0           my $name = shift;
447 0           my $email = shift;
448              
449 0           $name =~ s/^\s+//;
450 0           $name =~ s/\s+$//;
451 0           $name = lc($name);
452              
453 0           my $author = MT::Author->load({name => $name,
454             type => &MT::Author::AUTHOR()});
455            
456 0 0         if (! $author) {
457 0           $author = MT::Author->new();
458 0           $author->name($name);
459 0           $author->email($email);
460 0           $author->type(&MT::Author::AUTHOR);
461 0           $author->set_password($self->{cfg}->param("mt.author_password"));
462 0           $author->created_by($self->{cfg}->param("mt.blog_ownerid"));
463 0           $author->type(1);
464            
465 0 0         if (! $author->save()) {
466 0           $self->log()->error("failed to add author $name, $!");
467             }
468             }
469              
470 0           my $pe = MT::Permission->load({blog_id => $self->{cfg}->param("mt.blog_id"),
471             author_id => $author->id()});
472            
473 0 0         if (! $pe) {
474 0           $pe = MT::Permission->new();
475 0           $pe->blog_id($self->{cfg}->param("mt.blog_id"));
476 0           $pe->author_id($author->id());
477 0           $pe->role_mask($self->{cfg}->param("mt.author_permissions"));
478 0           $pe->save();
479             }
480            
481 0           return $author;
482             }
483              
484             =head2 $obj->place_category(MT::Entry, MT::Category, $is_primary)
485              
486             If it does not already exist for the combined entry object and category
487             object creates a new Movable Type placement entry for the pair.
488              
489             Returns a I object on success or undef if there was
490             an error.
491              
492             =cut
493              
494             sub place_category {
495 0     0 0   my $self = shift;
496 0           my $entry = shift;
497 0           my $category = shift;
498 0           my $primary = shift;
499              
500 0   0       $primary ||= 0;
501              
502 0           $self->log()->debug(sprintf("place %s (%s) for %s",$category->label(),$category->id(),$entry->id()));
503              
504 0           my $pl = MT::Placement->load({entry_id => $entry->id(),
505             category_id => $category->id()});
506              
507 0 0         if ($pl) {
508 0           $self->log()->debug("already placed with id");
509 0           return $pl;
510             }
511              
512 0           $pl = MT::Placement->new();
513 0           $pl->entry_id($entry->id());
514 0           $pl->blog_id($self->{cfg}->param("mt.blog_id"));
515 0           $pl->is_primary($primary);
516 0           $pl->category_id($category->id());
517            
518 0 0         if (! $pl->save()) {
519 0           $self->log()->error(sprintf("can't save secondary placement for %s (%s), $!",
520             $category->label(),$category->parent()));
521             }
522              
523 0           return $pl;
524             }
525              
526             =head2 $obg->mk_date($date_str)
527              
528             Returns a MT specific datetime string.
529             =cut
530              
531             sub mk_date {
532 0     0 1   my $self = shift;
533 0           my $str = shift;
534 0           my $time = str2time($str);
535 0           my $dt = time2str("%Y-%m-%d%H:%M:%S",$time);
536 0           $dt =~ s/(?:-|:)//g;
537 0           return $dt;
538             }
539              
540             =head2 $obj->upload_file(\*$fh, $path)
541              
542             Wrapper method for storing an file outside of Movable Type using the
543             blog engine's file manager.
544              
545             Returns true or false.
546              
547             =cut
548              
549             sub upload_file {
550 0     0 1   my $self = shift;
551 0           my $fh = shift;
552 0           my $dest = shift;
553              
554 0           $self->log()->debug("upload file to $dest");
555              
556 0           seek($fh,0,0);
557              
558 0           my $blog = MT::Blog->load($self->blog_id());
559 0           my $fmgr = $blog->file_mgr();
560 0           my $bytes = $fmgr->put($fh,$dest,"upload");
561            
562 0 0         if (! $bytes) {
563 0           $self->log()->error("failed to upload part to $dest, ".$fmgr->errstr());
564 0           return 0;
565             }
566              
567 0           return 1;
568             }
569              
570             =head2 $obj->blog_id()
571              
572             Wrapper method for calling $obj->{cfg}->param("mt.blog_id")
573              
574             =cut
575              
576             sub blog_id {
577 0     0 1   my $self = shift;
578 0           return $self->{cfg}->param("mt.blog_id");
579             }
580              
581             sub purge {
582 0     0 0   my $self = shift;
583 0           my $blog = MT::Blog->load($self->blog_id());
584              
585 0           my @classes = qw(MT::Permission MT::Entry
586             MT::Category MT::Notification);
587              
588 0           foreach my $class (@classes) {
589 0           eval "use $class;";
590 0           my $iter = $class->load_iter({blog_id => $self->blog_id()});
591 0           my @ids = ();
592            
593             # I'm not really sure why this needs to
594             # happen this way (or, really, why it's
595             # not black-boxed) but it's what MT::Blog
596             # does so there you go...
597            
598 0           while (my $obj = $iter->()) {
599 0           push @ids, $obj->id;
600             }
601            
602 0           for my $id (@ids) {
603 0           my $obj = $class->load($id);
604 0           print sprintf("%s remove %d\n",$class,$obj->id());
605 0           $obj->remove;
606             }
607             }
608             }
609              
610             =head2 $obj->ping_for_reply(MT::Entry, $reply_basename, $from)
611              
612             Wrapper method pinging another entry.
613              
614             The entry object is the post doing the pinging. I<$reply_basename> is the
615             post that is being pinged. I<$from> is a label indicating where the ping
616             is coming from.
617              
618             The entry being pinged is fetched by where the entry's basename matches
619             I$ and it's blog_id matches B.
620              
621             Returns true or false.
622              
623             =cut
624              
625             sub ping_for_reply {
626 0     0 0   my $self = shift;
627 0           my $entry = shift;
628 0           my $reply = shift;
629 0           my $from = shift;
630              
631 0           $self->log()->debug("reply is $reply");
632              
633 0           my $to_ping = MT::Entry->load({basename => $reply,
634             blog_id => $self->blog_id()});
635              
636 0 0         if (! $to_ping) {
637 0           $self->log()->warning("can't locate entry for $reply");
638 0           return 0;
639             }
640              
641 0           $self->log()->debug("to ping : ".$to_ping->id());
642            
643 0           my $tb = MT::Trackback->load({entry_id=>$to_ping->id()});
644            
645 0           $self->log()->debug("tb is : ".$tb->id());
646            
647 0 0         if (! $tb) {
648 0           $self->log()->warning("can't locate trackback entry for $reply");
649 0           return 0;
650             }
651            
652 0           my $ping = MT::TBPing->new();
653 0           $ping->blog_id($tb->blog_id());
654 0           $ping->tb_id($tb->id());
655 0           $ping->source_url($entry->permalink());
656 0           $ping->ip($self->blog_id());
657 0           $ping->visible(1);
658 0           $ping->junk_status(0);
659            
660 0           $ping->title($entry->title());
661 0           $ping->blog_name($from);
662            
663 0 0         if (! $ping->save()) {
664 0           $self->log()->error("can not save ping, $!");
665             }
666            
667 0           $self->log()->debug(sprintf("ping from %s to %s\n",
668             $entry->permalink(),
669             $to_ping->permalink()));
670            
671 0           my $blog = MT::Blog->load($ping->blog_id());
672 0           $blog->touch();
673            
674 0 0         if (! $blog->save()) {
675             # $self->log()->error();
676             }
677            
678 0           $self->imported($to_ping->id());
679            
680             #
681            
682 0           my @pinged = split("\n",$entry->pinged_urls());
683 0           my $ping_url = $to_ping->permalink();
684            
685 0 0         if (! grep /^$ping_url$/,@pinged ) {
686 0           push @pinged, $ping_url;
687 0           $entry->pinged_urls(join "\n", @pinged);
688 0           $entry->save();
689             }
690            
691 0           $self->log()->debug(sprintf("pinged %d : %s\n",
692             $entry->id(),
693             join(";",split("\n",$entry->pinged_urls()))));
694            
695 0           return 1;
696             }
697              
698             =head1 VERSION
699              
700             1.01
701              
702             =head1 DATE
703              
704             $Date: 2005/12/03 18:46:21 $
705              
706             =head1 AUTHOR
707              
708             Aaron Straup Cope Eascope@cpan.orgE
709              
710             =head1 BUGS
711              
712             Please report all bugs via : http://rt.cpan.org
713              
714             =head1 LICENSE
715              
716             Copyright (c) 2005 Aaron Straup Cope. All Rights Reserved.
717              
718             This is free software, you may use it and distribute it under
719             the same terms as Perl itself.
720              
721             =cut
722              
723             return 1;