File Coverage

blib/lib/Pepper/Utilities.pm
Criterion Covered Total %
statement 92 282 32.6
branch 31 192 16.1
condition 10 90 11.1
subroutine 17 27 62.9
pod 0 16 0.0
total 150 607 24.7


line stmt bran cond sub pod time code
1             package Pepper::Utilities;
2              
3             $Pepper::Utilities::VERSION = '1.3';
4              
5             # for utf8 support with JSON
6 1     1   636 use utf8;
  1         16  
  1         6  
7 1     1   567 use Encode qw( encode_utf8 );
  1         16216  
  1         91  
8              
9             # for encoding and decoding JSON
10 1     1   8 use Cpanel::JSON::XS;
  1         3  
  1         64  
11              
12             # for logging via logger()
13 1     1   1520 use Path::Tiny;
  1         11664  
  1         75  
14 1     1   806 use Data::Dumper;
  1         6535  
  1         111  
15              
16             # need some date/time toys
17 1     1   614 use Date::Format;
  1         13126  
  1         76  
18 1     1   1625 use DateTime;
  1         545262  
  1         60  
19 1     1   2061 use Date::Manip::Date;
  1         91403  
  1         60  
20              
21             # support template toolkit templates
22 1     1   770 use Template;
  1         19736  
  1         44  
23              
24             # for being a good person
25 1     1   8 use strict;
  1         3  
  1         21  
26 1     1   6 use warnings;
  1         3  
  1         3809  
