Diff -- /var/www/vhosts/3dshawn.com/repricer.3dshawn.com/MODS/Template.pm

O Operator
Diff

/var/www/vhosts/3dshawn.com/repricer.3dshawn.com/MODS/Template.pm

added on local at 2026-07-01 21:47:15

Added
+571
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 1c37fd28e4c7
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1package MODS::Template;
2#======================================================================
3# WebSTLs — templating engine.
4#
5# Reads a template file from TEMPLATES/ and converts custom tags into
6# real Perl, then runs the result to produce the rendered HTML.
7#
8# Tag reference:
9# $varname -> $tvars->{varname}
10# $var.sub.field -> $tvars->{var}->{sub}->{field}
11# [$var] -> same as $var, brackets stripped
12# [loop:@arr]...[/loop]
13# [if:cond]...[elsif:cond]...[else]...[/if]
14# [url:key] -> URL registry lookup
15# [ptag:N]...[/ptag:N] permission gates (DB-driven)
16# [area:name]...[/area:name] AJAX partials
17#======================================================================
18use strict;
19
20# Optional dependencies — load each at runtime so a missing module
21# doesn't kill compilation. The original code paired `eval{require}`
22# with a `use` statement, but `use` fires at COMPILE time regardless of
23# the eval, which crashes the whole .cgi if the module file is absent.
24# Plain `require` here keeps these truly optional.
25
26my $url = {}; # default: empty hashref so [url:key] in templates never crashes
27if (eval { require MODS::Urls; 1 }) {
28 my $getlist = MODS::Urls->new;
29 $url = $getlist->urls() || {};
30}
31
32my $db;
33if (eval { require MODS::DBConnect; 1 }) {
34 $db = MODS::DBConnect->new;
35}
36
37my $config;
38if (eval { require MODS::Config; 1 }) {
39 $config = MODS::Config->new;
40}
41
42my $database_name = $config ? $config->settings('database_name') : '';
43
44#Variables
45my $newline_delimeter = '----NL----';
46my $carriage_return_delimeter = '----CR----';
47my $newline_delimeter_re = qr/\Q$newline_delimeter\E/;
48my $carriage_return_delimeter_re = qr/\Q$carriage_return_delimeter\E/;
49
50#Calling loop1 if you are inside of loop2
51#$loop.variable_name
52#or
53#$loop1->{variable_name}
54
55#Calling outside of the loop back to the main set of variables in tvars
56#tvars.variable_name
57#or
58#$tvars->{variable_name}
59
60#Nested loops are always looking for looking to loop off of an array inside the previous loop, if you have a nested loop that is un-related to the loop it is nested within then call
61#back to the main variables using tvars
62#[loop:@tvars.main_array_name]
63
64#URLs are called with this command if it is in the MODS/URLs.pm module
65#[url:your_url_here]
66
67#Variables called like this
68#$var or $var.nested_var.nested_var or [$var] or [$var.nested_var.nested_var]
69
70#Loops
71#[loop:@array_of_hashrefs][/loop]
72
73#Logic statements, they can use all normal Perl operators such as < > = eq ne etc...
74#[if:$var][/if]
75#[elsif:$var1 eq 'test' && $var2 <= 5][/elseif]
76#[else][/else]
77
78
79sub new{
80 my($class, %args) = @_;
81 my $self = bless({}, $class);
82
83 return $self;
84}
85
86sub rebuild_variable{
87 #Converts template variables into real live Perl variables
88 #Example: $tvars.data.user_name turns into $tvars->{data}->{user_name}
89 my $variable = shift;
90
91 my @pieces = split(/\./, $variable);
92
93 my $new_variable;
94 foreach my $piece(@pieces){
95 if(!$new_variable){
96 $new_variable = $piece . '->';
97 }else{
98 $new_variable .= '{' . $piece . '}->';
99 }
100 }
101
102 chop($new_variable); #Remove the >
103 chop($new_variable); #Remove the -
104
105 return $new_variable;
106}
107
108sub template{
109 my ($self, $template, $tvars, $userinfo, $option) = @_;
110
111 # Inject standard role-aware tvars so every template can gate
112 # owner-only blocks with `[if:$is_owner]` / `[if:$is_team_member]`
113 # without each CGI having to set them. Underscore-leading would be
114 # more conventional but our [if:] resolver treats `$x` as the
115 # whole symbol, so we keep the names plain.
116 $tvars ||= {};
117 if ($userinfo && $userinfo->{user_id}) {
118 my $is_team = ($userinfo->{owner_user_id}) ? 1 : 0;
119 $tvars->{is_team_member} = $is_team;
120 $tvars->{is_owner} = $is_team ? 0 : 1;
121 } else {
122 $tvars->{is_team_member} = 0;
123 $tvars->{is_owner} = 0;
124 }
125
126 #Get the URLs from the software calling this module if the URLs were passed in
127 $url = $userinfo->{system_config}->{url} if $userinfo->{system_config};
128
129 #Get the template path from the software calling this module if it was passed in, otherwise use the normal path
130 my $template_path = 'TEMPLATES';
131 $template_path = $userinfo->{system_config}->{config}->{'template_path'} if $userinfo->{system_config};
132
133 #Use the callers DB connection if passed in, otherwise we may open our own below
134 my $dbh;
135 my $close_dbh = 0;
136 my $access_info = {};
137
138 #This area feature allows you to process only a piece of a template, used frequently with AJAX calls where you only want to return and update a piece/area of a page and not the whole page
139 my $area_name = ".$1" if $option =~ m/area\:(\w+)/i;
140
141 my @processed_parts;
142 push @processed_parts, qq~my \$print_contents;~;
143
144 #Get the template using slurp mode for speed
145 #Prevent path traversal - block directory traversal and absolute paths
146 if($template =~ m/\.\./ || $template =~ m/^\//){
147 return "Error: invalid template name";
148 }
149 open(my $fh, '<', "$template_path/$template") || return "Error: template not found";
150 my $template_html = do { local $/; <$fh> };
151 close $fh;
152
153 #Variables to track the opening and closing tags
154 my @opened_tags;
155
156 #Strip HTML comments before parsing. Template tags like [loop:] and
157 #[if:] sitting inside <!-- ... --> would otherwise be wrongly read as
158 #real directives, scrambling the bracket-nesting count and producing
159 #malformed Perl. HTML comments aren't visible in output so dropping
160 #them is harmless.
161 $template_html =~ s/<!--.*?-->//gs;
162
163 #Replace the newline chars until later so they don't get in the way, don't remove escaped ones though
164 $template_html =~ s/\n/$newline_delimeter/g;
165 $template_html =~ s/\r/$carriage_return_delimeter/g;
166
167
168 #----------------------------------------------------------------------------------------------------------
169 #-Permission tag — [ptag:N]...[/ptag:N] gates content on permission_features.id N.
170 #
171 # Accepts numeric IDs OR slug names (preferred):
172 # [ptag:edit_models]<button>Edit</button>[/ptag:edit_models]
173 # [ptag:13]<button>Edit</button>[/ptag:13] (same thing)
174 #
175 # Lookup model (WebSTLs):
176 # - users.permission_groups_id IS NULL -> owner / unconstrained. Every gate passes.
177 # - users.permission_groups_id IS NOT NULL -> team member. Only the
178 # feature ids listed in permission_groups.permission_features_ids
179 # (CSV) pass; everything else is stripped to an HTML comment.
180 #
181 # The query joins permission_groups directly off the users row so a
182 # single SELECT covers both cases (LEFT JOIN returns NULL for owners).
183 # If the template uses any slug names we also pull the 28-row
184 # permission_features map once and reuse it for every gate in this render.
185 #----------------------------------------------------------------------------------------------------------
186 if($template_html =~ m/ptag:/i){
187 $dbh = $db->db_connect();
188 $close_dbh = 1;
189
190 #Sanitize user input to numeric only to prevent SQL injection
191 my $safe_user_id = $userinfo->{user_id} || 0;
192 $safe_user_id =~ s/[^0-9]//g;
193 $safe_user_id ||= 0;
194
195 my $is_owner = 0;
196 my $features_csv = '';
197
198 if ($safe_user_id) {
199 my $sql = qq~SELECT u.permission_groups_id, pg.permission_features_ids
200 FROM $database_name.users u
201 LEFT JOIN $database_name.permission_groups pg
202 ON pg.id = u.permission_groups_id AND pg.group_status='1'
203 WHERE u.id = '$safe_user_id'
204 LIMIT 1~;
205 my $row = $db->db_readwrite($dbh, $sql, $ENV{SCRIPT_NAME}, __LINE__);
206
207 if (!$row || !defined $row->{permission_groups_id} || $row->{permission_groups_id} eq '') {
208 # Owner / unconstrained access
209 $is_owner = 1;
210 } else {
211 $features_csv = $row->{permission_features_ids} || '';
212 }
213 } else {
214 # Anonymous / no userinfo -> treat as no access
215 $is_owner = 0;
216 }
217
218 # Build slug -> id map only if a non-numeric ptag exists in the
219 # template. Numeric-only templates skip this trip entirely.
220 my %slug_to_id;
221 if ($template_html =~ m/\[\s*ptag:[a-zA-Z_]/) {
222 my @rows = $db->db_readwrite_multiple($dbh, qq~
223 SELECT id, slug FROM $database_name.permission_features
224 ~, $ENV{SCRIPT_NAME}, __LINE__);
225 foreach my $r (@rows) { $slug_to_id{$r->{slug}} = $r->{id}; }
226 }
227
228 $access_info = { permission_features_ids => $features_csv, is_owner => $is_owner };
229
230 while($template_html =~ s/\[\s{0,}\t{0,}ptag:(.*?)\](.*?)\[\/ptag:\1\]/[--$1--]/i){
231 my $ptag = $1;
232 my $html_content = $2;
233
234 # Resolve slug -> id if non-numeric. Unknown slug = deny.
235 my $id = ($ptag =~ /^\d+$/) ? $ptag : ($slug_to_id{$ptag} || 0);
236 my $allowed = $access_info->{is_owner}
237 || ($id && ",$access_info->{permission_features_ids}," =~ m/,$id,/);
238 if ($allowed) {
239 $template_html =~ s/\[--\Q$ptag\E--\]/$html_content/;
240 } else {
241 $template_html =~ s/\[--\Q$ptag\E--\]/\<\!-- $ptag:no access --\>/;
242 }
243 }
244 }
245 #----------------------------------------------------------------------------------------------------------
246 #----------------------------------------------------------------------------------------------------------
247
248
249 #----------------------------------------------------------------------------------------------------------
250 #-Area called, this will find and remove everything except the area called --------------------------------
251 if($area_name){
252 #If only the area was suppose to be loaded then lets get rid of everything else
253 my $tag = $area_name;
254 $tag =~ s/^\.//;
255
256 #Get the contents from between the area tags
257 $template_html =~ s/.*?\[area\(\w{0,}\)\:$tag\](.*?)\[\/area\:$tag\].*/$1/i;
258 if(!$1){
259 #If the tag wasn't found then try looking for the same tags without the (flags)
260 $template_html =~ s/.*?\[area\:$tag\](.*?)\[\/area\:$tag\].*/$1/i;
261 }
262
263 #First tag: remove the closing HTML comment code if the area tags were hidden inside of a comment
264 $template_html =~ s/^\s{0,}\t{0,}\-\-\>//i;
265 #Second tag: remove the closing HTML comment code if the area tags were hidden inside of a comment
266 $template_html =~ s/\s{0,}\t{0,}\-\-\>$//i;
267 }else{
268 #Find all area tags NOT currently being requested to see what we should do with them
269 while($template_html =~ s/(\[area)\((\w{0,})\)(\:)(\w+)(\])/$1$3$4$5/i || $template_html =~ m/\[area\:(\w+)\]/i){
270 my $flag = $2;
271
272 my $tag;
273 if($flag eq 'hide'){
274 $tag = $4;
275 #Remove the tags and area that tags surround to hide this until it is requested
276 #Code from before I added te comment removing code# $template_html =~ s/\[area\:$tag\].*?\[\/area\:$tag\]//i;
277 $template_html =~ s/(\<\!--)?\s{0,}\t{0,}\[area\:$tag\]\s{0,}\t{0,}(--\>)?.*?(\<\!--)?\s{0,}\t{0,}\[\/area\:$tag\]\s{0,}\t{0,}(--\>)?//i;
278 }else{
279 $tag = $1;
280 #Just remove the tags only from the HTML, just the template codes leaving clean HTML
281 #Code from before I added te comment removing code# $template_html =~ s/(\[area\:$tag\])(.*?)(\[\/area\:$tag\])/$2/i;
282 $template_html =~ s/(\<\!--)?\s{0,}\t{0,}(\[area\:$tag\])\s{0,}\t{0,}(--\>)?(.*?)(\<\!--)?\s{0,}\t{0,}(\[\/area\:$tag\])\s{0,}\t{0,}(--\>)?/$4/i;
283 }
284 #Debug: print qq~TAG FOUND: $tag<br>~;
285 }
286 }
287 #----------------------------------------------------------------------------------------------------------
288 #----------------------------------------------------------------------------------------------------------
289
290 #Replace all global variables in the template, non nested variables both inside and outside of loops
291 #Convert template variable into Perl variable formats, we are only converting ones that start with $tvars, this will call the subroutine called rebuild_variable in the second part of the regex where the replace happens
292 #
293 #The key portion uses \w+ (no dots) so a bare reference like
294 #$loop1.image_idx.webp resolves $loop1->{image_idx} and leaves .webp
295 #as literal text in the output. For genuine deep-nested access, use
296 #the bracket form: [$var.sub.field] -- that explicitly delimits the
297 #full path before any trailing literal text.
298 $template_html =~ s|\[?(?<!\\)\$tvars\.([\w\.]+)\]?|'$tvars->{' . join('}->{', split(/\./, $1)) . '}'|ge;
299 $template_html =~ s|\[(\$loop\d+)\.([\w\.]+)\]|$1 . join('', map { '->{' . $_ . '}' } split(/\./, $2))|ge;
300 $template_html =~ s|(\$loop\d+)\.(\w+)|$1\->{$2}|g;
301
302 my $opened_depth = 0; #Used for tracking nested loops as well as indenting code in debug mode
303 my $loop_open=0; #Track open loops so we can replace the variables
304 #Get a list of all opening tags so we know the order to close them in
305 while($template_html =~ m~(.*?)?((\[(if|elsif|else|loop):?.*?\])|(\[\/(if|elsif|else|loop)\]))|(.*)~cgi){
306 my $open = $3 if $3;
307 my $close = $5 if $5;
308
309 #Save the data we pulled out for later processing and putting back in place
310 push(@opened_tags, $open) if $open;
311
312 #Opening tag, depth tracks how nested this tag is, add one
313 $opened_depth++ if $open;
314
315 #Debug - prints out all of the opened and closed tags with indentation for easy view
316 #print ' 'x($opened_depth + 1) . "opened: $open\n" if $open;
317 #print ' 'x($opened_depth + 1) . "closed: $close\n" if $close;
318
319 #-----------------------------------------------------------------------------------------------------------------
320 #Now convert the HTML template tags to Perl code
321 my $html = $1.$7; #Copy for editing
322
323 #Creating temporary copy, removing newline and carriage return delimeters so we can test if there is still a value
324 my $tmp_html = $1.$7;
325 $tmp_html =~ s/$newline_delimeter_re//g;
326 $tmp_html =~ s/$carriage_return_delimeter_re//g;
327
328 if($loop_open > 0){
329 ##################################################################################################################
330 ### Replace any variables inside of loops ########################################################################
331
332 #-----------------------------------------------------------------------------------------------------------------
333 #Replace variables with [] brackets and without such as [$var] and [$var.var2] and [$var.var2.var3] etc... and $var and $var.var2 and $var.var2.var3
334 while($html =~ m~\[?(?<!\\)\$(?!tvars->)(?!loop\d{0,}->)([\w\.{0,}]+)\]?~ci){
335 my $var_found = $1;
336 my @nested = split(/\./, $var_found);
337 my $variable_design;
338 foreach my $nested(@nested){
339 if(!$variable_design){
340 $variable_design = "\$loop$loop_open-\>{$nested}";
341 }else{
342 $variable_design .= "-\>{$nested}";
343 }
344 }
345 $html =~ s/\[?\$$var_found\]?/$variable_design/; #Replace the variables in the open tags
346 }
347 #-----------------------------------------------------------------------------------------------------------------
348
349 #-----------------------------------------------------------------------------------------------------------------
350 #Take the loop array name and convert it to the read-array variable format. Three roots are recognised:
351 # [loop:@plain_field] -> @{$loopN->{plain_field}} (current loop's row -- N = $loop_open)
352 # [loop:@tvars.foo.bar] -> @{$tvars->{foo}->{bar}} (escape back to top-level tvars)
353 # [loop:@loop1.foo.bar] -> @{$loop1->{foo}->{bar}} (reach back to a specific outer loop's iter var)
354 #The loopN form was previously mishandled as a hash-key segment on top of the CURRENT $loopN itself,
355 #producing @{$loopN->{loop1}->{foo}} -- silently empty -- which broke every nested @loopN.field template.
356 if($open =~ m/\@((\w+|\.+)+)/){
357 my $var_found = $1;
358 my @nested = split(/\./, $1);
359 my $head;
360 if ($nested[0] =~ m/^tvars$/i) {
361 shift(@nested);
362 $head = '$tvars';
363 } elsif ($nested[0] =~ m/^loop\d+$/i) {
364 $head = '$' . shift(@nested);
365 } else {
366 $head = '$loop' . $loop_open;
367 }
368 my $variable_design = "\@{$head";
369 foreach my $nested(@nested){
370 $variable_design .= "-\>{$nested}";
371 }
372 $open =~ s/\@((\w+|\.+)+)/$variable_design\}/; #Replace the variables in the open tags
373 }
374 #-----------------------------------------------------------------------------------------------------------------
375
376 #-----------------------------------------------------------------------------------------------------------------
377 #Deal with variables in if/elsif conditions
378 if($open =~ m/(if|elsif).*?\$(?!tvars->)([\w\.]+)/){
379 while($open =~ m/\$(?!tvars->)(?!loop\d+->)([\w\.]+)/ci){
380 my $var_found = $1;
381 my @nested = split(/\./, $1);
382 my $variable_design;
383 foreach my $nested(@nested){
384 if(!$variable_design){
385 $variable_design = "\$loop$loop_open-\>{$nested}";
386 }else{
387 $variable_design .= "-\>{$nested}";
388 }
389 }
390 $open =~ s/\$$var_found/$variable_design/; #Replace the variables in the open tags
391 }
392 }
393 #-----------------------------------------------------------------------------------------------------------------
394
395 #The HTML is about to be wrapped in qq~...~ to form the
396 #Perl code we eval. A literal ~ in the HTML would close the
397 #qq early and leak HTML into code position, which then
398 #cascades into "Unrecognized character" errors when bytes
399 #spill out of string context. Escape it first so qq~\~~
400 #renders as a literal ~ in the output. Also escape @ before
401 #identifier chars so CSS rules (@media, @keyframes) and email
402 #addresses (support@taskforge.com) don't get parsed as array
403 #interpolation -- that fires "Not an ARRAY reference" at the
404 #closing brace of the surrounding block.
405 my $safe_html = $html;
406 $safe_html =~ s/~/\\~/g;
407 $safe_html =~ s/\@(?=[A-Za-z_\{\$])/\\\@/g;
408 push @processed_parts, qq~\$print_contents .= qq\~$safe_html\~\;~ if $tmp_html =~ m/[!-~]/;
409
410 ### Replace all of the variables inside this loop ################################################################
411 ##################################################################################################################
412 }else{
413 ##################################################################################################################
414 ### Replace any variables outside of loops #######################################################################
415
416 #-----------------------------------------------------------------------------------------------------------------
417 #Replace variables with [] brackets and without such as [$var] and [$var.var2] and [$var.var2.var3] etc... and $var and $var.var2 and $var.var2.var3
418 while($html =~ m~\[?(?<!\\)\$(?!tvars->)(?!\{)([\w\.]+)\]?~ci){
419 my $var_found = $1;
420 my @nested = split(/\./, $var_found);
421 my $variable_design;
422 foreach my $nested(@nested){
423 if(!$variable_design){
424 $variable_design = "\$tvars-\>{$nested}";
425 }else{
426 $variable_design .= "-\>{$nested}";
427 }
428 }
429 $html =~ s/\[?\$$var_found\]?/$variable_design/; #Replace the variables in the open tags
430 }
431 #-----------------------------------------------------------------------------------------------------------------
432
433 #-----------------------------------------------------------------------------------------------------------------
434 #Take the loop array name and convert it to the read array variable format such as [loop:@names.first] would become @{$namesDeal}->{first}
435 if($open =~ m/\@((\w+|\.+)+)/){
436 my $var_found = $1;
437 my @nested = split(/\./, $1);
438 my $variable_design;
439 foreach my $nested(@nested){
440 if(!$variable_design){
441 $variable_design = "\@{\$tvars-\>{$nested}}";
442 }else{
443 $variable_design .= "-\>{$nested}";
444 }
445 }
446 $open =~ s/\@((\w+|\.+)+)/$variable_design/; #Replace the variables in the open tags
447 }
448 #-----------------------------------------------------------------------------------------------------------------
449
450 #-----------------------------------------------------------------------------------------------------------------
451 #Deal with variables in if/elsif conditions
452 if($open =~ m/(if|elsif).*?\$(?!tvars->)([\w\.]+)/){
453 while($open =~ m/\$(?!tvars-\>)([\w\.]+)/ci){
454 my $var_found = $1;
455 my @nested = split(/\./, $1);
456 my $variable_design;
457 foreach my $nested(@nested){
458 if(!$variable_design){
459 $variable_design = "\$tvars-\>{$nested}";
460 }else{
461 $variable_design .= "-\>{$nested}";
462 }
463 }
464 $open =~ s/\$$var_found/$variable_design/; #Replace the variables in the open tags
465 }
466 }
467 #-----------------------------------------------------------------------------------------------------------------
468
469 #Same escape pass as the in-loop branch -- see comment there
470 #for the @media / email-address rationale.
471 my $safe_html = $html;
472 $safe_html =~ s/~/\\~/g;
473 $safe_html =~ s/\@(?=[A-Za-z_\{\$])/\\\@/g;
474 push @processed_parts, qq~\$print_contents .= qq\~$safe_html\~\;~ if $tmp_html =~ m/[!-~]/;
475
476 ### Replace all of the variables outside this loop ###############################################################
477 ##################################################################################################################
478 }
479
480 if($open){
481 #If a loop was opened them mark it here at the bottom, we don't want to modify the text found before the loop was opened, only after
482 $loop_open++ if $open =~ m/\[loop/i;
483
484 #Replace all template "opening" tags with actual Perl code
485 my $tag = $open;
486 $tag =~ s~\[(if|elsif):(.*?)\]~$1\($2\)\{~gi; #Replace all "if" and "elsif" tags
487 $tag =~ s~\[else\]~else\{~gi; #Replace all "else" tags
488 $tag =~ s~\[loop:(.*?)\]~foreach my \$loop$loop_open\($1\)\{~gi; #Replace all "loop" tags
489
490 push @processed_parts, "$tag";
491 }
492
493 if($close){
494 #Closing tag, depth tracks how nested this tag is, subtract one
495 $opened_depth-- if $close;
496 $loop_open-- if $close =~ m/\[\/loop/i;
497
498 #Replace all template "closing" tags with actual Perl tags
499 push @processed_parts, "}";
500 }
501 #-----------------------------------------------------------------------------------------------------------------
502
503 #Reset all of the regex vars with each loop pass we do to be safe
504 my $vars =~ s/()()()()()()//;
505 }
506
507 #Join all parts into the final processed template string. Append an
508 #explicit final-expression `$print_contents` so eval always returns the
509 #accumulated string, even when the template ends inside an if/foreach
510 #branch that didn't fire on this render.
511 my $processed_template = join('', @processed_parts) . qq~;\$print_contents;~;
512
513 #Replace all URLs in the template
514 $processed_template =~ s|\[url\:(.*?)\]|$url->{$1}|ig;
515
516 #Put back the newline and carriage return chars
517 $processed_template =~ s/$newline_delimeter_re/\n/g;
518 $processed_template =~ s/$carriage_return_delimeter_re/\r/g;
519
520 #Close database connection only if we opened one
521 $db->db_disconnect($dbh) if $close_dbh;
522
523 #Run the new files code in memory and then return the results
524 my $processed_template = &run_in_memory($processed_template, $tvars);
525}
526
527sub run_in_memory{
528 my $code = shift;
529 our $tvars = shift;
530
531 # Debug: APPEND tvars structure to disk for inspection
532 eval {
533 my $debug_path = '/var/www/vhosts/3dshawn.com/taskforge.3dshawn.com/_tvars_dump.txt';
534 if (open my $dh, '>>', $debug_path) {
535 print $dh "\n===== run_in_memory call =====\n";
536 foreach my $k (sort keys %$tvars) {
537 my $v = $tvars->{$k};
538 my $type = ref($v) || 'scalar';
539 my $val = (ref($v) eq 'ARRAY') ? '[' . scalar(@$v) . ' items]'
540 : (ref($v) eq 'HASH') ? '{' . join(',', sort keys %$v) . '}'
541 : (defined $v ? substr($v, 0, 60) : 'UNDEF');
542 print $dh "$k => $type :: $val\n";
543 }
544 close $dh;
545 }
546 };
547
548 my $print_contents;
549 {
550 no strict;
551 $print_contents = eval $code;
552 }
553
554 if($@){
555 my $err = $@;
556 eval {
557 my $path = '/var/www/vhosts/3dshawn.com/taskforge.3dshawn.com/_tmpl_dump.txt';
558 if (open my $fh, '>>', $path) {
559 print $fh "\n========== ERR: $err\n";
560 my $ln = 0;
561 foreach my $L (split /\n/, $code) { $ln++; print $fh sprintf('%4d: %s', $ln, $L), "\n"; }
562 close $fh;
563 }
564 };
565 return "Template eval error: $err";
566 }
567
568 return $print_contents;
569}
570
5711;