File Coverage

lib/CMS/JoomlaToDrupal.pm
Criterion Covered Total %
statement 12 103 11.6
branch 0 16 0.0
condition n/a
subroutine 4 10 40.0
pod 2 2 100.0
total 18 131 13.7


line stmt bran cond sub pod time code
1             package CMS::JoomlaToDrupal;
2              
3 1     1   25631 use strict;
  1         3  
  1         39  
4 1     1   5 use warnings;
  1         1  
  1         28  
5              
6 1     1   2402 use DBI;
  1         21203  
  1         70  
7 1     1   1621 use DateTime;
  1         200134  
  1         1433  
8              
9             =head1 NAME
10              
11             CMS::JoomlaToDrupal - migrate legacy Joomla content to Drupal
12              
13             =head1 VERSION
14              
15             Version 0.05
16              
17             =cut
18              
19             our $VERSION = '0.05';
20              
21             =head1 SYNOPSIS
22              
23             This code should populate a new drupal installation from a
24             legacy joomla site.
25              
26             use DBI;
27             use CMS::JoomlaToDrupal;
28              
29             my $dbh_joomla = DBI->connect();
30             my $dbh_drupal = DBI->connect();
31              
32             my $j2d = CMS::JoomlaToDrupal->new({
33             dbh_drupal => $dbh_drupal,
34             dbh_joomla => $dbh_joomla,
35             drupal_prefix => 'drupal_',
36             joomla_prefix => 'jos_',
37             log => '/home/me/logs/j2d.log',
38             email => 'site_admin@example.net' });
39              
40             $j2d->migrate();
41              
42             Install drupal in the usual way, running the script at:
43             http://example.com/install.php
44              
45             Then run the script above from a command line. After a moment,
46             your legacy joomla content should be in your drupal database
47             and be visible by visiting your home page. All stories will
48             be promoted to the front page. At this point you can apply
49             themes, custom modules can be enabled and the work of migrating
50             your site can continue without the tedium of manually inputing
51             legacy stories.
52              
53             =head1 METHODS
54              
55             =head2 ->new()
56              
57             Provided a DBI::db connection handle to each of the joomla and
58             the drupal databases, database table prefixes, plus a path to
59             a job log and an email address for the admin user on the new
60             drupal site, return an object offering access to the methods
61             necessary to migrate the old joomla site to a new drupal site.
62              
63             =cut
64              
65             sub new {
66 0     0 1   my $self = shift;
67 0           my $defaults = shift;
68 0           my $j2d = {};
69 0           $j2d->{'dbh_drupal'} = $defaults->{'dbh_drupal'};
70 0           $j2d->{'dbh_joomla'} = $defaults->{'dbh_joomla'};
71 0           $j2d->{'drupal_prefix'} = $defaults->{'drupal_prefix'};
72 0           $j2d->{'joomla_prefix'} = $defaults->{'joomla_prefix'};
73              
74 0           $j2d->{'log'} = $defaults->{'log'};
75 0           $j2d->{'email'} = $defaults->{'email'};
76 0           bless $j2d, $self;
77 0           return $j2d;
78             }
79              
80             =head2 ->migrate()
81              
82             Invoking this method will import authors, articles, comments
83             and hit counters from the configured Joomla database, into the
84             configured Drupal database. It creates a log of its activities.
85              
86             =cut
87              
88             sub migrate {
89 0     0 1   my $self = shift;
90 0           print STDERR "Now migrating site . . . \n";
91              
92 0 0         open('LOG','>',$self->{'log'}) or die "Unable to open error log file: \n\t$self->{'log'} \nfor writing. Check permissions and try again.";
93 0           $self->_import_authors();
94 0           $self->_import_articles();
95 0           $self->_import_comments();
96 0           close(LOG);
97            
98 0           print STDERR "Migration complete. Now exiting script.";
99 0           return;
100             }
101              
102             sub _import_comments {
103 0     0     my $self = shift;
104              
105 0           my $insert = "INSERT INTO $self->{'drupal_prefix'}comments (cid,pid,nid,uid,subject,comment,hostname,timestamp,name,status) VALUES(?,?,?,?,?,?,?,?,?,?);";
106 0           my $sth_insert = $self->{'dbh_drupal'}->prepare($insert);
107              
108 0           my $sql = "SELECT id,contentid,ip,name,title,comment,date,published FROM $self->{'joomla_prefix'}jomcomment";
109 0           my $sth_j = $self->{'dbh_joomla'}->prepare($sql);
110 0           $sth_j->execute();
111              
112 0           while( my $comment = $sth_j->fetchrow_hashref() ){
113              
114 0           my $id = 10000 + $comment->{'contentid'};
115              
116 0           $sth_insert->execute($comment->{'id'},0,$id,0,$comment->{'title'},$comment->{'comment'},$comment->{'ip'},_get_ts($comment->{'date'}),$comment->{'name'},$comment->{'published'});
117              
118             }
119              
120             }
121              
122             sub _import_articles {
123 0     0     my $self = shift;
124              
125 0           my $sql_grab_uid = "SELECT uid FROM $self->{'drupal_prefix'}users WHERE name = ?";
126 0           my $sth_d_uid = $self->{'dbh_drupal'}->prepare($sql_grab_uid);
127              
128 0           my $insert_node = "INSERT INTO $self->{'drupal_prefix'}node (nid,vid,type,title,uid,status,created,changed,comment,promote) VALUES(?,?,?,?,?,?,?,?,?,?)";
129 0           my $sth_d_insert_node = $self->{'dbh_drupal'}->prepare($insert_node);
130              
131 0           my $insert_rev = "INSERT INTO $self->{'drupal_prefix'}node_revisions (nid,vid,uid,title,body,teaser,log,timestamp,format) VALUES(?,?,?,?,?,?,?,?,?)";
132 0           my $sth_d_insert_rev = $self->{'dbh_drupal'}->prepare($insert_rev);
133              
134 0           my $sql_nid = "SELECT nid, vid FROM $self->{'drupal_prefix'}node ORDER BY nid DESC LIMIT 1";
135 0           my $sth_d_nid = $self->{'dbh_drupal'}->prepare($sql_nid);
136              
137 0           my $sql_nid_dupe_check = "SELECT count(*) FROM $self->{'drupal_prefix'}node_revisions WHERE nid = ?";
138 0           my $sth_d_check_for_duplicate_nid = $self->{'dbh_drupal'}->prepare($sql_nid_dupe_check);
139              
140 0           my $insert_hits = "INSERT INTO $self->{'drupal_prefix'}node_counter VALUES(?,?,0,?)";
141 0           my $sth_insert_hits = $self->{'dbh_drupal'}->prepare($insert_hits);
142              
143 0           my $sql = "SELECT id, title, title_alias, introtext, `fulltext`, created, created_by_alias, hits FROM $self->{'joomla_prefix'}content ORDER BY id ASC";
144 0           my $sth_j = $self->{'dbh_joomla'}->prepare($sql);
145 0           $sth_j->execute();
146              
147 0           while( my $article = $sth_j->fetchrow_hashref()){
148              
149 0           $sth_d_uid->execute($article->{'created_by_alias'});
150 0           my ($uid) = $sth_d_uid->fetchrow_array();
151 0 0         if (!defined($uid)){ next; }
  0            
152 0           my $ts = _get_ts($article->{'created'});
153 0           my $id = 10000 + $article->{'id'};
154 0           my $title;
155 0 0         if(defined($article->{'title_alias'})){
    0          
156 0           $title = $article->{'title_alias'};
157             } elsif(defined($article->{'title'})){
158 0           $title = $article->{'title'};
159             } else {
160 0           $title = '';
161             }
162 0           $sth_d_insert_node->execute($id,$id,'story',$title,$uid,1,$ts,$ts,1,0);
163              
164 0           $sth_d_nid->execute();
165 0           my ($nid,$vid) = $sth_d_nid->fetchrow_array();
166            
167 0           $sth_insert_hits->execute($nid,$article->{'hits'},_get_ts('2008-12-31 12:00:00'));
168              
169 0           $sth_d_check_for_duplicate_nid->execute($nid);
170 0           my ($nid_dupe) = $sth_d_check_for_duplicate_nid->fetchrow_array();
171              
172 0 0         if(!$nid_dupe){
173 0           $sth_d_insert_rev->execute($nid,$vid,$uid,$article->{'title_alias'},$article->{'fulltext'},$article->{'introtext'},'migrated by joomla_to_drupal.pl on 20081230',$ts,2);
174             } else {
175 0           print LOG "Now skipping duplicated $nid: ";
176 0 0         print LOG $article->{'title'} if(defined($article->{'title'}));
177 0           print LOG "\n";
178             }
179             }
180              
181 0           my $cutoff = _get_ts('2008-08-31 00:00:00');
182 0           my $sql_permit_comments = "UPDATE $self->{'drupal_prefix'}node SET promote = 1, comment = 2 WHERE created > '$cutoff'";
183 0           $self->{'dbh_drupal'}->do($sql_permit_comments);
184              
185             }
186              
187             sub _import_authors {
188 0     0     my $self = shift;
189              
190 0           my $table_d = $self->{'drupal_prefix'} . 'users';
191 0           my $insert = "INSERT INTO $table_d VALUES(NULL,?,'',\"$self->{'email'}\",0,0,0,'','',?,?,'',0,NULL,'','',\"$self->{'email'}\",\'a:1:{s:13:\"form_build_id\";s:37:\"form-495b83a835faf7494696d65783576244\";}');";
192 0           my $sth_d = $self->{'dbh_drupal'}->prepare($insert);
193              
194 0           my $table_j = $self->{'joomla_prefix'} . 'content';
195 0           my $sql = "select created_by_alias, created from $table_j group by created_by_alias order by created_by_alias ASC, created DESC ";
196 0           my $sth_j = $self->{'dbh_joomla'}->prepare($sql);
197 0           $sth_j->execute();
198              
199 0           while( my $author = $sth_j->fetchrow_hashref()){
200 0 0         if($author->{'created_by_alias'} eq '') { next; }
  0            
201             # print 'Now inserting into drupal database: ' . $author->{'created_by_alias'} . "\n";
202 0 0         if($author->{'created'} =~ m/0000-00-00/){
203 0           $author->{'created'} = '2008-12-31 12:00:00';
204 0           print LOG " . . . substituted arbitrary date for: $author->{'created_by_alias'}, was: 0000-00-00 00:00:00, now: $author->{'created'} \n";
205 0           next;
206             }
207 0           my $ts = _get_ts($author->{'created'});
208 0           $sth_d->execute($author->{'created_by_alias'},$ts,$ts);
209             }
210              
211             }
212              
213             sub _get_ts {
214 0     0     my $ts = shift;
215              
216 0           my $d = DateTime->new(
217             year => substr($ts,0,4),
218             month => substr($ts,5,2),
219             day => substr($ts,8,2),
220             hour => substr($ts,11,2),
221             minute => substr($ts,14,2),
222             second => substr($ts,17,2)
223             );
224              
225             # print $ts . ' is: ' . $d->epoch . "\n";
226              
227 0           return $d->epoch;
228             }
229              
230             =head1 TODO
231              
232             Rewrite to use CMS::Joomla to create the Joomla database handle,
233             perhaps add a CMS::Drupal as well. This way the constructor
234             would take drupal_cfg, joomla_cfg, log and email.
235              
236             =head1 AUTHOR
237              
238             Hugh Esco, C<< >>
239              
240             =head1 BUGS
241              
242             Please report any bugs or feature requests, tests, use cases,
243             patches or documentation to C
244             at rt.cpan.org>, or through the web interface at
245             L.
246             I will be notified, and then you'll automatically be notified
247             of progress on your bug as I make changes.
248              
249             This module is currently failing the pod-coverage tests,
250             for what reason escapes me at this moment. So that test
251             has been renamed so it is excluded from the make test run.
252             I tried to give you complete documentation, honest I did.
253              
254             The author is available, by contract, to prioritize needed
255             extensions or enhancements.
256              
257             =head1 SUPPORT
258              
259             You can find documentation for this module with the perldoc command.
260              
261             perldoc CMS::JoomlaToDrupal
262              
263             You can also look for information at:
264              
265             =over 4
266              
267             =item * AnnoCPAN: Annotated CPAN documentation
268              
269             L
270              
271             =item * CPAN Ratings
272              
273             L
274              
275             =item * RT: CPAN's request tracker
276              
277             L
278              
279             =item * Search CPAN
280              
281             L
282              
283             =back
284              
285             =head1 ACKNOWLEDGEMENTS
286              
287             This module was developed and successfully used to migrate
288             http://blackagendareport.com/
289              
290             including over 1000 stories and 10,000 comments accumulated
291             on the legacy site. Their support for its development is
292             appreciated.
293              
294             =head1 COPYRIGHT & LICENSE
295              
296             Copyright 2009 Hugh Esco, all rights reserved.
297              
298             This program is released under the following license: gpl
299              
300             =cut
301              
302             1; # End of CMS::JoomlaToDrupal
303              
304             1;
305