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