File Coverage

blib/lib/WWW/phpBB/Mod/Installer.pm
Criterion Covered Total %
statement 25 27 92.5
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 34 36 94.4


line stmt bran cond sub pod time code
1             package WWW::phpBB::Mod::Installer;
2            
3 2     2   65627 use 5.008008;
  2         14  
  2         86  
4 2     2   12 use strict;
  2         4  
  2         204  
5 2     2   11 use warnings;
  2         23  
  2         63  
6            
7 2     2   12 use Carp;
  2         2  
  2         211  
8 2     2   17 use File::Basename;
  2         4  
  2         218  
9 2     2   3997 use Data::Dumper;
  2         25573  
  2         158  
10 2     2   18 use Cwd 'abs_path';
  2         5  
  2         98  
11 2     2   2010 use File::Copy;
  2         5549  
  2         125  
12 2     2   2242 use XML::Xerces;
  0            
  0            
13             use DBI;
14            
15            
16             our $VERSION = '0.03';
17            
18            
19             require Exporter;
20            
21             our @ISA = qw(Exporter);
22            
23             our %EXPORT_TAGS = ( 'all' => [ qw() ] );
24             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
25             our @EXPORT = qw(uninstall_phpbb_mod
26             install_phpbb_mod
27             );
28            
29            
30             use constant ERROR => 'ERROR';
31             use constant DEBUG => 'DEBUG';
32             use constant AUDIT => 'AUDIT';
33            
34             use constant INSTALL => 'INSTALL';
35             use constant UNINSTALL => 'UNINSTALL';
36            
37             use constant DEFAULT_LANG => 'en';
38             use constant DEFAULT_STYLE => 'prosilver';
39             use constant DEFAULT_VERSION => 0;
40            
41            
42             my $script_absolute_path;
43             my $install_absolute_path;
44             my $install_absolute_file;
45             my $web_root_absolute_path;
46             my $backup_dir;
47             my $config_file;
48             my %log_handles;
49             my %headers;
50             my $phpbb_config_ref;
51             my $dbh;
52             my $phpbb_version;
53             my $style;
54             my $lang;
55            
56            
57             ###############################
58             # TODO
59             # 1. Support the uninstall command
60             # which will remove any edits made
61             # and also delete files copied as part of the install
62             # It will not modify the database.
63             #
64             ##############################
65            
66             sub uninstall_phpbb_mod{
67             my %args = shift;
68             $args{OPERATION} = UNINSTALL;
69             install_mod(%args);
70             }
71            
72             sub install_phpbb_mod{
73             my %args = (@_);
74             my $install_file = $args{INSTALL_FILE};
75             my $web_root = $args{WEB_ROOT};
76             my $tmp_style = $args{STYLE};
77             my $tmp_lang = $args{LANG};
78             my $operation = $args{OPERATION} || INSTALL;
79            
80             _setup();
81            
82             unless (-f $install_file) {
83             _write_log_entry(ERROR, "The install file '$install_file' does not exist.");
84             croak "\n\nThe install file '$install_file' does not exist.\n\n";
85             }
86             unless (-d $web_root) {
87             _write_log_entry(ERROR, "The phpbb web root directory '$web_root' does not exist.");
88             croak "\n\nThe phpbb web root directory '$web_root' does not exist.\n\n";
89             }
90            
91             $install_absolute_path = abs_path( dirname($install_file) );
92             if ($install_absolute_path =~ /^(.*)\/templates$/){
93             $install_absolute_path = $1;
94             }
95             _write_log_entry(DEBUG, "Install root path: $install_absolute_path");
96             $install_absolute_file = abs_path($install_file);
97             _write_log_entry(DEBUG, "Install file: $install_absolute_file");
98             $web_root_absolute_path = abs_path( $web_root);
99             _write_log_entry(DEBUG, "phpBB web root: $web_root_absolute_path");
100             $config_file = "$web_root_absolute_path/config.php";
101             _write_log_entry(DEBUG, "phpBB config file: $config_file");
102            
103             unless (-f $config_file) {
104             _write_log_entry(ERROR, "The phpbb web root directory '$web_root_absolute_path' does not contain a config.php");
105             croak "\n\nThe phpbb web root directory '$web_root_absolute_path' does not contain a config.php\n\n";
106             }
107            
108             $phpbb_config_ref = _read_phpbb_config();
109             eval {my $dbh = _mysql_connect();};
110             $phpbb_version = _get_phpbb_version();
111             $lang = $tmp_lang || _get_phpbb_lang();
112             _write_log_entry(AUDIT, "Language: '$lang'");
113             $style = $tmp_style || _get_phpbb_style();
114             _write_log_entry(AUDIT, "Style: '$style'");
115            
116             _write_log_entry(AUDIT, "Initialisation complete, processing installation file '$install_absolute_file'");
117             my $doc = _load_install_file($install_absolute_file);
118             my $instruction_ref = _process_document($doc);
119             _write_log_entry(DEBUG, 'Instruction list: ' . Data::Dumper->Dump([$instruction_ref]));
120            
121             if ($headers{target_version} ne $phpbb_version){
122             warn "\nWARNING: This mod is intended for a different version of phpbb\n" .
123             "\tBoard Version $phpbb_version\n" .
124             "\tMod written for version $headers{target_version}\n\n";
125             }
126            
127             if ($operation eq INSTALL){
128             _write_log_entry(AUDIT, "Beginning installation for mod $headers{title} " .
129             "version $headers{version_major}.$headers{version_minor}".
130             ".$headers{version_revision}$headers{version_release} " .
131             "by $headers{author}");
132             _process_instructions($instruction_ref, $backup_dir);
133             }
134             elsif($operation eq UNINSTALL){
135             _write_log_entry(AUDIT, "Beginning uninstall for mod $headers{title} " .
136             "version $headers{version_major}.$headers{version_minor}".
137             ".$headers{version_revision}$headers{version_release} " .
138             "by $headers{author}");
139             _process_uninstall($instruction_ref, $backup_dir);
140             }
141             else{
142             _write_log_entry(ERROR, "Unsupported operation '$operation'.\n");
143             croak "Unsupported operation '$operation'.\n" .
144             "Only\n\tOPERATION => 'INSTALL'\n\tOPERATION => 'UNINSTALL'\n" .
145             "are supported.\n";
146             }
147            
148             _write_log_entry(AUDIT, 'Complete');
149             _tear_down();
150             }
151            
152            
153             sub _setup{
154             $script_absolute_path = abs_path( dirname($0) );
155            
156             $backup_dir = _create_backup_dirs($script_absolute_path);
157            
158             my $log_dir = "$script_absolute_path/logs";
159             if (!-d $log_dir){
160             _create_dir_recursive($log_dir);
161             }
162            
163             open(my $error_handle, '>>', "$log_dir/error.log")
164             or croak "Can't open error log file $log_dir/error.log: $!\n";
165             open(my $debug_handle, '>>', "$log_dir/debug.log")
166             or croak "Can't open debug log file $log_dir/debug.log: $!\n";
167             open(my $audit_handle, '>>', "$log_dir/audit.log")
168             or croak "Can't open audit log file $log_dir/audit.log: $!\n";
169            
170             $log_handles{ERROR} = $error_handle;
171             $log_handles{DEBUG} = $debug_handle;
172             $log_handles{AUDIT} = $audit_handle;
173             }
174            
175             sub _tear_down{
176             $dbh->disconnect() if $dbh;
177             foreach my $handle (keys %log_handles){
178             close $log_handles{$handle};
179             }
180             }
181            
182             sub _get_phpbb_version{
183             my $version;
184             if ($dbh){
185             my $sql = "select * from " . $phpbb_config_ref->{table_prefix} .
186             "config where config_name = 'version'" ;
187             my $res ;
188             #selectall_hashref causes warnings from File::Copy under windows
189             #eval{$res = $dbh->selectall_hashref($sql, 'config_name') } ;
190             eval{$res = $dbh->selectall_arrayref($sql) } ;
191             $version = $res->[0]->[1];
192             _write_log_entry(AUDIT, "phpBB version: $version");
193             }
194             else{
195             _write_log_entry(AUDIT, 'No database connection, cannot get phpBB version');
196             }
197            
198             if (!$version){
199             $version = DEFAULT_VERSION;
200             }
201            
202             return $version;
203             }
204            
205             sub _get_phpbb_lang{
206             my $lang;
207             if ($dbh){
208             my $sql = "select config_value from " . $phpbb_config_ref->{table_prefix} .
209             "config where config_name = 'default_lang'" ;
210             my $res ;
211             #selectall_hashref causes warnings from File::Copy under windows
212             #eval{$res = $dbh->selectall_hashref($sql, 'config_name') } ;
213             eval{$res = $dbh->selectall_arrayref($sql) } ;
214             $lang = $res->[0]->[0];
215             _write_log_entry(DEBUG, "phpBB default lang: $lang");
216             }
217             else{
218             _write_log_entry(AUDIT, 'No database connection, cannot get phpBB language');
219             }
220            
221             if (!$lang){
222             $lang = DEFAULT_LANG;
223             }
224            
225             return $lang;
226             }
227            
228             sub _get_phpbb_style{
229             my $style;
230             if ($dbh){
231             my $sql = "select template_path from " . $phpbb_config_ref->{table_prefix} .
232             "styles_template where template_id = " .
233             "(select template_id from " . $phpbb_config_ref->{table_prefix} .
234             "styles where style_id = " .
235             "(select config_value from " . $phpbb_config_ref->{table_prefix} .
236             "config where config_name = 'default_style'))" ;
237             my $res ;
238             #selectall_hashref causes warnings from File::Copy under windows
239             #eval{$res = $dbh->selectall_hashref($sql, 'config_name') } ;
240             eval{$res = $dbh->selectall_arrayref($sql) } ;
241             $style = $res->[0]->[0];
242             _write_log_entry(DEBUG, "phpBB default style path: $style");
243             }
244             else{
245             _write_log_entry(AUDIT, 'No database connection, cannot get phpBB style');
246             }
247            
248             if (!$style){
249             $style = DEFAULT_STYLE;
250             }
251            
252             return $style;
253             }
254            
255             sub _mysql_connect {
256            
257             if ($phpbb_config_ref->{dbms} eq 'mysql'){
258             $dbh = DBI->connect(
259             'DBI:mysql:database=' . $phpbb_config_ref->{dbname} .';host=' . $phpbb_config_ref->{dbhost},
260             $phpbb_config_ref->{dbuser},
261             $phpbb_config_ref->{dbpasswd},
262             { RaiseError => 1,
263             AutoCommit => 1,
264             }
265             );
266             }
267             else{
268             _write_log_entry(AUDIT, 'Only mysql databases are supported, database updates are not possible');
269             }
270            
271             return $dbh;
272             }
273            
274             sub _read_phpbb_config{
275             my %phpbb_config;
276             open IN, '<', $config_file or croak "Can't open phpbb config file: $!\n";
277             while() {
278             my $line=$_;
279             if ($line =~ /^\s*\$(\w+)\s*=\s*'(\w*)'\;\s*$/) {
280             $phpbb_config{$1} = $2;
281             }
282             elsif ($line =~ /^\s*\@define\('(\w+)',\s*(\w+)\);\s*$/){
283             $phpbb_config{$1} = $2;
284             }
285             }
286             close (IN);
287             _write_log_entry(DEBUG, "phpBB Config: " . Data::Dumper->Dump([\%phpbb_config]));
288            
289             return \%phpbb_config;
290             }
291            
292             sub _load_install_file {
293             my $install_filename = shift;
294            
295             #create a parser and attempt to parse the XML document
296             my $dom = XML::Xerces::XercesDOMParser->new();
297             my $error_handler = XML::Xerces::PerlErrorHandler->new();
298             $dom->setErrorHandler($error_handler);
299             eval{$dom->parse($install_filename)};
300             croak("Couldn't parse file: $install_filename\n$@") if $@;
301            
302             #the parse was successful, we have a well formed xml instance
303             my $doc = $dom->getDocument();
304            
305             return $doc;
306             }
307            
308             sub _write_log_entry{
309             my $type = shift;
310             my $message = shift;
311            
312             my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst)
313             = localtime(time);
314             my $curr_time = sprintf "%4d-%02d-%02d %02d:%02d:%02d", $year+1900,$mon+1,$mday,$hour,$min,$sec;
315             my $log_line = "$curr_time - $message\n";
316            
317             my $file_handle = $log_handles{$type};
318             if ($file_handle){
319             print $file_handle $log_line;
320             }
321             else{
322             warn $log_line;
323             }
324             }
325            
326             sub _process_document {
327             my $install_doc = shift;
328            
329             my @instruction_list;
330             my $root = $install_doc->getDocumentElement();
331            
332             if ($root->hasChildNodes()) {
333             ROOT: foreach my $child ($root->getChildNodes) {
334             my $child_name = $child->getNodeName();
335             if ($child->isa('XML::Xerces::DOMElement')){
336             if ($child_name eq 'action-group'){
337             #process this node
338             if ($child->hasChildNodes()) {
339             foreach my $action ($child->getChildNodes) {
340             my $action_name = $action->getNodeName();
341             if ($action->isa('XML::Xerces::DOMElement')){
342             if ($action_name eq 'copy'){
343             my $copy_ref = _process_copy($action);
344             push (@instruction_list, $copy_ref);
345             }
346             elsif ($action_name eq 'sql'){
347             my $sql_ref = _process_sql($action);
348             push (@instruction_list, $sql_ref);
349             }
350             elsif ($action_name eq 'open'){
351             my $open_ref = _process_open($action);
352             push (@instruction_list, $open_ref);
353             }
354             elsif ($action_name eq 'diy-instructions'){
355             my $diy_ref = _process_diy_instructions($action);
356             push (@instruction_list, $diy_ref);
357             }
358             }
359             }
360             }
361             else{
362             #no actions
363             croak "No actions to perform\n";
364             }
365             }
366             elsif ($child_name eq 'header'){
367             _process_header($child);
368             }
369             else{
370             _write_log_entry(DEBUG, "Found additional first level child '$child_name' - skipping....");
371             }
372             }
373             }
374             }
375             else{
376             croak "Empty document nothing to process\n";
377             }
378            
379             return \@instruction_list;
380             }
381            
382             sub _process_header{
383             my $header = shift;
384            
385             foreach my $child ($header->getChildNodes) {
386             my $child_name = $child->getNodeName();
387             if ($child->isa('XML::Xerces::DOMElement')){
388             if ($child_name eq 'title'){
389             $headers{title} = $headers{title} || $child->getTextContent();
390             my %child_attrs = $child->getAttributes();
391             foreach my $attr_name (keys %child_attrs) {
392             if ($attr_name eq 'lang'){
393             if ($child_attrs{$attr_name} eq $lang){
394             $headers{title} = $child->getTextContent();
395             }
396             }
397             }
398             }
399             elsif ($child_name eq 'description'){
400             $headers{description} = $headers{description} || $child->getTextContent();
401             my %child_attrs = $child->getAttributes();
402             foreach my $attr_name (keys %child_attrs) {
403             if ($attr_name eq 'lang'){
404             if ($child_attrs{$attr_name} eq $lang){
405             $headers{description} = $child->getTextContent();
406             }
407             }
408             }
409             }
410             elsif ($child_name eq 'author-group'){
411             foreach my $ag_child($child->getChildNodes) {
412             my $ag_name = $ag_child->getNodeName();
413             if ($ag_child->isa('XML::Xerces::DOMElement')){
414             if ($ag_name eq 'author'){
415             foreach my $author_child($ag_child->getChildNodes) {
416             my $author_name = $author_child->getNodeName();
417             if ($author_child->isa('XML::Xerces::DOMElement')){
418             if ($author_name eq 'realname'){
419             $headers{author} = $author_child->getTextContent();
420             }
421             elsif ($author_name eq 'username'){
422             $headers{author_username} = $author_child->getTextContent();
423             }
424             }
425             }
426             }
427             }
428             }
429             }
430             elsif ($child_name eq 'mod-version'){
431             foreach my $mv_child($child->getChildNodes) {
432             my $mv_name = $mv_child->getNodeName();
433             if ($mv_child->isa('XML::Xerces::DOMElement')){
434             if ($mv_name eq 'major'){
435             $headers{version_major} = $mv_child->getTextContent();
436             }
437             elsif ($mv_name eq 'minor'){
438             $headers{version_minor} = $mv_child->getTextContent();
439             }
440             elsif ($mv_name eq 'revision'){
441             $headers{version_revision} = $mv_child->getTextContent();
442             }
443             elsif ($mv_name eq 'release'){
444             $headers{version_release} = $mv_child->getTextContent();
445             }
446             }
447             }
448             }
449             elsif ($child_name eq 'installation'){
450             foreach my $i_child($child->getChildNodes) {
451             my $i_name = $i_child->getNodeName();
452             if ($i_child->isa('XML::Xerces::DOMElement')){
453             if ($i_name eq 'target-version'){
454             foreach my $tv_child($i_child->getChildNodes) {
455             my $tv_name = $tv_child->getNodeName();
456             if ($tv_child->isa('XML::Xerces::DOMElement')){
457             if ($tv_name eq 'target-primary'){
458             $headers{target_version} = $tv_child->getTextContent();
459             }
460             }
461             }
462             }
463             }
464             }
465             }
466             }
467             }
468             foreach my $nv_name(qw{author author_username version_major
469             version_minor version_revision
470             version_release title description}){
471             if (!defined $headers{$nv_name}){
472             $headers{$nv_name} = '';
473             }
474             }
475             _write_log_entry(DEBUG, "Install file header values: " . Data::Dumper->Dump([\%headers]));
476             }
477            
478             sub _process_copy{
479             my $copy_node = shift;
480            
481             my %return_hash;
482             my @file_array;
483             $return_hash{action} = 'copy';
484            
485             foreach my $child ($copy_node->getChildNodes) {
486             my $child_name = $child->getNodeName();
487             if ($child->isa('XML::Xerces::DOMElement')){
488             if ($child_name eq 'file'){
489             my %file_hash;
490             my %file_attrs = $child->getAttributes();
491             foreach my $attr_name (keys %file_attrs) {
492             $file_hash{$attr_name} = $file_attrs{$attr_name};
493             }
494             push (@file_array, \%file_hash);
495             }
496             }
497             }
498            
499             $return_hash{files} = \@file_array;
500             return \%return_hash;
501             }
502            
503             sub _process_open{
504             my $open_node = shift;
505             my %return_hash;
506             my @edits;
507             $return_hash{action} = 'open';
508            
509             my %open_attrs = $open_node->getAttributes();
510             foreach my $attr_name (keys %open_attrs) {
511             if ($attr_name eq 'src'){
512             $return_hash{src} = $open_attrs{$attr_name};
513             }
514             }
515             foreach my $child ($open_node->getChildNodes) {
516             my $child_name = $child->getNodeName();
517             if ($child->isa('XML::Xerces::DOMElement')){
518             if ($child_name eq 'edit'){
519             my %edit_hash;
520             foreach my $edit_child ($child->getChildNodes) {
521             my $edit_child_name = $edit_child->getNodeName();
522             if ($edit_child->isa('XML::Xerces::DOMElement')){
523             if ($edit_child_name eq 'find'){
524             $edit_hash{find} = $edit_child->getTextContent();
525             my %find_attrs = $edit_child->getAttributes();
526             foreach my $attr_name (keys %find_attrs) {
527             if ($attr_name eq 'type'){
528             $edit_hash{find_type} = $find_attrs{$attr_name};
529             }
530             }
531             }
532             elsif ($edit_child_name eq 'action'){
533             my %action_attrs = $edit_child->getAttributes();
534             foreach my $attr_name (keys %action_attrs) {
535             if ($attr_name eq 'type'){
536             my $type_name = $action_attrs{$attr_name};
537             if ($type_name eq 'after-add'){
538             $edit_hash{action_after_add} = $edit_child->getTextContent();
539             }
540             elsif ($type_name eq 'before-add'){
541             $edit_hash{action_before_add} = $edit_child->getTextContent();
542             }
543             elsif ($type_name eq 'replace-with'){
544             $edit_hash{action_replace_with} = $edit_child->getTextContent();
545             }
546             }
547             }
548             }
549             elsif ($edit_child_name eq 'inline-edit'){
550             foreach my $ie_child ($edit_child->getChildNodes) {
551             my $ie_name = $ie_child->getNodeName();
552             if ($ie_child->isa('XML::Xerces::DOMElement')){
553             if ($ie_name eq 'inline-find'){
554             $edit_hash{inline_find} = $ie_child->getTextContent();
555             my %find_attrs = $ie_child->getAttributes();
556             foreach my $attr_name (keys %find_attrs) {
557             if ($attr_name eq 'type'){
558             $edit_hash{find_type} = $find_attrs{$attr_name};
559             }
560             }
561             }
562             elsif ($ie_name eq 'inline-action'){
563             my %action_attrs = $ie_child->getAttributes();
564             foreach my $attr_name (keys %action_attrs) {
565             if ($attr_name eq 'type'){
566             my $type_name = $action_attrs{$attr_name};
567             if ($type_name eq 'after-add'){
568             $edit_hash{inline_action_after_add} = $ie_child->getTextContent();
569             }
570             elsif ($type_name eq 'before-add'){
571             $edit_hash{inline_action_before_add} = $ie_child->getTextContent();
572             }
573             elsif ($type_name eq 'replace-with'){
574             $edit_hash{inline_action_replace_with} = $ie_child->getTextContent();
575             }
576             }
577             }
578             }
579             }
580             }
581             }
582             }
583             }
584             push (@edits, \%edit_hash);
585             }
586             }
587             }
588            
589             $return_hash{edits} = \@edits;
590            
591             return \%return_hash;
592             }
593            
594             sub _process_sql{
595             my $sql_node = shift;
596             my %return_hash;
597            
598             my %sql_attrs = $sql_node->getAttributes();
599             foreach my $attr_name (keys %sql_attrs) {
600             if ($attr_name eq 'dbms'){
601             $return_hash{dbms} = $sql_attrs{$attr_name};
602             }
603             }
604             $return_hash{action} = 'sql';
605             $return_hash{sql} = $sql_node->getTextContent();
606            
607             #process everything as mysql
608             #if (defined $return_hash{dbms}){
609             # if ($return_hash{dbms} ne 'mysql'){
610             # _write_log_entry(ERROR, "The only supported database is mysql, can't process SQL statement");
611             # croak "Can't process SQL statement only mysql is supported\n";
612             # }
613             # elsif ($return_hash{dbms} ne $phpbb_config_ref->{dbms}){
614             # _write_log_entry(ERROR, "php DBMS type is different to the SQL statement DBMS type");
615             # croak "php DBMS type is different to the SQL statement DBMS type\n";
616             # }
617             #}
618            
619             return \%return_hash;
620             }
621            
622             sub _process_diy_instructions{
623             my $diy_node = shift;
624            
625             my %return_hash;
626             $return_hash{action} = 'diy-instructions';
627             $return_hash{instruction} = $return_hash{instruction} || $diy_node->getTextContent();
628            
629             my %child_attrs = $diy_node->getAttributes();
630             foreach my $attr_name (keys %child_attrs) {
631             if ($attr_name eq 'lang'){
632             if ($child_attrs{$attr_name} eq $lang){
633             $return_hash{instruction} = $diy_node->getTextContent();
634             }
635             }
636             }
637            
638             return \%return_hash;
639             }
640            
641             sub _process_uninstall{
642             my $instructions_ref = shift;
643             my $backup_dir = shift;
644            
645             warn ("WARNING: Uninstall not yet implemented\n\n");
646             return;
647            
648             foreach my $instruction_ref (@{$instructions_ref}){
649             if ($instruction_ref->{action} eq 'copy'){
650             #phpbb_uninstall_copy_file($instruction_ref, $backup_dir);
651             }
652             elsif ($instruction_ref->{action} eq 'sql'){
653             #phpbb_uninstall_run_sql($instruction_ref);
654             }
655             elsif ($instruction_ref->{action} eq 'open'){
656             #phpbb_uninstall_open_file($instruction_ref);
657             }
658             elsif ($instruction_ref->{action} eq 'diy-instructions'){
659             #phpbb_uninstall_diy_instructions($instruction_ref);
660             }
661             }
662             }
663            
664            
665             sub _process_instructions{
666             my $instructions_ref = shift;
667             my $backup_dir = shift;
668            
669             foreach my $instruction_ref (@{$instructions_ref}){
670             if ($instruction_ref->{action} eq 'copy'){
671             _phpbb_copy_file($instruction_ref, $backup_dir);
672             }
673             elsif ($instruction_ref->{action} eq 'sql'){
674             _phpbb_run_sql($instruction_ref);
675             }
676             elsif ($instruction_ref->{action} eq 'open'){
677             _phpbb_open_file($instruction_ref);
678             }
679             elsif ($instruction_ref->{action} eq 'diy-instructions'){
680             _phpbb_diy_instructions($instruction_ref);
681             }
682             }
683             }
684            
685             sub _phpbb_copy_file{
686             my $instruction_ref = shift;
687             my $backup_dir = shift;
688            
689             foreach my $file_ref (@{$instruction_ref->{files}}){
690             my $to = $file_ref->{to};
691             if ( (defined $style) && ($to =~ /^(.*)prosilver(.*)$/) ){
692             $to = $1 . $style . $2;
693             }
694             elsif ( (defined $style) && ($to =~ /^(.*)subsilver2(.*)$/) ){
695             $to = $1 . $style . $2;
696             }
697            
698             my $source = "$install_absolute_path/" . $file_ref->{from};
699             my $destination = "$web_root_absolute_path/" . $to;
700             if (index($destination, '*') >= 0){
701             $destination = dirname ($destination);
702             }
703             _write_log_entry(AUDIT, "Copy file: $source to $destination");
704             if (-f $destination){
705             my $backup_filename = "$backup_dir/" . $to;
706             _create_dir_recursive( dirname ($backup_filename) );
707             _write_log_entry(DEBUG, "Backup file: $destination to $backup_filename");
708             copy ($destination, $backup_filename)
709             or croak "Failed to backup $destination\n";
710             }
711             _create_dir_recursive( dirname ($destination) );
712             copy ($source, $destination)
713             or croak "Copy failed from $source to $destination\n";
714             }
715             }
716            
717             sub _phpbb_run_sql{
718             my $instruction_ref = shift;
719             if ($dbh){
720             my $sql = $instruction_ref->{sql};
721             $sql =~ s/phpbb_/$phpbb_config_ref->{table_prefix}/g;
722             my @statements = split /;/, $sql;
723             foreach my $statement (@statements){
724             $statement = _trim($statement);
725             if (length($statement) > 0){
726             _write_log_entry(AUDIT, "Updating database: $statement");
727             my $sth;
728             eval{$sth = $dbh->prepare($statement)};
729             eval{$sth->execute();};
730             if ($@){
731             _write_log_entry(ERROR, "Unable to run SQL: '$statement' : " . $dbh->err . " : $@");
732             carp "Database error trying to run SQL. $@\n";
733             }
734             }
735             }
736             }
737             else{
738             _write_log_entry(ERROR, "Cannot update database, no database connection");
739             croak "Can't update database, no connection\n";
740             }
741             }
742            
743             sub _phpbb_open_file{
744             my $instruction_ref = shift;
745            
746             my $src = $instruction_ref->{src};
747             if ( (defined $style) && ($src =~ /^(.*)prosilver(.*)$/) ){
748             $src = $1 . $style . $2;
749             }
750             if ( (defined $style) && ($src =~ /^(.*)subsilver2(.*)$/) ){
751             $src = $1 . $style . $2;
752             }
753            
754             my $file_to_open = "$web_root_absolute_path/$src";
755             if (!-f $file_to_open){
756             _write_log_entry(ERROR, "File to open '$file_to_open' doesn't exist");
757             warn "WARNING: File to open '$file_to_open' doesn't exist\n";
758             }
759             _write_log_entry(AUDIT, "Opening file '$file_to_open'");
760             my $backup_filename = "$backup_dir/" . $src;
761             _create_dir_recursive( dirname ($backup_filename) );
762             _write_log_entry(DEBUG, "Backup file: $file_to_open to $backup_filename");
763             copy ($file_to_open, $backup_filename)
764             or croak "Failed to backup $file_to_open\n";
765            
766             {
767             local( $/, *FH ) ;
768             open( FH, '<', $file_to_open ) or croak "Couldn't open file for editing: $!\n";
769             my $file_text = ;
770             close (FH);
771            
772             foreach my $edit_ref (@{$instruction_ref->{edits}}){
773             my $find_start = index($file_text, $edit_ref->{find});
774             if($find_start >= 0){
775             my $find_text = $edit_ref->{find};
776             my $pre_text = substr $file_text, 0, $find_start;
777             my $post_text = substr $file_text, $find_start + length($find_text);
778            
779             if (defined $edit_ref->{inline_find}){
780             my $inline_find_start = index($find_text, $edit_ref->{inline_find});
781             if($inline_find_start >= 0){
782             my $inline_find_text = $edit_ref->{inline_find};
783             my $inline_pre_text = substr $find_text, 0, $inline_find_start;
784             my $inline_post_text = substr $find_text, $inline_find_start + length($inline_find_text);
785            
786             my $already_installed = 0;
787             if (defined $edit_ref->{inline_action_after_add}){
788             if (index($file_text, $inline_find_text . $edit_ref->{inline_action_after_add}) >= 0){
789             $already_installed = 1;
790             }
791             }
792             if (defined $edit_ref->{inline_action_before_add}){
793             if (index($file_text, $edit_ref->{inline_action_before_add} . $inline_find_text) >= 0){
794             $already_installed = 1;
795             }
796             }
797             if($already_installed){
798             _write_log_entry(ERROR, "It looks like the mod has already been applied to $file_to_open");
799             croak "It looks like the mod has already been applied to $file_to_open\n";
800             }
801            
802             if (defined $edit_ref->{inline_action_replace_with}){
803             $inline_find_text = $edit_ref->{inline_action_replace_with};
804             }
805             if (defined $edit_ref->{inline_action_after_add}){
806             $inline_find_text = $inline_find_text . $edit_ref->{inline_action_after_add};
807             }
808             if (defined $edit_ref->{inline_action_before_add}){
809             $inline_find_text = $edit_ref->{inline_action_before_add} . $inline_find_text;
810             }
811             $find_text = $inline_pre_text . $inline_find_text . $inline_post_text;
812             }
813             else{
814             _write_log_entry(ERROR, "Couldn't find the required inline edit: $edit_ref->{inline_find}");
815             warn "WARNING: Inline edit find failed, it must be dealt with manually. $edit_ref->{inline_find}\n";
816             }
817             }
818             if (defined $edit_ref->{action_replace_with} ||
819             defined $edit_ref->{action_before_add} ||
820             defined $edit_ref->{action_after_add}){
821            
822             #check if the mod has already been applied
823             my $already_installed = 0;
824             if (defined $edit_ref->{action_before_add}){
825             my $action_start = index($file_text, $edit_ref->{action_before_add});
826             if ($action_start >= 0){
827             $already_installed = 1;
828             }
829             }
830             if (defined $edit_ref->{action_after_add}){
831             my $action_start = index($file_text, $edit_ref->{action_after_add});
832             if ($action_start >= 0){
833             $already_installed = 1;
834             }
835             }
836             if($already_installed){
837             _write_log_entry(ERROR, "It looks like the mod has already been applied to $file_to_open");
838             croak "It looks like the mod has already been applied to $file_to_open\n";
839             }
840            
841             if (defined $edit_ref->{action_replace_with}){
842             $find_text = $edit_ref->{action_replace_with};
843             }
844             if (defined $edit_ref->{action_after_add}){
845             $find_text = $find_text . "\n\n" . $edit_ref->{action_after_add} . "\n";
846             }
847             if (defined $edit_ref->{action_before_add}){
848             $find_text = "\n" . $edit_ref->{action_before_add} . "\n\n" . $find_text;
849             }
850             }
851             $file_text = $pre_text . $find_text . $post_text;
852            
853             open (OUT, '>', $file_to_open) or croak "Unable to rewrite file '$file_to_open': $!\n";
854             binmode OUT;
855             print OUT $file_text;
856             close (OUT);
857             }
858             else{
859             _write_log_entry(ERROR, "Couldn't find the required edit: $edit_ref->{find}");
860             warn "WARNING: Edit find failed, it must be dealt with manually. $edit_ref->{find}\n";
861             }
862             }
863             }
864             }
865            
866             sub _phpbb_diy_instructions{
867             my $instruction_ref = shift;
868             _write_log_entry(AUDIT, "DIY Instruction: " . $instruction_ref->{instruction});
869             print STDOUT "\n\nDIY Instructions:\n" . $instruction_ref->{instruction} . "\n\n";
870             }
871            
872            
873             sub _create_dir_recursive{
874             my $complete_dir = shift;
875            
876             my @file_parts = split /\//, $complete_dir;
877             my $curr_dir = '';
878             foreach my $file_part (@file_parts){
879             $curr_dir .= "$file_part/";
880             if (!-d $curr_dir){
881             mkdir ($curr_dir) or croak "mkdir failed for '$curr_dir'\n";
882             }
883             }
884             }
885            
886            
887             sub _create_backup_dirs{
888             my $working_dir = shift;
889            
890             my $backup_dir = "$working_dir/backups";
891            
892             if (!-d $backup_dir){
893             mkdir ($backup_dir)
894             or croak "Can't create backup directory '$backup_dir'\n";
895             }
896            
897             my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst)
898             = localtime(time);
899             my $curr_time = sprintf "%4d-%02d-%02d_%02d-%02d-%02d", $year+1900,$mon+1,$mday,$hour,$min,$sec;
900             my $current_backup_dir = "$backup_dir/$curr_time";
901            
902             if (!-d $current_backup_dir){
903             mkdir ($current_backup_dir)
904             or croak "Can't create backup directory '$current_backup_dir'\n";
905             }
906            
907             return $current_backup_dir;
908             }
909            
910             sub _trim {
911             my $txt=shift;
912             $_=$txt;
913             $txt =~ s/^\s+|\s+$//g ;
914             return $txt
915             }
916            
917             1;
918            
919             __END__