27              
28             sub new {
29 1     1 0 1006 my ($class, $args) = @_;
30              
31             # make the object
32             my $self = bless {
33             'request' => $$args{request},
34             'response' => $$args{response},
35             'json_coder' => Cpanel::JSON::XS->new->utf8->allow_nonref->allow_blessed,
36             'config_file' => $ENV{HOME}.'/pepper/config/pepper.cfg',
37 1         31 'pepper_directory' => $ENV{HOME}.'/pepper',
38             }, $class;
39            
40             # read in the system configuration
41 1 50       8 $self->read_system_configuration() if !$$args{skip_config};
42            
43 1         4 return $self;
44             }
45              
46             ### START METHODS FOR GENERATING RESPONSES AND LOGS
47              
48             # method to deliver html & json out to the client;
49             # this must be in here to be available even if not in plack mode
50             sub send_response {
51 0     0 0 0 my ($self, $content, $stop_here, $content_type, $content_filename) = @_;
52              
53             # if not in Plack/PSGI land, we will skip working with $self->{response}
54              
55             # $content needs to be one of a text/html string, an ARRAYREF or a HASHREF
56 0         0 my $ref_type = ref($content);
57            
58 0         0 my ($access_message, $error_id, $access_error, $die_text, $display_error_message, $html_generator, $error_html);
59            
60 0   0     0 $stop_here ||= 0; # don't want an uninitiated value
61 0 0 0     0 if ($stop_here == 1 || $stop_here == 3) { # if $stop_here is a 1 or 3, we are stopping due to an error condition
62             # if it is plain text, we should most likely log the error message sent to us
63             # and just present the error ID
64             # exception is if you're a developer running a script; in that case,
65             # set the 'development_server' in your system configuration
66            
67             # note access errors for display below
68 0 0       0 $access_error = 1 if $content =~ /^Access\:/;
69              
70 0 0       0 if (length($content)) {
71 0         0 $error_id = $self->logger($content,'fatals'); # 'these errors go into the 'fatals' log
72             # send an accurate response code
73 0         0 $self->{response}->status(500);
74              
75             # unless we are on the dev server or it's the no-app message, present the error ID instead
76 0 0 0     0 if ($self->{config}{development_server} eq 'Y' || $content =~ /^No application exists/) {
77 0         0 $display_error_message = $content;
78             # need period at the end
79 0 0       0 $display_error_message .= '.' if $display_error_message !~ /(\.|\?|\!)$/;
80             } else { # hide the error
81 0         0 $content = 'Execution failed; error ID: '.$error_id."\n";
82 0         0 $ref_type = ''; # make sure it gets treated as plain text;
83             }
84              
85             # if we are in API mode, let's send back JSON
86 0 0       0 if ($self->{auth_token}) {
    0          
87 0         0 $ref_type = "HASH" ;
88 0         0 $content = {
89             'status' => 'Error',
90             'error_id' => $error_id,
91             'display_error_message' => $display_error_message,
92             };
93             # developers see the actual message
94 0 0       0 $$content{display_error_message} = $display_error_message if $display_error_message;
95              
96             # if we are in Web UI mode, pipe it out to the user as HTML;
97             } elsif ($self->{request}) {
98            
99 0         0 $self->send_response($content);
100 0 0       0 if ($self->{db}) { # if we connected to the DB, end our transaction
101 0         0 $self->{db}->do_sql('rollback');
102             }
103              
104             # do not continue if in the inner eval{} loop
105 0 0       0 if ($stop_here == 1) {
106 0         0 die 'Execution stopped: '.$content;
107             } else { # if $stop_here == 3, then we are in a 'superfatal' from pepper.psgi
108 0         0 return;
109             }
110            
111             }
112              
113             }
114             }
115              
116             # if they sent a valid content type, no need to change it
117 0 0 0     0 if ($content_type && $content_type =~ /\//) {
    0 0        
    0 0        
    0          
    0          
    0          
118             # nothing to do here
119              
120             } elsif ($ref_type eq "HASH" || $ref_type eq "ARRAY") { # make it into json
121 0         0 $content_type = 'application/json';
122 0         0 $content = $self->json_from_perl($content);
123              
124             } elsif ($content =~ /^\/\/ This is Javascript./) { # it is 99% likely to be Javascript
125 0         0 $content_type = 'text/javascript';
126              
127             } elsif ($content =~ /^\/\* This is CSS./) { # it is 99% likely to be CSS
128 0         0 $content_type = 'text/css';
129              
130             } elsif ($content =~ /<\S+>/) { # it is 99% likely to be HTML
131 0         0 $content_type = 'text/html';
132              
133             } elsif (!$ref_type && length($content)) { # it is plain text
134 0         0 $content_type = 'text/plain';
135              
136             } else { # anything else? something of a mistake, panic a little
137 0         0 $content_type = 'text/plain';
138 0         0 $content = 'ERROR: The resulting content was not deliverable.';
139              
140             }
141              
142             # if in Plack, pack the response for delivery
143 0 0       0 if ($self->{response}) {
144 0         0 $self->{response}->content_type($content_type);
145             # is this an error? Change from 200 to 500, if not done so already
146 0 0 0     0 if ($content =~ /^(ERROR|Execution failed)/ && $self->{response}->status() eq '200') {
147 0         0 $self->{response}->status(500);
148             }
149 0 0 0     0 if ($content_filename && $content_type !~ /^image/) {
150 0         0 $self->{response}->header('Content-Disposition' => 'attachment; filename="'.$content_filename.'"');
151             }
152 0         0 $self->{response}->body($content);
153            
154             } else { # print to stdout
155 0         0 print $content;
156             }
157              
158 0 0       0 if ($stop_here == 1) { # if they want us to stop here, do so; we should be in an eval{} loop to catch this
159 0         0 $die_text = "Execution stopped.";
160 0 0       0 $die_text .= '; Error ID: '.$error_id if $error_id;
161 0 0       0 $self->{db}->do_sql('rollback') if $self->{db}; # end our transaction
162 0         0 die $die_text;
163             }
164            
165             }
166              
167             # subroutine to process a template via template toolkit
168             # this is for server-side processing of templates
169             sub template_process {
170 1     1 0 28 my ($self, $args) = @_;
171             # $$args can contain: include_path, template_file, template_text, template_vars, send_out, save_file, stop_here
172             # it *must* include either template_text or template_file
173              
174             # declare vars
175 1         5 my ($output, $tt, $tt_error);
176              
177             # default include path
178 1 50       6 if (!$$args{include_path}) {
    0          
179 1         5 $$args{include_path} = $self->{pepper_directory}.'/template/';
180             } elsif ($$args{include_path} !~ /\/$/) { # make sure of trailing /
181 0         0 $$args{include_path} .= '/';
182             }
183              
184             # $$args{tag_style} = 'star', 'template' or similiar
185             # see https://metacpan.org/pod/Template#TAG_STYLE
186              
187             # default tag_style to regular, [% %]
188 1   50     17 $$args{tag_style} ||= 'template';
189              
190             # crank up the template toolkit object, and set it up to save to the $output variable
191 1         2 $output = '';
192             $tt = Template->new({
193             ENCODING => 'utf8',
194             INCLUDE_PATH => $$args{include_path},
195             OUTPUT => \$output,
196             TAG_STYLE => $$args{tag_style},
197 1   33     27 }) || $self->send_response("$Template::ERROR",1);
198              
199             # process the template
200 1 50       23931 if ($$args{template_file}) {
    50          
201 0         0 $tt->process( $$args{template_file}, $$args{template_vars}, $output, {binmode => ':encoding(utf8)'} );
202              
203             } elsif ($$args{template_text}) {
204 1         11 $tt->process( \$$args{template_text}, $$args{template_vars}, $output, {binmode => ':encoding(utf8)'} );
205              
206             } else { # one or the other
207 0         0 $self->send_response("Error: you must provide either template_file or template_text",1);
208             }
209              
210             # make sure to throw error if there is one
211 1         30721 $tt_error = $tt->error();
212 1 50       32 $self->send_response("Template Error in $$args{template_file}: $tt_error",1) if $tt_error;
213              
214             # send it out to the client, save to the filesystem, or return to the caller
215 1 50       7 if ($$args{send_out}) { # output to the client
    50          
216              
217             # the '2' tells mr_zebra to avoid logging an error
218 0         0 $self->send_response($output,2);
219              
220             } elsif ($$args{save_file}) { # save to the filesystem
221 0         0 $self->filer( $$args{save_file}, 'write', $output);
222 0         0 return $$args{save_file}; # just kick back the file name
223              
224             } else { # just return
225 1         6 return $output;
226             }
227             }
228              
229             # method to log messages under the 'log' directory
230             sub logger {
231             # takes three args: the message itself (required), the log_type (optional, one word),
232             # and an optional log location/directory
233 0     0 0 0 my ($self, $log_message, $log_type, $log_directory) = @_;
234              
235             # return if no message sent; no point
236 0 0       0 return if !$log_message;
237              
238             # default is 'errors' log type
239 0   0     0 $log_type ||= 'errors';
240              
241             # no spaces or special chars in that $log_type
242 0         0 $log_type =~ s/[^a-z0-9\_]//gi;
243              
244 0         0 my ($error_id, $todays_date, $current_time, $log_file, $now);
245              
246             # how about a nice error ID
247 0         0 $error_id = $self->random_string(15);
248              
249             # what is today's date and current time
250 0         0 $now = time(); # this is the unix epoch / also a quick-find id of the error
251 0         0 $todays_date = $self->time_to_date($now,'to_date_db','utc');
252 0         0 $current_time = $self->time_to_date($now,'to_datetime_iso','utc');
253 0         0 $current_time =~ s/\s//g; # no spaces
254              
255             # target log file - did they provide a target log_directory?
256 0 0 0     0 if ($log_directory && -d $log_directory) { # yes
257 0         0 $log_file = $log_directory.'/'.$log_type.'-'.$todays_date.'.log';
258             } else { # nope, take default
259 0         0 $log_file = $self->{pepper_directory}.'/log/'.$log_type.'-'.$todays_date.'.log';
260             }
261              
262             # sometimes time() adds a \n
263 0         0 $log_message =~ s/\n//;
264              
265             # if they sent a hash or array, it's a developer doing testing. use Dumper() to output it
266 0 0 0     0 if (ref($log_message) eq 'HASH' || ref($log_message) eq 'ARRAY') {
267 0         0 $log_message = Dumper($log_message);
268             }
269              
270             # if we have the plack object (created via pack_luggage()), append to the $log_message
271 0 0       0 if ($self->{request}) {
272 0         0 $log_message .= ' | https://'.$self->{request}->env->{HTTP_HOST}.$self->{request}->request_uri();
273             }
274              
275             # append to our log file via Path::Tiny
276 0         0 path($log_file)->append_raw( 'ID: '.$error_id.' | '.$current_time.': '.$log_message."\n" );
277              
278             # return the code/epoch for an innocent-looking display and for fast lookup
279 0         0 return $error_id;
280             }
281              
282             ### START GENERAL UTILITIES
283              
284             # simple routine to get a DateTime object for a timestamp, e.g. 2016-09-04 16:30
285             sub get_datetime_object {
286 1     1 0 3 my ($self, $time_string, $time_zone_name) = @_;
287              
288             # default timezone is New York
289 1         3 $time_zone_name = $self->{time_zone_name};
290 1   50     4 $time_zone_name ||= 'America/New_York';
291              
292 1         2 my ($dt, $year, $month, $day, $hour, $minute, $second);
293              
294             # be willing to just accept the date and presume midnight
295 1 50       9 if ($time_string =~ /^\d{4}-\d{2}-\d{2}$/) {
296 0         0 $time_string .= ' 00:00:00';
297             }
298              
299             # i will generally just send minutes; we want to support seconds too, and default to 00 seconds
300 1 50       6 if ($time_string =~ /\s\d{2}:\d{2}$/) {
301 1         4 $time_string .= ':00';
302             }
303              
304             # if that timestring is not right, just get one for 'now'
305 1 50       6 if ($time_string !~ /^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$/) {
306              
307 0         0 $dt = DateTime->from_epoch(
308             epoch => time(),
309             time_zone => $time_zone_name,
310             );
311              
312             # otherwise, get a custom datetime object
313             } else {
314              
315             # have to slice-and-dice it a bit to make sure DateTime is happy
316 1         6 $time_string =~ s/-0/-/g;
317 1         11 ($year,$month,$day,$hour,$minute,$second) = split /-|\s|:/, $time_string;
318 1         5 $hour =~ s/^0//;
319 1         3 $minute =~ s/^0//;
320              
321             # try to set up the DateTime object, wrapping in eval in case they send an invalid time
322             # (which happens if you go for 2am on a 'spring-forward' day
323 1         5 eval {
324 1         14 $dt = DateTime->new(
325             year => $year,
326             month => $month,
327             day => $day,
328             hour => $hour,
329             minute => $minute,
330             second => $second,
331             time_zone => $time_zone_name,
332             );
333             };
334              
335 1 50       736 if ($@) { # if they called for an invalid time, just move ahead and hour and try again
336 0         0 $hour++;
337 0         0 $dt = DateTime->new(
338             year => $year,
339             month => $month,
340             day => $day,
341             hour => $hour,
342             minute => $minute,
343             second => $second,
344             time_zone => $time_zone_name,
345             );
346             }
347              
348             }
349              
350             # send it out
351 1         3 return $dt;
352             }
353              
354             # method to read/write/append to a file via Path::Tiny
355             sub filer {
356             # required arg is the full path to the file
357             # optional second arg is the operation: read, write, or append. default to 'read'
358             # optional third arg is the content for write or append operations
359 0     0 0 0 my ($self, $file_location, $operation, $content) = @_;
360              
361             # return if no good file path
362 0 0       0 return if !$file_location;
363              
364             # default operation is 'read'
365 0 0 0     0 $operation = 'read' if !$operation || $operation !~ /read|write|append|basename/;
366              
367             # return if write or append and no content
368 0 0 0     0 return if $operation !~ /read|basename/ && !$content;
369              
370             # do the operations
371 0 0       0 if ($operation eq 'read') {
    0          
    0          
    0          
372              
373 0         0 $content = path($file_location)->slurp_raw;
374 0         0 return $content;
375              
376             } elsif ($operation eq 'write') {
377              
378 0         0 path($file_location)->spew_raw( $content );
379              
380             } elsif ($operation eq 'append') {
381              
382             # make sure the new content ends with a \n
383 0 0       0 $content .= "\n" if $content !~ /\n$/;
384              
385 0         0 path($file_location)->append_raw( $content );
386              
387             } elsif ($operation eq 'basename') {
388              
389 0         0 return path($file_location)->basename;
390             }
391              
392             }
393              
394              
395             # two json translating methods using the great JSON module
396             # First, make perl data structures into JSON objects
397             sub json_from_perl {
398 1     1 0 613 my ($self, $data_ref) = @_;
399              
400             # for this, we shall go UTF8
401 1         39 return $self->{json_coder}->encode( $data_ref );
402             }
403              
404             # Second, make JSON objects into Perl structures
405             sub json_to_perl {
406 1     1 0 8 my ($self, $json_text) = @_;
407              
408             # first, let's try via UTF-8 decoding
409 1         10 my $json_text_ut8 = encode_utf8( $json_text );
410 1         3 my $perl_hashref = {};
411 1         2 eval {
412 1         20 $perl_hashref = $self->{json_coder}->decode( $json_text_ut8 );
413             };
414              
415 1         3 return $perl_hashref;
416             }
417              
418             # utility to generate a random string
419             sub random_string {
420 0     0 0 0 my ($self, $length, $numbers_only) = @_;
421              
422             # default that to 10
423 0   0     0 $length ||= 10;
424              
425 0         0 my (@chars,$string);
426              
427 0 0       0 if ($numbers_only) { # what they want...
428 0         0 @chars = ('0'..'9');
429             } else { # both
430 0         0 @chars = ('0'..'9', 'A'..'F');
431             }
432              
433 0         0 while ($length--) {
434 0         0 $string .= $chars[rand @chars]
435             };
436              
437 0         0 return $string;
438             }
439              
440              
441             # method to read a JSON file into a hashref
442             sub read_json_file {
443 0     0 0 0 my ($self, $json_file_path) = @_;
444            
445             # we shall give them an empty hashref if nothing else
446 0 0 0     0 return {} if !$json_file_path || !(-e $json_file_path);
447            
448 0         0 my $json_content = $self->filer($json_file_path);
449              
450 0 0       0 return {} if !$json_content;
451            
452 0         0 return $self->json_to_perl($json_content);
453            
454             }
455              
456             # method to save JSON into a file
457             sub write_json_file {
458 0     0 0 0 my ($self, $json_file_path, $data_structure) = @_;
459            
460 0 0 0     0 return if !$json_file_path || ref($data_structure) !~ /ARRAY|HASH/;
461            
462             # writing one liners like this does not make me feel beautiful
463 0         0 $self->filer($json_file_path, 'write', $self->json_from_perl($data_structure) );
464              
465             }
466              
467             # start the timeToDate method, where we convert between UNIX timestamps and human-friendly dates
468             sub time_to_date {
469             # declare vars & grab args
470 1     1 0 9 my ($self, $timestamp, $task, $time_zone_name) = @_;
471 1         3 my ($day, $dt, $diff, $month, $templ, $year);
472              
473             # default timezone to UTC if no timezone sent or set
474             # if they sent a 'utc', force it to be Etc/GMT -- this is for the logger
475 1 50 33     6 $time_zone_name = 'Etc/GMT' if !$time_zone_name || $time_zone_name eq 'utc';
476              
477             # allow them to set a default time zone by setting $pepper->{utilities}{time_zone_name}
478             # or $ENV{PERL_DATETIME_DEFAULT_TZ}
479 1   0     4 $time_zone_name ||= $self->{time_zone_name} || $ENV{PERL_DATETIME_DEFAULT_TZ};
      33        
480              
481             # set the time zone if not set
482 1   33     15 $self->{time_zone_name} ||= $time_zone_name;
483              
484             # fix up timestamp as necessary
485 1 50       8 if (!$timestamp) { # empty timestamp --> default to current timestamp
    50          
486 0         0 $timestamp = time();
487             } elsif ($timestamp =~ /\,/) { # human date...make it YYYY-MM-DD
488 0         0 ($month,$day,$year) = split /\s/, $timestamp; # get its pieces
489             # turn the month into a proper number
490 0 0       0 if ($month =~ /Jan/) { $month = "01";
  0 0       0  
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
491 0         0 } elsif ($month =~ /Feb/) { $month = "02";
492 0         0 } elsif ($month =~ /Mar/) { $month = "03";
493 0         0 } elsif ($month =~ /Apr/) { $month = "04";
494 0         0 } elsif ($month =~ /May/) { $month = "05";
495 0         0 } elsif ($month =~ /Jun/) { $month = "06";
496 0         0 } elsif ($month =~ /Jul/) { $month = "07";
497 0         0 } elsif ($month =~ /Aug/) { $month = "08";
498 0         0 } elsif ($month =~ /Sep/) { $month = "09";
499 0         0 } elsif ($month =~ /Oct/) { $month = "10";
500 0         0 } elsif ($month =~ /Nov/) { $month = "11";
501 0         0 } elsif ($month =~ /Dec/) { $month = "12"; }
502              
503             # remove the comma from the date and make sure it has two digits
504 0         0 $day =~ s/\,//;
505 0 0       0 $day = '0'.$day if $day < 10;
506              
507 0         0 $timestamp = $year.'-'.$month.'-'.$day;
508              
509             }
510             # if they passed a YYYY-MM-DD date, also we will get a DateTime object
511              
512             # need that epoch if a date string was set / parsed
513 1 50 33     9 if ($month || $timestamp =~ /-/) {
514 1         12 $dt = $self->get_datetime_object($timestamp.' 00:00',$time_zone_name);
515 1         7 $timestamp = $dt->epoch;
516 1         12 $time_zone_name = 'Etc/GMT'; # don't offset dates, only timestamps
517             }
518              
519             # default task is the epoch for the first second of the day
520 1   50     4 $task ||= 'to_unix_start';
521              
522             # proceed based on $task
523 1 50 33     43 if ($task eq "to_unix_start") { # date to unix timestamp -- start of the day
    50 33        
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    0          
524 0         0 return $timestamp; # already done above
525             } elsif ($task eq "to_unix_end") { # date to unix timestamp -- end of the day
526 0         0 return ($timestamp + 86399); # most done above
527             } elsif ($task eq "to_date_db") { # unix timestamp to db-date (YYYY-MM-DD)
528 0         0 $templ = '%Y-%m-%d';
529             } elsif (!$task || $task eq "to_date_human") { # unix timestamp to human date (Mon DD, YYYY)
530 0         0 ($diff) = ($timestamp - time())/15552000; # drop the year if within the last six months
531 0 0 0     0 if ($diff > -1 && $diff < 1) {
532 0         0 $templ = '%B %e';
533             } else {
534 0         0 $templ = '%B %e, %Y';
535             }
536             } elsif ($task eq "to_date_human_full") { # force YYYY in above
537 0         0 $templ = '%B %e, %Y';
538             } elsif ($task eq "to_date_human_abbrev") { # shorter month name in above
539 0         0 $templ = '%b %e, %Y';
540             } elsif ($task eq "to_date_human_dayname") { # unix timestamp to human date (DayOfWeekName, Mon DD, YYYY)
541 0         0 ($diff) = ($timestamp - time())/15552000; # drop the year if within the last six months
542 0 0 0     0 if ($diff > -1 && $diff < 1) {
543 0         0 $templ = '%A, %b %e';
544             } else {
545 0         0 $templ = '%A, %b %e, %Y';
546             }
547             } elsif ($task eq "to_year") { # just want year
548 0         0 $templ = '%Y';
549             } elsif ($task eq "to_month" || $task eq "to_month_name") { # unix timestamp to month name (Month YYYY)
550 0         0 $templ = '%B %Y';
551             } elsif ($task eq "to_month_abbrev") { # unix timestamp to month abreviation (MonYY, i.e. Sep15)
552 0         0 $templ = '%b%y';
553             } elsif ($task eq "to_date_human_time") { # unix timestamp to human date with time (Mon DD, YYYY at HH:MM:SS XM)
554 0         0 ($diff) = ($timestamp - time())/31536000;
555 0 0 0     0 if ($diff >= -1 && $diff <= 1) {
556 0         0 $templ = '%b %e at %l:%M%P';
557             } else {
558 0         0 $templ = '%b %e, %Y at %l:%M%P';
559             }
560             } elsif ($task eq "to_just_human_time") { # unix timestamp to humantime (HH:MM:SS XM)
561 0         0 $templ = '%l:%M%P';
562             } elsif ($task eq "to_just_military_time") { # unix timestamp to military time
563 0         0 $templ = '%R';
564             } elsif ($task eq "to_datetime_iso") { # ISO-formatted timestamp, i.e. 2016-09-04T16:12:00+00:00
565 0         0 $templ = '%Y-%m-%dT%X%z';
566             } elsif ($task eq "to_day_of_week") { # epoch to day of the week, like 'Saturday'
567 1         34 $templ = '%A';
568             } elsif ($task eq "to_day_of_week_numeric") { # 0..6 day of the week
569 0         0 $templ = '%w';
570             }
571              
572             # if they sent a time zone, offset the timestamp epoch appropriately
573 1 50       5 if ($time_zone_name ne 'Etc/GMT') {
574             # have we cached this?
575 0 0       0 if (!$self->{tz_offsets}{$time_zone_name}) {
576 0         0 $dt = DateTime->from_epoch(
577             epoch => $timestamp,
578             time_zone => $time_zone_name,
579             );
580 0         0 $self->{tz_offsets}{$time_zone_name} = $dt->offset;
581             }
582              
583             # apply the offset
584 0         0 $timestamp += $self->{tz_offsets}{$time_zone_name};
585             }
586              
587             # now run the conversion
588 1         7 $timestamp = time2str($templ, $timestamp,'GMT');
589 1         153 $timestamp =~ s/ / /g; # remove double spaces;
590 1         3 $timestamp =~ s/GMT //;
591 1         16 return $timestamp;
592             }
593              
594             ### START METHODS FOR pepper setup
595              
596             # loads up $self->{config}; auto-called via new() above
597             sub read_system_configuration {
598 0     0 0   my $self = shift;
599            
600 0           my ($the_file, $obfuscated_json, $config_json);
601            
602             # kick out if that file does not exist yet
603 0 0         if (!(-e $self->{config_file})) {
604 0           $self->send_response('ERROR: Can not find system configuration file.',1);
605             }
606              
607             # try to read it in
608 0           eval {
609 0           $obfuscated_json = $self->filer( $self->{config_file} );
610 0           $config_json = pack "h*", $obfuscated_json;
611 0           $self->{config} = $self->json_to_perl($config_json);
612             };
613            
614             # error out if there was any failure
615 0 0 0       if ($@ || ref($self->{config}) ne 'HASH') {
616 0           $self->send_response('ERROR: Could not read in system configuration file: '.$@,1);
617             }
618              
619             }
620              
621             # save a system config file
622             sub write_system_configuration {
623 0     0 0   my ($self,$new_config) = @_;
624            
625             # convert config to JSON
626 0           my $config_json = $self->json_from_perl($new_config);
627             # slight obfuscation
628 0           my $obfuscated_json = unpack "h*", $config_json;
629              
630             # stash out the file
631 0           path( $self->{config_file} )->spew_raw( $obfuscated_json );
632              
633             # set the permissions
634 0           chmod 0600, $self->{config_file} ;
635             }
636              
637             # method to update the endpoint mapping configs via 'pepper set-endpoint'
638             sub set_endpoint_mapping {
639 0     0 0   my ($self, $endpoint_uri, $endpoint_handler) = @_;
640            
641 0 0 0       if (!$endpoint_uri || !$endpoint_handler) {
642 0           $self->send_response('Error: Both arguments are required for set_endpoint_mapping()',1);
643             }
644            
645             # did they choose to store in a database table?
646 0 0         if ($self->{config}{url_mappings_table}) {
647            
648             # make sure that table exists
649 0           my ($database_name, $table_name) = split /\./, $self->{config}{url_mappings_table};
650 0           my ($table_exists) = $self->{db}->quick_select(qq{
651             select count(*) from information_schema.tables
652             where table_schema=? and table_name=?
653             },[ $database_name, $table_name ]);
654              
655             # if the table does not exist, try to make it
656 0 0         if (!$table_exists) {
657            
658             # we won't create databases/schema in this library
659 0           my ($database_exists) = $self->{db}->quick_select(qq{
660             select count(*) from information_schema.schemata
661             where schema_name=?
662             },[ $database_name ]);
663            
664 0 0         if (!$database_exists) {
665 0           $self->send_response("Error: Database schema $database_exists does not exist",1);
666             }
667            
668             # safe to create the table
669 0           $self->{db}->do_sql(qq{
670             create table $self->{config}{url_mappings_table} (
671             endpoint_uri varchar(200) primary key,
672             handler_module varchar(200) not null
673             )
674             });
675            
676             }
677            
678             # finally, create the mapping
679 0           $self->{db}->do_sql(qq{
680             replace into $self->{config}{url_mappings_table}
681             (endpoint_uri, handler_module) values (?, ?)
682             }, [$endpoint_uri, $endpoint_handler] );
683            
684             # save this change
685 0           $self->{db}->commit();
686            
687             # otherwise, save to a JSON file
688             } else {
689            
690 0           my $url_mappings = $self->read_json_file( $self->{config}{url_mappings_file} );
691 0           $$url_mappings{$endpoint_uri} = $endpoint_handler;
692 0           $self->write_json_file( $self->{config}{url_mappings_file}, $url_mappings );
693            
694             }
695            
696             }
697              
698             # method to delete an endpoint mapping via 'pepper delete-endpoint'
699             sub delete_endpoint_mapping {
700 0     0 0   my ($self, $endpoint_uri) = @_;
701            
702 0 0         if (!$endpoint_uri) {
703 0           $self->send_response('Error: The endpoint uri must be specified for delete_endpoint_mapping()',1);
704             }
705              
706             # did they choose to store in a database table?
707 0 0         if ($self->{config}{url_mappings_table}) {
708              
709 0           $self->{db}->do_sql(qq{
710             delete from $self->{config}{url_mappings_table}
711             where endpoint_uri=?
712             }, [$endpoint_uri] );
713              
714             # save this change
715 0           $self->{db}->commit();
716            
717             # or a JSON file?
718             } else {
719              
720 0           my $url_mappings = $self->read_json_file( $self->{config}{url_mappings_file} );
721 0           delete ( $$url_mappings{$endpoint_uri} );
722 0           $self->write_json_file( $self->{config}{url_mappings_file}, $url_mappings );
723              
724             }
725              
726             }
727              
728             1;
729              
730             __END__
731              
732             =head1 NAME
733              
734             Pepper::Utilities
735              
736             =head1 DESCRIPTION
737              
738             This package provides useful functions for web services and scripts built using the
739             Pepper quick-start kit. These methods can be access via the main 'Pepper' object,
740             and are all documented in that package. Please see 'perldoc Pepper' or the main
741             documentation on MetaCPAN.