Parcourir la source

Re-factor html renders into T::R::HTML, no longer pass render_cb

George S. Baugh il y a 3 ans
Parent
commit
28cd69b778
4 fichiers modifiés avec 130 ajouts et 128 suppressions
  1. 11 72
      lib/TCMS.pm
  2. 100 54
      lib/Trog/Routes/HTML.pm
  3. 2 2
      lib/Trog/Routes/JSON.pm
  4. 17 0
      lib/Trog/Vars.pm

+ 11 - 72
lib/TCMS.pm

@@ -14,7 +14,6 @@ use Text::Xslate ();
 use Plack::MIME  ();
 use Mojo::File   ();
 use DateTime::Format::HTTP();
-use Encode qw{encode_utf8};
 use CGI::Cookie ();
 use File::Basename();
 use IO::Compress::Deflate();
@@ -28,6 +27,7 @@ use Trog::Auth;
 use Trog::Utils;
 use Trog::Config;
 use Trog::Data;
+use Trog::Vars;
 
 # Troglodyne philosophy - simple as possible
 
@@ -46,22 +46,6 @@ my %aliases = $data->aliases();
 #1MB chunks
 my $CHUNK_SIZE = 1024000;
 
-# Things we will actually produce from routes rather than just serving up files
-my $ct = 'Content-type';
-my %content_types = (
-    plain => "text/plain;",
-    html  => "text/html; charset=UTF-8",
-    json  => "application/json;",
-    blob  => "application/octet-stream;",
-);
-
-my $cc = 'Cache-control';
-my %cache_control = (
-    revalidate => "no-cache, max-age=0",
-    nocache    => "no-store",
-    static     => "public, max-age=604800, immutable",
-);
-
 #Stuff that isn't in upstream finders
 my %extra_types = (
     '.docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
@@ -97,7 +81,7 @@ sub app {
     $path =~ s/[\/]+/\//g;
 
     # Let's open up our default route before we bother to see if users even exist
-    return $routes{default}{callback}->($query,\&_render) unless -f "config/setup";
+    return $routes{default}{callback}->($query) unless -f "config/setup";
 
     my $cookies = {};
     if ($env->{HTTP_COOKIE}) {
@@ -111,7 +95,7 @@ sub app {
 
     #Disallow any paths that are naughty ( starman auto-removes .. up-traversal)
     if (index($path,'/templates') == 0 || $path =~ m/.*\.psgi$/i ) {
-        return Trog::Routes::HTML::forbidden($query, \&_render);
+        return Trog::Routes::HTML::forbidden($query);
     }
 
     # If it's just a file, serve it up
@@ -142,8 +126,8 @@ sub app {
     $query->{deflate} = $deflate;
     $query->{user}    = $active_user;
 
-    return Trog::Routes::HTML::notfound($query, \&_render) unless exists $routes{$path};
-    return Trog::Routes::HTML::badrequest($query, \&_render) unless grep { $env->{REQUEST_METHOD} eq $_ } ($routes{$path}{method},'HEAD');
+    return Trog::Routes::HTML::notfound($query) unless exists $routes{$path};
+    return Trog::Routes::HTML::badrequest($query) unless grep { $env->{REQUEST_METHOD} eq $_ } ($routes{$path}{method},'HEAD');
 
     @{$query}{keys(%{$routes{$path}{'data'}})} = values(%{$routes{$path}{'data'}}) if ref $routes{$path}{'data'} eq 'HASH' && %{$routes{$path}{'data'}};
 
@@ -174,7 +158,7 @@ sub app {
     #XXX there is a trick to now use strict refs, but I don't remember it right at the moment
     {
         no strict 'refs';
-        my $output = $routes{$path}{callback}->($query, \&_render);
+        my $output = $routes{$path}{callback}->($query);
         return $output;
     }
 };
@@ -187,11 +171,13 @@ sub _serve ($path, $streaming=0, $last_fetch=0, $deflate=0) {
         $ft = Plack::MIME->mime_type($ext) if $ext;
         $ft ||= $extra_types{$ext} if exists $extra_types{$ext};
     }
-    $ft ||= $content_types{plain};
+    $ft ||= $Trog::Vars::content_types{plain};
 
+    my $ct = 'Content-type';
     my @headers = ($ct => $ft);
     #TODO use static Cache-Control for everything but JS/CSS?
-    push(@headers,$cc => $cache_control{revalidate});
+
+    push(@headers,'Cache-control' => $Trog::Vars::cache_control{revalidate});
 
     #TODO Return 304 unchanged for files that haven't changed since the requestor reports they last fetched
     my $mt = (stat($path))[9];
@@ -232,54 +218,7 @@ sub _serve ($path, $streaming=0, $last_fetch=0, $deflate=0) {
         return [ $code, \@headers, [$dfh]];
     }
 
-    return [ 403, [$ct => $content_types{plain}], ["STAY OUT YOU RED MENACE"]];
-}
-
-sub _render ($template, $vars, @headers) {
-
-    #XXX default vars that need to be pulled from config
-    $vars->{dir}       //= 'ltr';
-    $vars->{lang}      //= 'en-US';
-    $vars->{title}     //= 'tCMS';
-    #XXX Need to have minification detection and so forth, use LESS
-    $vars->{stylesheets}  //= [];
-    #XXX Need to have minification detection, use Typescript
-    $vars->{scripts} //= [];
-
-    # Absolute-ize the paths for scripts & stylesheets
-    @{$vars->{stylesheets}} = map { index($_, '/') == 0 ? $_ : "/$_" } @{$vars->{stylesheets}};
-    @{$vars->{scripts}}     = map { index($_, '/') == 0 ? $_ : "/$_" } @{$vars->{scripts}};
-
-    $vars->{contenttype} //= $content_types{html};
-    $vars->{cachecontrol} //= $cache_control{revalidate};
-
-    $vars->{code} ||= 200;
-
-    push(@headers, $ct => $vars->{contenttype});
-    push(@headers, $cc => $vars->{cachecontrol}) if $vars->{cachecontrol};
-
-    my $body = Trog::Routes::HTML::themed_render('header.tx', $vars);
-    $body   .= Trog::Routes::HTML::themed_render($template,$vars);
-    $body   .= Trog::Routes::HTML::themed_render('footer.tx', $vars);
-    $body = encode_utf8($body);
-
-    #Return data in the event the caller does not support deflate
-    if (!$vars->{deflate}) {
-        push( @headers, "Content-Length" => length($body) );
-        return [ $vars->{code}, \@headers, [$body]];
-    }
-
-    #Compress
-    push( @headers, "Content-Encoding" => "deflate" );
-
-    #Disallow framing UNLESS we are in embed mode
-    push( @headers, "Content-Security-Policy" => qq{frame-ancestors 'none'} ) unless $vars->{embed};
-
-    my $dfh;
-    IO::Compress::Deflate::deflate( \$body => \$dfh );
-    print $IO::Compress::Deflate::DeflateError if $IO::Compress::Deflate::DeflateError;
-    push( @headers, "Content-Length" => length($dfh) );
-    return [$vars->{code}, \@headers, [$dfh]];
+    return [ 403, [$ct => $Trog::Vars::content_types{plain}], ["STAY OUT YOU RED MENACE"]];
 }
 
 1;

+ 100 - 54
lib/Trog/Routes/HTML.pm

@@ -13,6 +13,9 @@ use List::MoreUtils();
 use Capture::Tiny qw{capture};
 use HTML::SocialMeta;
 
+use Encode qw{encode_utf8};
+use IO::Compress::Deflate;
+
 use Trog::Utils;
 use Trog::Config;
 use Trog::Data;
@@ -182,7 +185,7 @@ Most subsequent functions simply pass content to this function.
 
 =cut
 
-sub index ($query,$render_cb, $content = '', $i_styles = []) {
+sub index ($query, $content = '', $i_styles = []) {
     $query->{theme_dir}  = $td;
 
     $content ||= themed_render($landing_page, $query);
@@ -208,7 +211,7 @@ sub index ($query,$render_cb, $content = '', $i_styles = []) {
 
     #Do embed content
     my $tmpl = $query->{embed} ? 'embed.tx' : 'index.tx';
-    return $render_cb->( $tmpl, {
+    return finish_render( $tmpl, {
         %$query,
         search_lang    => $data->lang(),
         search_help    => $data->help(),
@@ -294,7 +297,7 @@ Implements the 4XX status codes.  Override templates named the same for theming
 
 =cut
 
-sub _generic_route ($rname, $code, $title, $query, $render_cb) {
+sub _generic_route ($rname, $code, $title, $query) {
     $query->{code} = $code;
 
     $query->{title} = $title;
@@ -306,7 +309,7 @@ sub _generic_route ($rname, $code, $title, $query, $render_cb) {
         styles   => $styles,
     deflate  => $query->{deflate},
     });
-    return Trog::Routes::HTML::index($query, $render_cb, $content, $styles);
+    return Trog::Routes::HTML::index($query, $content, $styles);
 }
 
 sub notfound (@args) {
@@ -339,7 +342,7 @@ Return an appropriate robots.txt
 
 =cut
 
-sub robots ($query, $render_cb) {
+sub robots ($query) {
     return [200, ["Content-type:text/plain\n"],[themed_render('robots.tx', { domain => $query->{domain} })]];
 }
 
@@ -349,9 +352,9 @@ One time setup page; should only display to the first user to visit the site whi
 
 =cut
 
-sub setup ($query, $render_cb) {
+sub setup ($query) {
     File::Touch::touch("config/setup");
-    return $render_cb->('notconfigured.tx', {
+    return finish_render('notconfigured.tx', {
         title => 'tCMS Requires Setup to Continue...',
         stylesheets => _build_themed_styles('notconfigured.css'),
     });
@@ -363,7 +366,7 @@ Sets the user cookie if the provided user exists, or sets up the user as an admi
 
 =cut
 
-sub login ($query, $render_cb) {
+sub login ($query) {
 
     # Redirect if we actually have a logged in user.
     # Note to future me -- this user value is overwritten explicitly in server.psgi.
@@ -371,7 +374,7 @@ sub login ($query, $render_cb) {
     $query->{to} //= $query->{route};
     $query->{to} = '/config' if $query->{to} eq '/login';
     if ($query->{user}) {
-        return $routes{$query->{to}}{callback}->($query,$render_cb);
+        return $routes{$query->{to}}{callback}->($query);
     }
 
     #Check and see if we have no users.  If so we will just accept whatever creds are passed.
@@ -404,7 +407,7 @@ sub login ($query, $render_cb) {
     }
 
     $query->{failed} //= -1;
-    return $render_cb->('login.tx', {
+    return finish_render('login.tx', {
         title         => 'tCMS 2 ~ Login',
         to            => $query->{to},
         failure => int( $query->{failed} ),
@@ -491,10 +494,10 @@ Deletes your users' session and opens the index.
 
 =cut
 
-sub logout ($query, $render_cb) {
+sub logout ($query) {
     Trog::Auth::killsession($query->{user}) if $query->{user};
     delete $query->{user};
-    return Trog::Routes::HTML::index($query,$render_cb);
+    return Trog::Routes::HTML::index($query);
 }
 
 =head2 config
@@ -503,12 +506,12 @@ Renders the configuration page, or redirects you back to the login page.
 
 =cut
 
-sub config ($query, $render_cb) {
+sub config ($query) {
     if (!$query->{user}) {
-        return login($query,$render_cb);
+        return login($query);
     }
     #NOTE: we are relying on this to skip the ACL check with 'admin', this may not be viable in future?
-    return forbidden($query, $render_cb) unless grep { $_ eq 'admin' } @{$query->{acls}};
+    return forbidden($query) unless grep { $_ eq 'admin' } @{$query->{acls}};
 
     my $css   = _build_themed_styles('config.css');
     my $js    = _build_themed_scripts('post.js');
@@ -516,7 +519,7 @@ sub config ($query, $render_cb) {
     $query->{failure} //= -1;
     my @series = _get_series(1);
 
-    return $render_cb->('config.tx', {
+    return finish_render('config.tx', {
         title              => 'Configure tCMS',
         theme_dir          => $td,
         stylesheets        => $css,
@@ -565,8 +568,8 @@ Implements /config/save route.  Saves what little configuration we actually use
 
 =cut
 
-sub config_save ($query, $render_cb) {
-    return forbidden($query, $render_cb) unless grep { $_ eq 'admin' } @{$query->{acls}};
+sub config_save ($query) {
+    return forbidden($query) unless grep { $_ eq 'admin' } @{$query->{acls}};
 
     $conf->param( 'general.theme',      $query->{theme} )      if defined $query->{theme};
     $conf->param( 'general.data_model', $query->{data_model} ) if $query->{data_model};
@@ -581,7 +584,7 @@ sub config_save ($query, $render_cb) {
     my $parent = getppid;
     kill 'HUP', $parent;
 
-    return config($query, $render_cb);
+    return config($query);
 }
 
 =head2 themeclone
@@ -590,8 +593,8 @@ Clone a theme by copying a directory.
 
 =cut
 
-sub themeclone ($query, $render_cb) {
-    return forbidden($query, $render_cb) unless grep { $_ eq 'admin' } @{$query->{acls}};
+sub themeclone ($query) {
+    return forbidden($query) unless grep { $_ eq 'admin' } @{$query->{acls}};
     my ($theme, $newtheme) = ($query->{theme},$query->{newtheme});
 
     my $themedir = 'www/themes';
@@ -603,7 +606,7 @@ sub themeclone ($query, $render_cb) {
         $query->{failure} = 0;
         $query->{message} = "Successfully cloned theme '$theme' as '$newtheme'.";
     }
-    return config($query, $render_cb);
+    return config($query);
 }
 
 =head2 post_save
@@ -612,9 +615,9 @@ Saves posts submitted via the /post pages
 
 =cut
 
-sub post_save ($query, $render_cb) {
+sub post_save ($query) {
 
-    return forbidden($query, $render_cb) unless grep { $_ eq 'admin' } @{$query->{acls}};
+    return forbidden($query) unless grep { $_ eq 'admin' } @{$query->{acls}};
     my $to = delete $query->{to};
 
     #Copy this down since it will be deleted later
@@ -636,7 +639,7 @@ sub post_save ($query, $render_cb) {
     $query->{acls} = $acls;
     $query->{message} = $query->{failure} ? "Failed to add post!" : "Successfully added Post";
     delete $query->{id};
-    return posts($query, $render_cb);
+    return posts($query);
 }
 
 =head2 profile
@@ -645,8 +648,8 @@ Saves / updates new users.
 
 =cut
 
-sub profile ($query, $render_cb) {
-    return forbidden($query, $render_cb) unless grep { $_ eq 'admin' } @{$query->{acls}};
+sub profile ($query) {
+    return forbidden($query) unless grep { $_ eq 'admin' } @{$query->{acls}};
 
     #TODO allow users to do something OTHER than be admins
     if ($query->{password}) {
@@ -657,7 +660,7 @@ sub profile ($query, $render_cb) {
     $query->{user} = delete $query->{username};
     delete $query->{password};
 
-    return post_save($query, $render_cb);
+    return post_save($query);
 }
 
 =head2 post_delete
@@ -666,14 +669,14 @@ deletes posts.
 
 =cut
 
-sub post_delete ($query, $render_cb) {
-    return forbidden($query, $render_cb) unless grep { $_ eq 'admin' } @{$query->{acls}};
+sub post_delete ($query) {
+    return forbidden($query) unless grep { $_ eq 'admin' } @{$query->{acls}};
 
     $query->{failure} = $data->delete($query);
     $query->{to} = $query->{to};
     $query->{message} = $query->{failure} ? "Failed to delete post $query->{id}!" : "Successfully deleted Post $query->{id}";
     delete $query->{id};
-    return posts($query, $render_cb);
+    return posts($query);
 }
 
 =head2 series
@@ -683,7 +686,7 @@ Displays identified series, not all series.
 
 =cut
 
-sub series ($query, $render_cb) {
+sub series ($query) {
     my $is_admin = grep { $_ eq 'admin' } @{$query->{acls}};
 
     #we are either viewed one of two ways, /post/$id or /$aclname
@@ -712,7 +715,7 @@ sub series ($query, $render_cb) {
     $query->{primary_post} = $posts[0];
     $query->{in_series} = 1;
 
-    return posts($query,$render_cb);
+    return posts($query);
 }
 
 =head2 avatars
@@ -721,17 +724,14 @@ Returns the avatars.css.  Limited to 1000 users.
 
 =cut
 
-sub avatars ($query, $render_cb) {
+sub avatars ($query) {
     #XXX if you have more than 1000 editors you should stop
     push(@{$query->{acls}}, 'public');
     my $tags = _coerce_array($query->{tag});
-    my $processor = Text::Xslate->new(
-        path   => _dir_for_resource('avatars.tx'),
-    );
 
     my @posts = _post_helper($query, $tags, $query->{acls});
 
-    my $content = $processor->render('avatars.tx', {
+    my $content = themed_render('avatars.tx', {
         users => \@posts,
     });
 
@@ -744,7 +744,7 @@ Implements direct user profile view.
 
 =cut
 
-sub users ($query, $render_cb) {
+sub users ($query) {
     # Capture the username
     my (undef, undef, $username) = split(/\//, $query->{route});
 
@@ -762,7 +762,7 @@ sub users ($query, $render_cb) {
     $query->{user_obj}     = $posts[0];
     $query->{primary_post} = $posts[0];
     $query->{in_series}    = 1;
-    return posts($query,$render_cb);
+    return posts($query);
 }
 
 =head2 posts
@@ -771,7 +771,7 @@ Display multi or single posts, supports RSS and pagination.
 
 =cut
 
-sub posts ($query, $render_cb, $direct=0) {
+sub posts ($query, $direct=0) {
     #Process the input URI to capture tag/id
     $query->{route} //= $query->{to};
     my (undef, undef, $id) = split(/\//, $query->{route});
@@ -815,7 +815,7 @@ sub posts ($query, $render_cb, $direct=0) {
     }
 
     if (!$is_admin) {
-        return notfound($query, $render_cb) unless @posts;
+        return notfound($query) unless @posts;
     }
 
     my $fmt = $query->{format} || '';
@@ -950,7 +950,7 @@ sub posts ($query, $render_cb, $direct=0) {
         months    => [0..11],
     });
     return $content if $direct;
-    return Trog::Routes::HTML::index($query, $render_cb, $content, $styles);
+    return Trog::Routes::HTML::index($query, $content, $styles);
 }
 
 sub _templates_in_dir($path) {
@@ -999,7 +999,7 @@ Passing compressed=1 will gzip the output.
 
 =cut
 
-sub sitemap ($query, $render_cb) {
+sub sitemap ($query) {
 
     my (@to_map, $is_index, $route_type);
     my $warning = '';
@@ -1105,14 +1105,10 @@ sub sitemap ($query, $render_cb) {
     }
 
     @to_map = sort @to_map unless $is_index;
-    my $processor = Text::Xslate->new(
-        path   => _dir_for_resource('sitemap.tx'),
-    );
-
     my $styles = _build_themed_styles('sitemap.css');
 
     $query->{title} = "$query->{domain} : Sitemap";
-    my $content = $processor->render('sitemap.tx', {
+    my $content = themed_render('sitemap.tx', {
         title      => "Site Map",
         to_map     => \@to_map,
         is_index   => $is_index,
@@ -1120,7 +1116,7 @@ sub sitemap ($query, $render_cb) {
         route      => $query->{route},
     });
 
-    return Trog::Routes::HTML::index($query, $render_cb,$content,$styles);
+    return Trog::Routes::HTML::index($query,$content,$styles);
 }
 
 sub _rss ($query,$posts) {
@@ -1180,22 +1176,22 @@ Basically a thin wrapper around Pod::Html.
 
 =cut
 
-sub manual ($query, $render_cb) {
+sub manual ($query) {
     require Pod::Html;
     require Capture::Tiny;
 
-    return forbidden($query, $render_cb) unless grep { $_ eq 'admin' } @{$query->{acls}};
+    return forbidden($query) unless grep { $_ eq 'admin' } @{$query->{acls}};
 
     #Fix links from Pod::HTML
     $query->{module} =~ s/\.html$//g if $query->{module};
 
     my $infile = $query->{module} ? "$query->{module}.pm" : 'tCMS/Manual.pod';
-    return notfound($query,$render_cb) unless -f "lib/$infile";
+    return notfound($query) unless -f "lib/$infile";
     my $content = capture { Pod::Html::pod2html(qw{--podpath=lib --podroot=.},"--infile=lib/$infile") };
 
     my @series = _get_series(1);
 
-    return $render_cb->('manual.tx', {
+    return finish_render('manual.tx', {
         title       => 'tCMS Manual',
         theme_dir   => $td,
         content     => $content,
@@ -1268,4 +1264,54 @@ sub _themed_template ($resource) {
     return _dir_for_resource("templates/$resource")."/templates/$resource";
 }
 
+sub finish_render ($template, $vars, @headers) {
+
+    #XXX default vars that need to be pulled from config
+    $vars->{dir}       //= 'ltr';
+    $vars->{lang}      //= 'en-US';
+    $vars->{title}     //= 'tCMS';
+    #XXX Need to have minification detection and so forth, use LESS
+    $vars->{stylesheets}  //= [];
+    #XXX Need to have minification detection, use Typescript
+    $vars->{scripts} //= [];
+
+    # Absolute-ize the paths for scripts & stylesheets
+    @{$vars->{stylesheets}} = map { CORE::index($_, '/') == 0 ? $_ : "/$_" } @{$vars->{stylesheets}};
+    @{$vars->{scripts}}     = map { CORE::index($_, '/') == 0 ? $_ : "/$_" } @{$vars->{scripts}};
+
+    $vars->{contenttype} //= $Trog::Vars::content_types{html};
+    $vars->{cachecontrol} //= $Trog::Vars::cache_control{revalidate};
+
+    $vars->{code} ||= 200;
+
+    my $body = themed_render('header.tx', $vars);
+    $body   .= themed_render($template,$vars);
+    $body   .= themed_render('footer.tx', $vars);
+    $body = encode_utf8($body);
+
+    my $ct = 'Content-type';
+    my $cc = 'Cache-control';
+    push(@headers, $ct => $vars->{contenttype});
+    push(@headers, $cc => $vars->{cachecontrol}) if $vars->{cachecontrol};
+
+    #Return data in the event the caller does not support deflate
+    if (!$vars->{deflate}) {
+        push( @headers, "Content-Length" => length($body) );
+        return [ $vars->{code}, \@headers, [$body]];
+    }
+
+    #Compress
+    push( @headers, "Content-Encoding" => "deflate" );
+
+    #Disallow framing UNLESS we are in embed mode
+    push( @headers, "Content-Security-Policy" => qq{frame-ancestors 'none'} ) unless $vars->{embed};
+
+    my $dfh;
+    IO::Compress::Deflate::deflate( \$body => \$dfh );
+    print $IO::Compress::Deflate::DeflateError if $IO::Compress::Deflate::DeflateError;
+    push( @headers, "Content-Length" => length($dfh) );
+    return [$vars->{code}, \@headers, [$dfh]];
+}
+
+
 1;

+ 2 - 2
lib/Trog/Routes/JSON.pm

@@ -30,7 +30,7 @@ our %routes = (
 
 my $contenttype = "Content-type:application/json;";
 
-sub catalog ($query, $input, $=) {
+sub catalog ($query) {
     my $enc = JSON::MaybeXS->new( utf8 => 1 );
     my %rcopy = %{\%routes};
     foreach my $r (keys(%rcopy)) {
@@ -39,7 +39,7 @@ sub catalog ($query, $input, $=) {
     return [200,[$contenttype],[$enc->encode(\%rcopy)]];
 }
 
-sub webmanifest ($query, $input, $=) {
+sub webmanifest ($query) {
     my $enc = JSON::MaybeXS->new( utf8 => 1 );
     my %manifest = (
         "icons" => [

+ 17 - 0
lib/Trog/Vars.pm

@@ -0,0 +1,17 @@
+package Trog::Vars;
+
+use strict;
+use warnings;
+
+our %content_types = (
+    plain => "text/plain;",
+    html  => "text/html; charset=UTF-8",
+    json  => "application/json;",
+    blob  => "application/octet-stream;",
+);
+
+our %cache_control = (
+    revalidate => "no-cache, max-age=0",
+    nocache    => "no-store",
+    static     => "public, max-age=604800, immutable",
+);