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
|
|
|
|
|
|
|
|