File Coverage

blib/lib/FWS/V2/File.pm
Criterion Covered Total %
statement 19 265 7.1
branch 0 72 0.0
condition 0 62 0.0
subroutine 7 22 31.8
pod n/a
total 26 421 6.1


line stmt bran cond sub pod time code
1             package FWS::V2::File;
2              
3 1     1   23 use 5.006;
  1         3  
  1         45  
4 1     1   5 use strict;
  1         2  
  1         1827  
5              
6              
7             =head1 NAME
8              
9             FWS::V2::File - Framework Sites version 2 text and image file methods
10              
11             =head1 VERSION
12              
13             Version 1.13091122
14              
15             =cut
16              
17             our $VERSION = '1.13091122';
18              
19              
20             =head1 SYNOPSIS
21              
22             use FWS::V2;
23              
24             my $fws = FWS::V2->new();
25              
26             #
27             # retrieve a reference to an array of data we asked for
28             #
29             my $fileArrayRef = $fws->fileArray( directory => "/home/directory" );
30              
31              
32              
33             =head1 DESCRIPTION
34              
35             Framework Sites version 2 file writing, reading and manipulation methods.
36              
37             =head1 METHODS
38              
39              
40             =head2 backupFWS
41              
42             Create a backup of the filesSecurePath, filesPath and the database and place it under the filesSecurePath backups directory. The file names will be date keyed and be processed by the restoreFWS method by they keyed date string. This will exclude any table that has the word 'session' in it, or anything that starts with 'admin_'.
43              
44             Parameters:
45             id: file name of files - the date string in numbers will be used if no id is passed
46             excludeTables: Comma delimited list of tables you do not want to back up
47             excludeFiles: Do not backup the FWS web accessable files
48             minMode: Only backup anything that HAS to be there, no core, no js and css backup files.
49             excludeSiteFiles: Backup site files related to plugins and the fws instance, but not the once related to the site
50             excludeSecureFiles: Do not backup the secure files
51              
52             Usage:
53             $fws->backupFWS(%params);
54              
55             Inside of the go.pl if you add certain site wide paramaters it will alter the behaviour of the backup"
56              
57             $fws->{FWSBackupExcludeTables} = 'notThisSiteTableThatsSpecial,orThisOne';
58              
59             =cut
60              
61             sub backupFWS {
62 0     0     my ( $self, %paramHash ) = @_;
63              
64             #
65             # set or use the default id
66             #
67 0   0       $paramHash{id} ||= $self->formatDate( format => 'number' );
68              
69             #
70             # build inital directories where this will be stored
71             #
72 0           my $backupDir = $self->{fileSecurePath} . '/backups';
73 0           my $backupFile = $backupDir . '/' . $paramHash{id};
74 0           $self->makeDir( $backupDir );
75              
76             #
77             # turn the exclude table into a ha to compare against
78             #
79 0           my %excludeTables;
80 0           map { $excludeTables{$_} = 1 } split ( ',', $paramHash{excludeTables} );
  0            
81              
82             #
83             # Dump the database
84             #
85 0           open ( my $SQLFILE, '>', $backupFile . '.sql' );
86 0           my $tables = $self->runSQL( SQL => 'SHOW TABLES' );
87 0           while ( @$tables ) {
88 0           my $table = shift( @$tables );
89 0 0 0       if ( $table !~ /session/ && $table !~ /^admin_/ && !$excludeTables{$table} ) {
      0        
90 0           print $SQLFILE 'DROP TABLE IF EXISTS ' . $self->safeSQL( $table ) . ';' . "\n";
91 0           print $SQLFILE $self->{'_DBH_' . $self->{DBName} . $self->{DBHost} }->selectall_arrayref( 'SHOW CREATE TABLE ' . $self->safeSQL( $table ) )->[0][1] . ';' . "\n";
92 0           my $sth = $self->{'_DBH_' . $self->{DBName} . $self->{DBHost} }->prepare( 'SELECT * FROM ' . $table );
93 0           $sth->execute();
94 0           while ( my @data = $sth->fetchrow_array() ) {
95 0           map ( $_ = "'" . $self->safeSQL( $_ ) . "'", @data );
96 0           map ( $_ =~ s/\0/\\0/sg, @data );
97 0           map ( $_ =~ s/\n/\\n/sg, @data );
98 0           print $SQLFILE 'INSERT INTO ' . $table . ' VALUES (' . join( ',', @data ) . ');' . "\n";
99             }
100             }
101             }
102 0           close $SQLFILE;
103              
104 0 0         if ( !$paramHash{excludeFiles} ) {
105 0 0         if ( !$paramHash{excludeSiteFiles} ) {
106 0           $self->packDirectory( minMode => $paramHash{minMode}, fileName => $backupFile . '.files', directory => $self->{filePath} );
107             }
108             else {
109 0           $self->packDirectory( minMode => $paramHash{minMode}, directoryList => '/fws,/plugins', fileName => $backupFile . '.files', directory => $self->{filePath} );
110             }
111             }
112              
113 0 0 0       if ( !$paramHash{excludeSecureFiles} && !$paramHash{minMode} ) {
114 0           $self->packDirectory( minMode => $paramHash{minMode}, fileName => $backupFile . '.secureFiles', directory => $self->{fileSecurePath}, baseDirectory => $self->{fileSecurePath} );
115             }
116              
117 0           return $paramHash{id};
118             }
119              
120              
121             =head2 restoreFWS
122              
123             Restore a backup created by backupFWS. This will overwrite the files in place that are the same, and will replace all database tables with the one from the restore that are restored. All tables, and files not part of the restore will be left untouched.
124              
125             $fws->restoreFWS( id => 'someID' );
126              
127             =cut
128              
129             sub restoreFWS {
130 0     0     my ( $self, %paramHash ) = @_;
131              
132 0           $self->FWSLog( 'Restore started: ' . $paramHash{id} );
133              
134 0           my $restoreFile = $self->{fileSecurePath} . '/backups/' . $paramHash{id};
135 0           $self->unpackDirectory( fileName => $restoreFile . '.files', directory => $self->{filePath} );
136 0           $self->unpackDirectory( fileName => $restoreFile . '.secureFiles', directory => $self->{fileSecurePath} );
137              
138 0           my $sqlFile = $self->{fileSecurePath} . '/backups/' . $paramHash{id} . '.sql';
139 0 0         open ( my $SQLFILE, '<', $sqlFile ) || $self->FWSLog( 'Could not read file: ' . $sqlFile );
140 0           my $statement;
141             my $endTest;
142 0           while ( <$SQLFILE> ) {
143 0           $statement .= $_;
144 0           $endTest .= $_;
145              
146             #
147             # git rid of all the escaped tics, and then eat the
148             #
149 0           $endTest =~ s/''//sg;
150 0           $endTest =~ s/'(.*?)'//sg;
151              
152             #
153             # if there is no tick, reset for next pass, and keep going
154             #
155 0 0 0       if ( $endTest !~ /'/ && $endTest =~ /;$/ ) {
156 0           $self->runSQL( SQL=> $statement );
157 0           $statement = '';
158 0           $endTest = '';
159             }
160             }
161              
162 0           close $SQLFILE;
163 0           return;
164             }
165              
166             =head2 createSizedImages
167              
168             Create all of the derived images from a file upload based on its schema definition
169              
170             my %dataHashToUpdate = $fws->dataHash(guid=>'someGUIDThatHasImagesToUpdate');
171             $fws->createSizedImages(%dataHashToUpdate);
172              
173             If the data hash might not be correct because it is actually stored in a different table you can pass the field name you wish to update
174              
175             $fws->createSizedImages(guid=>'someGUID',image_1=>'/someImage/image.jpg');
176              
177             =cut
178              
179             sub createSizedImages {
180 0     0     my ( $self, %paramHash ) = @_;
181              
182             #
183             # going to need the current hash plus its derived schema to figure out what we should be making
184             #
185 0           my %dataHash = ( %paramHash,$self->dataHash( guid => $paramHash{guid} ) );
186 0           my %schemaHash = $self->schemaHash( $dataHash{type} );
187              
188             #
189             # if siteGUID is blank, lets get the one of the site we are on
190             #
191 0   0       $paramHash{siteGUID} ||= $self->{siteGUID};
192              
193             #
194             # bust though all the fields and see if we need to do anything with them
195             #
196 0           for my $field ( keys %dataHash ) {
197              
198             #
199             # for non secure files lets prune the 640,custom, and thumb fields
200             #
201 0           my $dataType = $schemaHash{$field}{fieldType};
202 0 0 0       if ( $dataType eq 'file' || $dataType eq 'secureFile' ) {
203              
204             #
205             # get just the file name... we will use this a few times
206             #
207 0           my $fileName = $self->justFileName( $dataHash{$field} );
208              
209             #
210             # set the file path based on secure or not
211             #
212 0           my $dirPath = $self->{filePath};
213 0 0         if ( $dataType eq 'secureFile' ) { $dirPath = $self->{fileSecurePath} }
  0            
214              
215             #
216             # check for thumb creation... if so lets do it!
217             #
218 0           for my $fieldName ( keys %schemaHash ) {
219 0 0 0       if ( $schemaHash{$fieldName}{fieldParent} eq $field && $schemaHash{$fieldName}{fieldParent} ) {
220              
221             #
222             # A directive to create a new image exists! lets figure out where and how, and do it
223             #
224 0           my $directory = $self->safeDir( $dirPath . '/' . $paramHash{siteGUID} . '/' . $paramHash{guid} );
225 0           my $newDirectory = $self->safeDir( $dirPath . '/' . $paramHash{siteGUID} . '/' . $paramHash{guid} . '/' . $fieldName );
226 0           my $newFile = $newDirectory . '/' . $fileName;
227 0           my $webFile = $self->{fileWebPath} . '/' . $paramHash{siteGUID} . '/' . $paramHash{guid} . '/' . $fieldName . '/' . $fileName;
228              
229              
230             #
231             # make the image width 100, if its not specified
232             #
233 0 0         if ( $schemaHash{$fieldName}{imageWidth} < 1 ) { $schemaHash{$fieldName}{imageWidth} = 100 }
  0            
234              
235             #
236             # Make the subdir if its not already there
237             #
238 0           $self->makeDir( $newDirectory );
239              
240             #
241             # create the new image
242             #
243 0           $self->saveImage( sourceFile => $directory . '/' . $fileName, fileName => $newFile, width => $schemaHash{$fieldName}{imageWidth} );
244              
245             #
246             # if its a secure file, we only save it from site guid on...
247             #
248 0 0         if ( $dataType eq 'secureFile' ) { $webFile = '/' . $paramHash{siteGUID} . '/' . $paramHash{guid} . '/' . $fileName }
  0            
249              
250             #
251             # if the new image is not there, then lets blank out the file
252             #
253 0 0         if ( !-e $newFile ) { $webFile = '' }
  0            
254              
255 0 0         if ( $paramHash{guid} ) {
256             #
257             # save a blank one, or save a good one
258             #
259 0           $self->saveExtra( table => 'data', siteGUID => $paramHash{siteGUID}, guid => $paramHash{guid}, field => $fieldName, value => $webFile );
260             }
261             }
262             }
263             }
264             }
265              
266 0           return;
267             }
268              
269             =head2 fileArray
270              
271             Return a directory listing into a FWS hash array reference.
272              
273             #
274             # retrieve a reference to an array of data we asked for
275             #
276             my $fileArray = $fws->fileArray( directory =>'/home/directory' );
277              
278             #
279             # loop though the array printing the files we found
280             #
281             for my $i (0 .. $#$fileArray) {
282             print $fileArray->[$i]{file}. "\n";
283             }
284              
285             =cut
286              
287             sub fileArray {
288 0     0     my ( $self, %paramHash ) =@_;
289              
290             #
291             # ensure nothing scary is in the directory
292             #
293 0           $paramHash{directory} = $self->safeDir( $paramHash{directory} );
294              
295             #
296             # pull the directory into an array
297             #
298 0           opendir ( my $DIR, $paramHash{directory} );
299 0           my @getDir = grep( !/^\.\.?$/, readdir( $DIR ));
300 0           closedir $DIR;
301              
302 0           my @fileHashArray;
303 0           foreach my $dirFile ( @getDir ) {
304 0 0         if ( -f $paramHash{directory} . '/' . $dirFile ) {
305              
306 0           my %fileHash;
307 0           $fileHash{file} = $dirFile;
308 0           $fileHash{fullFile} = $paramHash{directory} . '/' . $dirFile;
309 0           $fileHash{size} = ( stat $fileHash{fullFile} )[7];
310 0           $fileHash{date} = ( stat $fileHash{fullFile} )[9];
311              
312             #
313             # push it to the array
314             #
315 0           push ( @fileHashArray, {%fileHash} );
316             }
317             }
318 0           return \@fileHashArray;
319             }
320              
321              
322             =head2 formMapToHashArray
323              
324             Return a reference to a hash array from its human readable formMap. The format is new line delmited per array item then subsectioned by |, then name valued paired with ~ per each subsection. The first item of each | and ~ delemited section is translaged into the name hash key while the rest of the ~ delemented are convered into a name value pair.
325              
326             Example:
327              
328             title 1~type~type 1|sub title 1a|sub title 1b
329             title 2~something~extra|sub title 2a~extaKey~extra value|sub title 2b
330              
331             Will return:
332              
333             [
334             {
335             'name' => 'title 1',
336             'type' => 'type 1',
337             'optionArray' => [
338             { 'name' => 'sub title 1a' },
339             { 'name' => 'sub title 1b' }
340             ]
341             },
342             {
343             'name' => 'title 2',
344             'something' => 'extra',
345             'optionArray' => [
346             { 'name' => 'sub title 2a', 'extaKey' => 'extra value' },
347             { 'name' => 'sub title 2b' }
348             ]
349             }
350             ];
351              
352             =cut
353              
354             sub formMapToHashArray {
355 0     0     my ( $self, $obj ) = @_;
356              
357 0           my @formArray;
358 0           for my $line ( split ( /\n/, $obj ) ) {
359              
360 0           my @optionArray;
361 0           my @items = split ( /\|/, $line );
362              
363 0           my %item;
364             my %itemExt;
365 0           ( $item{name}, %itemExt ) = split( /~/, shift( @items ) );
366 0           %item = ( %itemExt, %item );
367              
368 0           while ( @items ) {
369 0           my %option;
370             my %optionExt;
371 0           ( $option{name}, %optionExt) = split( /~/, shift( @items ) );
372 0           %option = ( %optionExt, %option );
373 0           push( @optionArray, {%option} );
374             }
375              
376 0           $item{optionArray} = \@optionArray;
377 0           push( @formArray, {%item} );
378             }
379              
380 0           return \@formArray;
381             }
382              
383              
384             =head2 getEncodedBinary
385              
386             Retrive a file and convert it into a base 64 encoded binary.
387              
388             #
389             # Get the file
390             #
391             my $base64String = $fws->getEncodedBinary( $someFileWeWantToConvert );
392              
393             =cut
394              
395             sub getEncodedBinary {
396 0     0     my ( $self, $fileName ) = @_;
397              
398             #
399             #convert file to base64
400             #
401 1     1   1098 use MIME::Base64;
  1         1007  
  1         785  
402 0           my $rawFile;
403              
404 0 0         open ( my $FILE, '<', $fileName ) || $self->FWSLog( 'Could not read file: ' . $fileName );
405 0           binmode $FILE;
406 0           while ( read ( $FILE, my $buffer, 1 ) ) { $rawFile .= $buffer }
  0            
407 0           close $FILE;
408              
409 0           my $rawfile = encode_base64( $rawFile );
410 0           return $rawfile;
411             }
412              
413              
414             =head2 unpackDirectory
415              
416             The counterpart to packDirectory. This will put the files under the directory you choose from a file created by packDirectory.
417              
418             #
419             # Put the files somewhere
420             #
421             $fws->unpackDirectory( directory => $someDirectory, fileName => '/something' );
422              
423             =cut
424              
425             sub unpackDirectory {
426 0     0     my ( $self, %paramHash ) = @_;
427              
428 0           $self->FWSLog( 'Unpacking files: ' . $paramHash{fileName} . ' -> ' . $paramHash{directory} );
429              
430             #
431             # for good mesure, make the directory in case this is super fresh
432             #
433 0           $self->makeDir( $paramHash{directory} );
434              
435             #
436             # PH's for file slurping
437             #
438 0           my $fileReading;
439             my $fileName;
440              
441             #
442             # open file
443             #
444 0           open ( my $UNPACKFILE, '<', $paramHash{fileName} );
445 0           while ( <$UNPACKFILE> ) {
446 0           my $line = $_;
447              
448             #
449             # eat the return
450             #
451 0           chomp $line;
452              
453 0 0         if ( $line =~ /^FILE_END\|/ ) {
    0          
454             #
455             # save the file to that directory
456             #
457 0           $self->saveEncodedBinary( $paramHash{directory} . '/' . $fileName, $fileReading );
458              
459             #
460             # reset so when we come around again we will no we are done.
461             #
462 0           $fileName = '';
463 0           $fileReading = '';
464             }
465              
466             #
467             # if we have a file name, we are currenlty looking for a
468             # file. eat those lines up and stick them in a diffrent var
469             #
470 0           elsif ( $fileName ne '' ) { $fileReading .= $line . "\n" }
471              
472             #
473             # if this is a start of a file, lets get it set up and
474             # define the file name, the next time we go around we
475             # will be looking at the base 64
476             #
477 0 0         if ( $line =~ /^FILE\|/ ) {
478 0           ( $fileName = $line ) =~ s/.*?\|\/*(.*)\n*/$1/sg;
479 0           ( my $directory = $paramHash{directory} . '/' . $fileName ) =~ s/^(.*)\/.*/$1/sg;
480 0           $self->makeDir( $directory );
481             }
482              
483              
484             }
485 0           close $UNPACKFILE;
486              
487 0           return;
488             }
489              
490              
491              
492             =head2 uploadFile {
493              
494             Run generic upload file routine.
495              
496             $fws->uploadFile( '/directory', $FILEHANDLE, 'newfilename.ext' );
497              
498             =cut
499              
500             sub uploadFile {
501 0     0     my ( $self, $directory, $fileHandle, $fileName ) = @_;
502              
503 0           $directory = $self->safeDir( $directory );
504 0           $fileName = $self->safeFile( $fileName );
505              
506             #
507             # make the directory if its not already there
508             #
509 0           $self->makeDir( $directory );
510              
511             #
512             # get the file from the browser
513             #
514 0           my $byteReader;
515             my $buffer;
516 0           my $fileHolder;
517 0           while ( $byteReader = read( $fileHandle, $buffer, 1024 ) ) { $fileHolder .= $buffer }
  0            
518              
519             #
520             # if we meet the restrictions write the file to the filesystem and create thumbnails and icons.
521             #
522 0 0         open( my $SFILE, '>', $directory . '/' . $fileName ) || $self->FWSLog( "Could not write to file: " . $directory . "/" . $fileName );
523 0           print $SFILE $fileHolder;
524 0           close $SFILE;
525              
526 0           return $directory . "/" . $fileName;
527             }
528              
529              
530             =head2 packDirectory
531              
532             MIME encode a directory ready for a FWS export. This will exclude anything that starts with /backup, /cache, /import_ or ends with .log or .pm.someDateNumber.
533              
534             #
535             # Get the file
536             #
537             my $packedFileString = $fws->packDirectory( directory => $someDirectory );
538              
539              
540             You can also pass the key directoryList, and it will only add directories on this comma delimtied list unless the file begins with FWS.
541              
542             #
543             # Only grab fws and plugins dirs
544             #
545             my $packedFileString = $fws->packDirectory( directory => $someDirectory, directoryList => '/fws,/plugins' );
546              
547              
548             =cut
549              
550             sub packDirectory {
551 0     0     my ( $self, %paramHash ) = @_;
552              
553             #
554             # set the default base dir for parsing
555             #
556 0   0       $paramHash{baseDirectory} ||= $self->{filePath};
557 0           my $dirPath = $paramHash{baseDirectory};
558              
559             #
560             # this will need some MIME and file find action
561             #
562 1     1   17 use File::Find;
  1         2  
  1         87  
563 1     1   7 use MIME::Base64;
  1         2  
  1         855  
564              
565 0           my $FILEFILE;
566 0 0         if ( $paramHash{fileName} ) { open ( $FILEFILE, ">", $paramHash{fileName} ) }
  0            
567              
568             #
569             # PH for the return
570             #
571 0           my $packFile;
572              
573             finddepth( sub {
574             #
575             # clean up the name so it will always be consistant
576             #
577 0     0     my $fullFileName = $File::Find::name;
578 0           ( my $file = $fullFileName ) =~ s/^$dirPath//sg;
579              
580             #
581             # if we have a list of dirs, lets make sure we are ok to process this one
582             #
583 0           my $dirOK = 0;
584 0 0         if ( $paramHash{directoryList} ne '' ) {
585 0 0         map { if ( $file =~ /^$_/ ) { $dirOK = 1 } } split( /,/, $paramHash{directoryList} );
  0            
  0            
586             }
587              
588             #
589             # if we didn't pass a directoryList then we are all good for every file
590             #
591 0           else { $dirOK = 1 }
592              
593             #
594             # move though the files
595             #
596 0 0 0       if (-f $fullFileName
      0        
      0        
      0        
      0        
      0        
      0        
      0        
597             && $file !~ /^\/(import_|backup|cache|fws\/cache)/i
598             && $file !~ /(.log|\.pm\.\d+)$/i
599             && ( ( $file !~ /^\/fws\//i && $file !~ /^\/plugin\// && $file !~ /FWSElement-/ ) || !$paramHash{minMode} )
600             && ( $file !~ /FWSElement-/ || !$paramHash{noFWSBackups} )
601             && ( $dirOK || $file =~ /^FWS/ ) ) {
602              
603             #
604             # get the file
605             #
606 0           my $rawFile;
607 0 0         open ( my $FILE, '<', $fullFileName ) || $self->FWSLog( 'Can not read file: ' . $! );
608 0           binmode $FILE;
609 0           while ( read( $FILE, my $buffer, 1 ) ) { $rawFile .= $buffer }
  0            
610 0           close $FILE;
611              
612             #
613             # print the header - encode it - footer around the file
614             #
615 0           my $fileLine = 'FILE|' . $file . "\n" . encode_base64( $rawFile ) . 'FILE_END|' . $file . "\n";
616 0 0         if ( $paramHash{fileName} ne '' ) { print $FILEFILE $fileLine }
  0            
617 0           else { $packFile .= $fileLine }
618              
619             }
620 0           }, $paramHash{directory} );
621              
622 0 0         if ( $paramHash{fileName} ) { close $FILEFILE }
  0            
623              
624 0           return $packFile;
625             }
626              
627              
628             =head2 saveEncodedBinary
629              
630             Decode a base 64 encoded string and save it as its file.
631              
632             #
633             # Save the file
634             #
635             $fws->saveEncodedBinary( $someFileWeWantToSave, $theBase64EcodedString );
636              
637             =cut
638              
639             sub saveEncodedBinary {
640 0     0     my ( $self, $fileName, $rawFile ) = @_;
641 1     1   7 use MIME::Base64;
  1         3  
  1         1052  
642             #
643             # take a base64 text string, and save it to filesystem
644             #
645 0           open ( my $FILE, ">", $fileName );
646 0           binmode $FILE;
647 0           $rawFile = decode_base64( $rawFile );
648 0           print $FILE $rawFile;
649 0           close $FILE;
650 0           return;
651             }
652              
653              
654             =head2 pluginInfo
655              
656             Extract the version and description from a FWS plugin. If no version is labeled or exists it will return 0.0000 and the description will be blank.
657              
658             #
659             # get the info from the plugin
660             #
661             my %pluginInfo = $fws->pluginInfo( $somePluginFile );
662             print "Description: " . $pluginInfo{description} . "\n";
663             print "Version: " . $pluginInfo{version} . "\n";
664             print "Author: " . $pluginInfo{author} . "\n";
665             print "Author Email: " . $pluginInfo{authorEmail} . "\n";
666              
667             =cut
668              
669             sub pluginInfo {
670 0     0     my ( $self, $pluginFile ) = @_;
671              
672             #
673             # the return we will build
674             #
675 0           my %returnHash;
676              
677             #
678             # pull the file into a string so we can parse it
679             #
680             my $scriptContent;
681 0 0         if ( -e $pluginFile ) {
682 0           open ( my $SCRIPTFILE, "<", $pluginFile );
683 0           while ( <$SCRIPTFILE> ) { $scriptContent .= $_ }
  0            
684 0           close $SCRIPTFILE;
685             }
686            
687             #
688             # strip the version and header data out and create the commit button
689             #
690 0           $scriptContent =~ s/our\s\$VERSION\s*=\s*\'([\d\.]+).*?\n//s;
691 0           $returnHash{version} = $1;
692            
693             #
694             # make the version cool if its not in there
695             #
696 0           $returnHash{version} =~ s/[^\d\.]//g;
697 0   0       $returnHash{version} ||= '0.0000';
698              
699             #
700             # get description
701             #
702 0           $scriptContent =~ s/.head1\sNAME\n\n[a-zA-Z0-9]+\s-\s(.*?)\n//sg;
703 0           $returnHash{description} = $1;
704            
705             #
706             # Pull out the author
707             #
708 0           $scriptContent =~ s/.head1 AUTHOR[\n]*(.*?),\sC\<\<\s\<\s*(.*?)\s*\>\s\>\>.*//sg;
709 0           $returnHash{authorName} = $1;
710 0           ( $returnHash{authorEmail} = $2 ) =~ s/ at /\@/g;
711            
712 0           return %returnHash;
713             }
714              
715              
716             =head2 makeDir
717              
718             Make a new directory with built in safety mechanics. If the directory is not under the filePath or fileSecurePath then nothing will be created.
719              
720             $fws->makeDir( directory => $self->{filePath} . '/thisNewDir' );
721              
722             This by default is ran in safe mode, making sure directorys are only created in the filePath or fileSecurePath. This can be turned off by passing nonFWS => 1.
723              
724             =cut
725              
726             sub makeDir {
727 0     0     my ( $self, @paramArray ) = @_;
728            
729             #
730             # set paramHash if its a hash or, its in single value
731             # mode using the directory
732             #
733 0           my %paramHash;
734 0 0         if ( $#paramArray ) { %paramHash = @paramArray }
  0            
735 0           else { $paramHash{directory} = $paramArray[0] }
736            
737             #
738             # kill double ..'s so noobdy tries to leave our tight environment of security
739             #
740 0           $paramHash{directory} = $self->safeDir( $paramHash{directory} );
741            
742             #
743             # gently bail if we didn't get a directory
744             #
745 0 0         return if $paramHash{directory} eq '';
746              
747             #
748             # to make sure nothing fishiy is going on, you should only be making dirs under this area
749             #
750 0           my $filePath = $self->safeDir( $self->{filePath} );
751 0           my $fileSecurePath = $self->safeDir( $self->{fileSecurePath} );
752            
753 0 0 0       if ( $paramHash{directory} =~ /^\Q$filePath\E/ || $paramHash{directory} =~ /^\Q$fileSecurePath\E/ || $paramHash{nonFWS} ) {
      0        
754              
755             #
756             # create an array we can loop though to rebuild it making them on the fly
757             #
758 0           my @directories = split( /\//, $paramHash{directory} );
759              
760             #
761             # delete the $paramHash{directory} because we will rebuild it
762             #
763 0           $paramHash{directory} = '';
764              
765             #
766             # loop though each one making them if they need to
767             #
768 0           foreach my $thisDir ( @directories ) {
769             #
770             # make the dir and send a debug message
771             #
772 0           $paramHash{directory} .= $thisDir . '/';
773 0           mkdir( $paramHash{directory}, 0755 );
774             }
775             }
776              
777             else {
778 0           $self->FWSLog( 'makeDir() in unauthorized directory: ' . $paramHash{directory} );
779 0           return;
780             }
781 0           return $paramHash{directory};
782             }
783              
784              
785             =head2 runInit
786              
787             Run init scripts for a site. This can only be used after setSiteValues() or setSiteFiendly() is called.
788              
789             $fws->runInit();
790              
791             =cut
792              
793             sub runInit {
794 0     0     my ( $self ) = @_;
795 0           return $self->runScript( 'init' );
796             }
797              
798             =head2 runScript
799              
800             Run a FWS element script. This should not be used outside of the FWS core. There is no recursion or security checking and should not be used inside of elements to perfent possible recursion. Only use this if you are absolutly sure of the script content and its safety.
801              
802             %valueHash = $fws->runScript( 'scriptName', %valueHash );
803              
804             =cut
805              
806             sub runScript {
807 0     0     my ( $self, $guid, %valueHash ) = @_;
808              
809             #
810             # because of the nature of the element caching it is possible for one to be ran twice, to make sure lets create a testing hash
811             #
812 0           my %scriptRan;
813              
814              
815             #
816             # if this is blank, lets just not do it
817             #
818 0 0         if ( $guid ) {
819             #
820             # copy the self object to fws
821             #
822 0           my $fws = $self;
823              
824             #
825             # get the short hand hash to see whats up
826             #
827 0           my %fullElementHash = $self->_fullElementHash();
828              
829 0           for my $fullGUID ( sort { $fullElementHash{$a}{alphaOrd} <=> $fullElementHash{$b}{alphaOrd} } keys %fullElementHash) {
  0            
830              
831             #
832             # lets see if we have a match
833             #
834 0           my $liveGUID;
835 0 0         if ( $fullGUID eq $guid ) { $liveGUID = $fullElementHash{$fullGUID}{guid} }
  0            
836 0 0         if ( $fullElementHash{$fullGUID}{type} eq $guid ) { $liveGUID = $fullElementHash{$fullGUID}{guid} }
  0            
837              
838              
839             #
840             # we snagged one! lets do it!
841             #
842 0 0 0       if ( $liveGUID && !$scriptRan{$liveGUID} ) {
843             #
844             # se the flag so we don't do this one twice
845             #
846 0           $scriptRan{$liveGUID} = '1';
847              
848 0           my %elementHash = $fws->elementHash(guid=>$liveGUID);
849              
850 0 0         if ( $elementHash{scriptDevel} ) {
851             ## no critic
852 0           eval $elementHash{scriptDevel};
853             ## use critic
854 0           my $errorCode = $@;
855 0 0         if ( $errorCode ) { $self->FWSLog( $guid, $errorCode ) }
  0            
856             }
857             }
858             }
859              
860             #
861             # now put it back
862             #
863 0           $self = $fws;
864              
865             }
866             #
867             # return the valueHash back in case the script altered it
868             #
869 0           return %valueHash;
870             }
871              
872              
873             =head2 saveImage
874              
875             Save an image with a unique width or height. The file will be converted to extensions graphic type of the fileName passed. Source, fileName and either width or height is required.
876              
877             #
878             # convert this png, to a jpg that is 110x110
879             #
880             $fws->saveImage(
881             sourceFile => '/somefile.png',
882             fileName => '/theNewFile.jpg',
883             width => '110',
884             height => '110'
885             );
886              
887             #
888             # convert this png, to a jpg that is 110x110 but chop off the bottom of the height if it resizes to larget then 110 instead of shrinking or stretching
889             #
890             $fws->saveImage(
891             sourceFile =>'/somefile.png',
892             fileName =>'/theNewFile.jpg',
893             width =>'110',
894             cropHeight =>'110'
895             );
896              
897             =cut
898              
899             sub saveImage {
900             my ( $self, %paramHash ) = @_;
901              
902             #
903             # use GD in trueColor mode
904             #
905 1     1   680 use GD();
  0            
  0            
906             GD::Image->trueColor(1);
907              
908             #
909             # create new image
910             #
911             my $image;
912             if ( !( $image = GD::Image->new( $paramHash{sourceFile} ) ) ) {
913             $self->FWSLog( 'Image cannot be opened by GD for resizing, it might be currupt: ' . $paramHash{sourceFile} );
914             return 0;
915             }
916              
917             #
918             # if we truely have an image lets continue if not, lets pretend this didn't even happen
919             #
920             else {
921              
922             #
923             # get current widht/height for mat to resize
924             #
925             my ( $width, $height ) = $image->getBounds();
926              
927             #
928             # if you are binding a width and a height, then do some magic to truncate extra sizing
929             #
930             if ( $paramHash{height} && $paramHash{width} ) {
931             if ( ( $width / $paramHash{width} ) > ( $height / $paramHash{height} ) ) {
932             $paramHash{cropWidth} = $paramHash{width};
933             $paramHash{width} = '';
934             }
935             else {
936             $paramHash{cropHeight} = $paramHash{height};
937             $paramHash{height} = '';
938             }
939             }
940              
941             #
942             # do math to get new width/height
943             #
944             $paramHash{height} ||= int( $paramHash{width} / $width * $height );
945             $paramHash{width} ||= int( $paramHash{height} / $height * $width );
946              
947             #
948             # make sure size is at least 1
949             #
950             if ( $paramHash{width} < 1 ) { $paramHash{width} = 1 }
951             if ( $paramHash{height} < 1 ) { $paramHash{height} = 1 }
952              
953             #
954             # Resize image and save to a file using proper mime type
955             #
956             my $sizedImage = GD::Image->new( $paramHash{width}, $paramHash{height} );
957             $sizedImage->copyResampled( $image, 0, 0, 0, 0, $paramHash{width}, $paramHash{height}, $width, $height );
958              
959             #
960             # trim it up or this is pointless if the perpsective is already correct, but what the hay!
961             #
962             $paramHash{cropWidth} ||= $paramHash{width};
963             $paramHash{cropHeight} ||= $paramHash{height};
964              
965             #
966             # do the deed
967             #
968             my $newImage = GD::Image->new( $paramHash{cropWidth}, $paramHash{cropHeight} );
969             $newImage->copyResized( $sizedImage, 0, 0, 0, 0, $paramHash{width}, $paramHash{height}, $paramHash{width}, $paramHash{height} );
970              
971             #
972             # safe the the physical file
973             # save as what ever extnesion was passed for the name
974             #
975             open ( my $IMG, '>', $paramHash{fileName} ) || $self->FWSLog( 'Could not write to file: ' . $! );
976             binmode $IMG;
977             if ( $paramHash{fileName} =~ /\.(jpg|jpeg|jpe)$/i ) { print $IMG $newImage->jpeg() }
978             if ( $paramHash{fileName} =~ /\.png$/i ) { print $IMG $newImage->png() }
979             if ( $paramHash{fileName} =~ /\.gif$/i ) { print $IMG $newImage->gif() }
980             close $IMG;
981             }
982             return 1;
983             }
984              
985             =head2 FWSDecrypt
986              
987             Decrypt data if a site has the proper configuration
988              
989             my $decryptedData = $fws->FWSDecrypt( 'alsdkjfalkj230948lkjxldkfj' );
990              
991             =cut
992              
993             sub FWSDecrypt {
994             my ( $self, $encData )= @_;
995              
996             if ( $self->{encryptionType} =~ /blowfish/i ) {
997             require Crypt::Blowfish;
998             Crypt::Blowfish->import();
999             my $cipher1 = Crypt::Blowfish->new( substr( $self->{encryptionKey}, 0, 56 ) );
1000             my $cipher2 = Crypt::Blowfish->new( substr( $self->{encryptionKey}, 57, 56 ) );
1001             my $cipher3 = Crypt::Blowfish->new( substr( $self->{encryptionKey}, 111, 56 ) );
1002             my $data = pack( "H*", $encData );
1003             my $dec;
1004             while ( length( $data ) > 0 ) {
1005             my $len = length( $data );
1006             $dec .= $cipher3->decrypt( substr( $data, 0, 8 ) );
1007             if ( $len > 8 ) { $data = substr( $data, 8 ) } else { $data = '' }
1008             }
1009             $data = $dec;
1010             $dec = '';
1011             while ( length( $data ) > 0 ) {
1012             my $len = length( $data );
1013             $dec .= $cipher2->decrypt( reverse( substr( $data, 0, 8 ) ) );
1014             if ( $len > 8 ) { $data = substr( $data, 8 ) } else { $data = '' }
1015             }
1016             $data = $dec;
1017             $dec = '';
1018             my $size = substr( $data, 0, 8 );
1019             $data = substr( $data, 8 );
1020             while ( length( $data ) > 0 ) {
1021             my $len = length( $data );
1022             $dec .= $cipher1->decrypt( substr( $data, 0, 8 ) );
1023             if ( $len > 8 ) { $data = substr( $data, 8 ) } else { $data = '' }
1024             }
1025             $encData = substr( $dec, 0, $size );
1026             }
1027             return $encData;
1028             }
1029              
1030              
1031              
1032             =head2 FWSEncrypt
1033              
1034             Encrypt data if a site has the proper configuration
1035              
1036             my $encryptedData = $fws->FWSEncrypt( 'encrypt this stuff' );
1037              
1038             =cut
1039              
1040             sub FWSEncrypt {
1041             my ( $self, $data )= @_;
1042             my $enc;
1043              
1044             if ( $self->{encryptionType} =~ /blowfish/i ) {
1045             require Crypt::Blowfish;
1046             Crypt::Blowfish->import();
1047             my $cipher1 = Crypt::Blowfish->new( substr( $self->{encryptionKey}, 0, 56 ) );
1048             my $cipher2 = Crypt::Blowfish->new( substr( $self->{encryptionKey}, 57, 56 ) );
1049             my $cipher3 = Crypt::Blowfish->new( substr( $self->{encryptionKey}, 111, 56 ) );
1050             my $fullLength = length( $data );
1051             while ( length( $data ) > 0 ) {
1052             my $len = length( $data );
1053             if ( $len < 8 ) { $data .= "\000"x(8-$len) }
1054             $enc .= $cipher1->encrypt( substr( $data, 0, 8 ) );
1055             if ( $len > 8 ) { $data = substr( $data, 8 ) } else { $data = '' }
1056             }
1057             $fullLength = sprintf("%8d", $fullLength);
1058             $fullLength=~ tr/ /0/;
1059             $data = $fullLength.$enc;
1060             $enc = '';
1061             while ( length( $data ) > 0 ) {
1062             my $len = length( $data );
1063             $enc .= $cipher2->encrypt( reverse( substr( $data, 0, 8 ) ) );
1064             if ( $len > 8 ) { $data = substr( $data, 8 ) } else { $data = '' }
1065             }
1066             $data = $enc;
1067             $enc = '';
1068             while ( length( $data ) > 0 ) {
1069             my $len = length( $data );
1070             $enc .= $cipher3->encrypt( substr( $data, 0, 8 ) );
1071             if ( $len > 8 ) {$data = substr( $data, 8 ) } else { $data = '' }
1072             }
1073             $data = unpack( "H*", $enc );
1074             }
1075             return $data;
1076             }
1077              
1078              
1079             =head2 tailFile
1080              
1081             Read x number of lines from the end of a file. If lines are not specified it will default to 10.
1082              
1083             #
1084             # Print last 10 lines of the FWS.log file
1085             #
1086             print $fws->tailFile( lines => 50, fileName => $fws->{fileSecurePath} . "/FWS.log" );
1087              
1088             =cut
1089              
1090             sub tailFile {
1091             my ( $self, %paramHash ) = @_;
1092              
1093             #
1094             # set the default ot 10 lines
1095             #
1096             $paramHash{lines} ||= 10;
1097              
1098             #
1099             # open the file
1100             #
1101             open ( my $TAILFILE, "<", $paramHash{fileName} );
1102              
1103              
1104             #
1105             # set our cursor to to know where we are at
1106             #
1107             my $lineCursor;
1108             my $tailReturn;
1109              
1110             while ( <$TAILFILE> ) {
1111              
1112             #
1113             # advance the cursor and add the next line to the end
1114             #
1115             $tailReturn .= $_;
1116             $lineCursor++;
1117              
1118             #
1119             # eat the first line if we have what we needed
1120             #
1121             if ( $lineCursor > $paramHash{lines} ) { $tailReturn =~ s/^(.*?)\n// }
1122             }
1123              
1124             close $TAILFILE;
1125              
1126             return $tailReturn;
1127             }
1128              
1129              
1130             =head2 FWSLog
1131              
1132             Append something to the FWS.log file if FWSLogLevel is set to 1 which is default.
1133              
1134             #
1135             # Soemthing is happening
1136             #
1137             $fws->FWSLog("this is happening\nthis is a new log line");
1138              
1139             If a multi line string is passed it will break it up in to more than one log entries.
1140              
1141             =cut
1142              
1143             sub FWSLog{
1144             my ( $self, $module, $errorText ) = @_;
1145             if ( $self->{FWSLogLevel} > 0 ) {
1146             open ( my $FILE, ">>", $self->{fileSecurePath} . "/FWS.log" ) || return 0;
1147              
1148             #
1149             # if you only pass it one thing, lets set it up so it will display
1150             #
1151             if ( !$errorText ) {
1152             $errorText = $module;
1153             $module = 'FWS';
1154             }
1155              
1156             #
1157             # split up the lines so we can pass a whole bunch and have them format each on one line
1158             #
1159             my @resultLines = split /\n/, $errorText;
1160             foreach my $resultLine ( @resultLines ) {
1161             if ( $resultLine ) {
1162             print $FILE $ENV{REMOTE_ADDR} . " - [".$self->formatDate( format => "apache" ) . "] " . $module . ": " . $resultLine . " [" . $ENV{SERVER_NAME} . $ENV{REQUEST_URI} . "]\n";
1163             }
1164             }
1165             close $FILE;
1166             }
1167              
1168             return $module;
1169             }
1170              
1171              
1172             =head2 SQLLog
1173              
1174             Append something to the SQL.log file if SQLLogLevel is set to 1 or 2. Level 1 will log anything that updates a database record, and level 2 will log everything. In good practice this should not be used, as all SQL statements are ran via the runSQL method which applies SQLLog.
1175              
1176             #
1177             # Soemthing is happening
1178             #
1179             $fws->SQLLog( $theSQLStatement );
1180              
1181             =cut
1182              
1183              
1184             sub SQLLog{
1185             my ( $self, $SQL ) = @_;
1186             if ( $self->{SQLLogLevel} > 0 ) {
1187             open ( my $FILE, ">>", $self->{fileSecurePath}."/SQL.log" ) || return 0;
1188             if ( ( $self->{SQLLogLevel} eq '1' && ( $SQL =~/^insert/i || $SQL=~/^delete/i || $SQL=~/^update/i || $SQL=~/^alter/i ) ) || $self->{SQLLogLevel} eq '2' ) {
1189             print $FILE $ENV{REMOTE_ADDR} . " - [" . $self->formatDate( format => "apache" ) . "] " . $SQL . " [" . $ENV{SERVER_NAME} . $ENV{REQUEST_URI} . "]\n";
1190             }
1191             close $FILE;
1192             }
1193             return 1;
1194             }
1195              
1196              
1197             sub _saveElementFile {
1198             my ( $self, $guid, $siteGUID, $table, $ext, $content ) = @_;
1199              
1200             #
1201             # for security reasons lets make sure ext is safe
1202             #
1203             if ( ( $ext eq 'css' || $ext eq 'js' ) && ( $table eq 'element' || $table eq 'templates' || $table eq 'site' || $table eq 'page' ) ) {
1204              
1205             #
1206             # if siteGUID is blank, lets get the one of the site we are on
1207             #
1208             $siteGUID ||= $self->{siteGUID};
1209              
1210             #
1211             # set the directory and make it if it might not exist
1212             #
1213             my $directory = $self->{filePath}."/".$siteGUID."/".$guid;
1214             $self->makeDir( $directory );
1215              
1216             #
1217             # set the timestamp so we will add this to the file name for the cachable named ones
1218             #
1219             my $timeStamp = time();
1220              
1221             #
1222             # for security lets get rid of anything dangerous
1223             #
1224             my $name = $self->safeDir( $directory . "/FWSElement." . $ext );
1225             my $backupName = $self->safeDir( $directory . "/FWSElement-" . $timeStamp . "." . $ext );
1226              
1227             #
1228             # save the file to the FS
1229             #
1230             open ( my $FILE, ">", $name ) || $self->FWSLog( "Could not write to file: " . $name );
1231             print $FILE $content;
1232             close $FILE;
1233              
1234             #
1235             # update Key field is guid, unless we are talking about the "site" table, then it is "siteGUID"
1236             #
1237             if ( $table eq 'site' ) { $guid = $siteGUID }
1238              
1239             #
1240             # if it is blank, then we are actually here to delete it
1241             #
1242             if ( !$content ) {
1243             unlink $name;
1244             if ( $table eq 'page' ) { $self->saveExtra( table => 'data', siteGUID => $siteGUID, guid => $guid, field => $ext . 'Devel', value => '0' ) }
1245             else { $self->runSQL( SQL => "update " . $self->safeSQL( $table ) . " set " . $self->safeSQL( $ext ) . "_devel=0 where guid='" . $self->safeSQL( $guid ) . "'" ) }
1246             }
1247             else {
1248             if ( $table eq 'page' ) { $self->saveExtra( table => 'data', siteGUID => $siteGUID, guid => $guid, field => $ext . 'Devel', value => $timeStamp ) }
1249             else { $self->runSQL( SQL => "update " . $self->safeSQL( $table ) . " set " . $self->safeSQL( $ext ) . "_devel=" . $self->safeSQL( $timeStamp ) . " where guid='" . $self->safeSQL( $guid ) . "'" ) }
1250              
1251             #
1252             # save the backupName one
1253             #
1254             open ( my $FILE, ">", $backupName ) || $self->FWSLog( "Could not write to file: " . $backupName );
1255             print $FILE $content;
1256             close $FILE;
1257             }
1258              
1259             #
1260             # Remove JS and CSS Cache
1261             #
1262             $self->flushWebCache();
1263             }
1264             return;
1265             }
1266              
1267              
1268             sub _versionData {
1269             my ( $self, $location, $url, $saveVersion ) = @_;
1270             my @metaData;
1271              
1272             #
1273             # vesrion tags all end it .txt and start with current_
1274             #
1275             $url = "current_" . $url . ".txt";
1276             if ( $location =~ /live/i ) {
1277              
1278             #
1279             # get the major ver
1280             #
1281             my $liveDistServer = $self->{FWSServer}."/fws_".$self->{FWSVersion};
1282              
1283             require LWP::UserAgent;
1284             my $browser = LWP::UserAgent->new();
1285             my $response = $browser->get( $liveDistServer."/".$url );
1286             if (!$response->is_success ) { return "Unavailable" }
1287             @metaData = split(/\n/,$response->content);
1288             }
1289             else {
1290             open (my $FILE, "<", $self->fileSecurePath . "/" . $url ) || return "";
1291             @metaData = <$FILE>;
1292             close $FILE;
1293             }
1294              
1295             #
1296             # get the major ver of the version name we recived
1297             #
1298             my $majorVer = shift( @metaData );
1299             my $build = shift( @metaData );
1300             if ( $saveVersion ) {
1301             open ( my $FILE, '>', $self->{fileSecurePath} . "/" . $url );
1302             print $FILE $majorVer."\n" . $build."\n";
1303             close $FILE;
1304             }
1305              
1306             my $returnString = $majorVer;
1307             if ( $build ) { $returnString .= ' Build '.$build }
1308             $returnString =~ s/\n//sg;
1309              
1310             my @verSplit = split( /\./, $majorVer );
1311             $majorVer = $verSplit[0] . '.' . $verSplit[1];
1312              
1313             return ( $returnString, $majorVer, $build );
1314             }
1315              
1316             sub _getElementEditText {
1317             my ( $self, $siteGUID, $guid, $ext )= @_;
1318              
1319             #
1320             # get a file that is to edited in ACE from the elements. only works on js and css files
1321             #
1322             my $fileText;
1323             if ( $ext eq 'js' || $ext eq 'css' ) {
1324             $self->FWSLog( "Opening Element File: " . $self->{filePath} . '/' . $siteGUID . '/' . $guid . '/FWSElement.' . $ext );
1325             my $file = $self->safeDir( $self->{filePath} . '/' . $siteGUID . '/' . $guid . '/FWSElement.' . $ext);
1326             if ( -e $file ) {
1327             open ( my $FILE, "<", $file );
1328             while ( <$FILE> ) { $fileText .= $_ }
1329             close $FILE;
1330             }
1331             }
1332             return $fileText;
1333             }
1334              
1335              
1336              
1337             =head1 AUTHOR
1338              
1339             Nate Lewis, C<< >>
1340              
1341             =head1 BUGS
1342              
1343             Please report any bugs or feature requests to C, or through
1344             the web interface at L. I will be notified, and then you'll
1345             automatically be notified of progress on your bug as I make changes.
1346              
1347              
1348              
1349              
1350             =head1 SUPPORT
1351              
1352             You can find documentation for this module with the perldoc command.
1353              
1354             perldoc FWS::V2::File
1355              
1356              
1357             You can also look for information at:
1358              
1359             =over 4
1360              
1361             =item * RT: CPAN's request tracker (report bugs here)
1362              
1363             L
1364              
1365             =item * AnnoCPAN: Annotated CPAN documentation
1366              
1367             L
1368              
1369             =item * CPAN Ratings
1370              
1371             L
1372              
1373             =item * Search CPAN
1374              
1375             L
1376              
1377             =back
1378              
1379              
1380             =head1 LICENSE AND COPYRIGHT
1381              
1382             Copyright 2013 Nate Lewis.
1383              
1384             This program is free software; you can redistribute it and/or modify it
1385             under the terms of either: the GNU General Public License as published
1386             by the Free Software Foundation; or the Artistic License.
1387              
1388             See http://dev.perl.org/licenses/ for more information.
1389              
1390              
1391             =cut
1392              
1393             1; # End of FWS::V2::File