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