ONO::Render

package ONO::Render;
################################################################################
# COPYRIGHT / LICENSE #
################################################################################
#
# This file is part of the ONO Software Project.
#
# Copyright (C) 2000-2025 Jos KIRPS [ www.kirps.com | jos_AT_kirps_DOT_com ]
# and The Joopita Project [ www.joopita.org | contact_AT_joopita_DOT_com ]
#
# This file, as well as other parts of the ONO Software Project or related
# elements, are FREE SOFTWARE available under the ARTISTIC LICENSE 2.0.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# For the full license, see /ono/osr/license/LICENSE.txt, or write to
# jos_AT_kirps_DOT_com or contact_AT_joopita_DOT_com.
#
################################################################################
# END OF COPYRIGHT / LICENSE, HERE COMES THE CODE ... #
################################################################################


use strict;

use CGI;

use ONO::Lib::Basic;

use ONO::IO;
use ONO::DB;

use ONO::Lib::DateTime::ToolBox;
use ONO::Lib::Web::Client;
use ONO::Lib::Web::Cookie;

use ONO::Lib::Parser;

use ONO::Render::Compress;
use ONO::Render::Placeholders;
use ONO::Render::Simple;

#: ONO::Render helps with the generation of both simple and complex web pages.
#:
#: Example 1:
#:
#; ONO::Render->render_simple("Hello World!");
#:
#: Example 2:
#:
#; print ONO::Render->open();
#; print ONO::Render->head();
#; print ONO::Render->body();
#; print "Hello World!"
#; print ONO::Render->close();
#:
#: Example 3:
#:
#; print ONO::Render->open($cookie,$lang);
#; print ONO::Render->head('MyName');
#; print ONO::Render->cssjs('','/site-data/demo/my-own-css.css');
#; print ONO::Render->title_key_desc_robots($title,$keywords,$description,$robots);
#; print ONO::Render->body("background:#333333");
#; print "Hello World!"
#; print ONO::Render->close();
#:
#: Example 4:
#:
#; print ONO::Render->render_pre($keywords,$description,$vars_ref);
#; print ONO::Render->render("Hello World!",$title,$vars_ref);

###############################################################################
# ONO - INIT
###############################################################################

sub open_simple {

#: Generate web page code (step 1 of 4), but don't include content-type,
#: cookie or lang data (good for saving HTML files, not good for live
#: rendering).

return qq~<!doctype html>\n<html>\n~;

}

sub open {

#: Generate web page code (step 1 of 4).
#: Note that you may pass cookie and language info to this function.

my $cookie = $_[1];
if ($cookie) {
$cookie = "$cookie\n";
}

return qq~Content-type:text/html; charset=UTF-8\n$cookie\n<!doctype html>\n<html lang="$_[2]">\n~;

}

sub head {

#: Generate web page code (step 2 of 4).
#: Note that you may pass author information to this function, and
#: that this step may optionally be followed by calling cssjs().

my $MOD;
if ($ENV{'MOD_PERL'}) {
$MOD = "/MP";
}

return qq~<head>
<meta charset="utf-8">
<meta name="Author" content="$_[1]">
<meta name="Generator" content="ONO$MOD">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="referrer" content="no-referrer">
~;

}

sub pwa {

#: Optional: Generate code for a PWA app, this needs to be
#: between the head and body calls, if used.

return qq~<link rel="manifest" href="manifest.json">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="$_[1]">
<link rel="apple-touch-icon" href="icon-192.png">
<meta name="theme-color" content="#000000">
~;

}

sub body {

#: Generate web page code (step 3 of 4).
#: Note that you may pass body style information to this function.

if ($_[1]) {

return qq~</head>\n<body style="$_[1]">~;

} else {

return qq~</head>\n<body>~;

}

}

sub close {

#: Generate web page code (step 4 of 4).

return qq~<script>onojs_init_post();</script>
</body>
</html>
~;

}

###############################################################################
# ONO - LOAD ONO CSS AND JS
###############################################################################

sub cssjs {

#: Load ONO CSS and ONO JS files, and also display a timestamp and client
#: IP information.
#:
#: Note that you may pass software base and additional CSS file information
#: to this function.
#:
#: CSS and JS files will be reloaded once a day on production systems,
#: while they will ALWAYS be reloaded on development stations (which makes
#: debugging CSS and JS a lot easier).

my (
$self,
$BASE,
$EXT,
$REL,
) = @_;

my (
$sec,$min,$hour,
$mday,$mon,$year,
) = ONO::Lib::DateTime::ToolBox->get();

my $IP = ONO::Lib::Web::Client->ip();
$IP =~ s~\.~-~g;
$IP =~ s~\:\:~-~g;

if ($REL) {
$REL = "&reloffset=$REL";
# } else {
# if ($BASE) {
# $REL = "&reloffset=2";
# }
}

my $CSSJS;

if (ONO::IO->devstation) {

$CSSJS = qq~<link rel="stylesheet" type="text/css" href="$BASE/ono/osr/css/ono/ono_$year$mon.css?$year$mon$mday$hour$min$sec" media="all">
<link rel="stylesheet" type="text/css" href="$BASE/ono/osr/css/ono/ono_buttons_$year$mon.css?$year$mon$mday$hour$min$sec">
<script src="$BASE/ono/osr/javascript/ono/ono_$year$mon.js?$year$mon$mday$hour$min$sec&devstation=1$REL"></script>
<!-- ONORenderEngine-$year$mon$mday-$hour$min$sec-$IP -->
~;

} else {

my $TODAY = "$year$mon$mday$hour";

$CSSJS = qq~<link rel="stylesheet" type="text/css" href="$BASE/ono/osr/css/ono.css?$TODAY" media="all">
<link rel="stylesheet" type="text/css" href="$BASE/ono/osr/css/ono_buttons.css?$TODAY">
<script src="$BASE/ono/osr/javascript/ono.js?$TODAY$REL"></script>
<!-- ONORenderEngine-$year$mon$mday-$hour$min$sec-$IP -->
~;

}

if ($EXT) {
$CSSJS .= qq~<link rel="stylesheet" type="text/css" href="$EXT">~;
}

return $CSSJS;

}

###############################################################################
# ONO - INIT
###############################################################################

sub init {

#: Get the name, the main directory name, as well as configuration data
#: of the current ONO project.

$ENV{'SCRIPT_NAME'} =~ m~/cgi-bin/local/perl/(.*?)/(.*?)\.pl~;

my $app = $1;
my $App = ucfirst $app;

my %vars;
my $vars_ref = \%vars;

my %conf = ONO::IO->confread("cgi-bin/local/perl/$app/$App/Config.conf");

return ($app,$App,\%conf);

}

###############################################################################
# ONO - PRINT TITLE, KEY, DESC
###############################################################################

sub title_key_desc_robots {

#: Generate HTML code: page title, keywords, description, and robot
#: information. Default robot data is 'index, follow'.

my $ROBOTS = $_[4];
if (!$ROBOTS) {
$ROBOTS = "index, follow";
}

return qq~<title>$_[1]</title>
<meta name="keywords" content="$_[2]">
<meta name="description" content="$_[3]">
<meta name="robots" content="$ROBOTS">
~;

}

###############################################################################
# ONO - USEFUL TITLE GENERATOR
###############################################################################

sub title {

#: Generate a useful title by analyzing the page code. Only useful for some
#: generated content, is is strongly recommended to use the ONO SEO tools
#: in most cases.

my ($self,$title,$WEB) = @_;

my $TITLE;

if ($WEB =~ m~<h1>(.*?)</h1>~) {
$title = qq~$1 - $title~;
}
if ($WEB =~ m~<h1 style="(.*?)">(.*?)</h1>~) {
$title = qq~$2 - $title~;
}

$title =~ s~(\?|\!)~~g;

my @tps = split(/ - /,$title);
my %used;

foreach my $tp (@tps) {
$tp =~ s~(\(|\))~~g;
if ($tp =~ /[A-Za-z0-9]/ && !$used{$tp} && $TITLE !~ / $tp / && length "$TITLE$tp" < 55 && $tp !~ /#/) {
$TITLE .= qq~$tp - ~;
$used{$tp}++;
}
}

return $TITLE;

}

###############################################################################
# ONO - USEFUL DESCRIPTION GENERATOR
###############################################################################

sub description {

#: Generate a useful description by analyzing the page code. Only useful
#: for some generated content, is is strongly recommended to use the ONO SEO
#: tools in most cases.

my (
$self,
$desc,
$WEB,
$default,
) = @_;

$WEB =~ s~(\n|\r|\t|\")~ ~g;
$WEB =~ s~</li>~\.~g;
$WEB =~ s~<(.*?)>~ ~g;
$WEB =~ s~ ~ ~g;
$WEB =~ s~\|~-~g;

$WEB = ONO::Lib::Basic->remove_bad_spaces($WEB);

my ($DESC,$success);

my @wp = split(/ /,$WEB);
my $whilecounter;
while ("$DESC$wp[$whilecounter]" < 128 && $whilecounter < 25) {
if ($wp[$whilecounter] !~ /#/ && $wp[$whilecounter] !~ m~(http|https)://~ && $wp[$whilecounter] !~ m~(\{|\}|\(|\)|\=|function|window\.|\.onload)~) {
$DESC .= qq~$wp[$whilecounter] ~;
}
$whilecounter++;
}
$DESC .= "...";

if (length $DESC > 60) {
$success++;
}

if (!$success) {
$DESC = $default;
}

my $whilecounter;
while ($whilecounter < 256 && ($DESC =~ s~ ~ ~g || $DESC =~ s~--~-~g || $DESC =~ s~\. \.~\.~g || $DESC =~ s~ $~~g || $DESC =~ s~^ ~~g || $DESC =~ s~\,$~~g)) {
$whilecounter++;
}

if ($DESC =~ /(\{|\})/) {

# don't return JS code

return "";

} else {

return ucfirst $DESC;

}
}

###############################################################################
# ONO - PRE-RENDER
###############################################################################

sub render_simple {

#: Print a very simple HTML page, only the page code itself is required as
#: input.
#:
#: Simply call the function, do NOT use any print instruction here.
#: ONO CSS and JS will always be loaded.

my (
$self,
$WEB,
$no_content_type
) = @_;

my $BASE = ONO::IO->base();
my $CSSJS = &cssjs("",$BASE);

if (!$no_content_type) {
print qq~Content-Type: text/html; charset=UTF-8\n\n~;
}

print qq~<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="index,follow">
$CSSJS
</head>
<body>
$WEB
<script>onojs_init_post();</script>
</body>
</html>
~;

#<script>onojs_custom_select_exec();</script>

return "";

}

###############################################################################
# ONO - PRE-RENDER
###############################################################################

sub render_pre {

#: Print the head of a HTML page, supporting cookie creation via input vars.
#: Note that this will NOT close the page head or open the page body!
#: This function should be used on top of a script, followed by calling the
#: render() function at the bottom of the script.
#:
#: Simply call the function, do NOT use any print instruction here.
#: ONO CSS and JS will always be loaded.

my (
$self,
$keywords,
$description,
$vars_ref,
) = @_;

my %vars = %$vars_ref;

my ($MOD,$BASE);
if ($ENV{'MOD_PERL'}) {
$MOD = qq~/MP~;
}
if ($ENV{'REQUEST_URI'} =~ m~^(.*?)/cgi-bin/~) {
$BASE = $1;
}

my ($app,$App,$conf_ref) = &init;
my %conf = %$conf_ref;

my $cookie;
if ($vars{'cookie_sid'}) {
$cookie = "$vars{'cookie_sid'}\n";
}

my $CSSJS = &cssjs("",$BASE);
if (ONO::IO->exists("favicon.png")) {
$CSSJS .= qq~<link rel="shortcut icon" type="image/png" href="/favicon.png">~;
}

if (!$vars{'ono_render_disable_content_type'}) {
print qq~Content-Type: text/html; charset=UTF-8\n$cookie\n~;
}
print qq~<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="index,follow">
<meta name="Generator" content="ONO$MOD">
<meta name="keywords" content="$keywords">
<meta name="description" content="$description">
$CSSJS
<style>
h1,h2,h3,h4,h5,h6 {color:$conf{'actioncolor'}}
a {color:$conf{'actioncolor'}}
</style>
~;

return "";

}

###############################################################################
# ONO - RENDER
###############################################################################

sub render {

#: After using render_pre(), this will print the rest of a HTML page. Note
#: that this will to some automated processing that's not yet documented
#: here, so be careful (or check the code).
#:
#: Simply call the function, do NOT use any print instruction here.
#: ONO CSS and JS will always be loaded.

my (
$self,
$WEB,
$title,
$vars_ref,
) = @_;

if ($WEB =~ /ckeditor/i) {
print qq~<script src="/ono/osr/javascript/ckeditor/ckeditor.js"></script>~;
}

my $BASE;
if ($ENV{'REQUEST_URI'} =~ m~^(.*?)/cgi-bin/~) {
$BASE = $1;
}
if ($BASE) {
$WEB =~ s~="/~="$BASE/~g;
$WEB =~ s~='/~='$BASE/~g;
$WEB =~ s~url\('/~url\('$BASE/~g;
$WEB =~ s~="$BASE$BASE/~="$BASE/~g;
}

my (
$app,
$App,
$conf_ref,
) = &init;
my %conf = %$conf_ref;

my %vars = %$vars_ref;

my ($status,$WEB) = ONO::Lib::Parser->parse($WEB,$vars_ref);
$WEB = ONO::Render::Placeholders->parse($WEB);

my ($META,$STYLE,$SCRIPT,$whilecounter,$ONLOAD,$FOCUS_ONLOAD,$ADD_ONLOAD,@opts);

while ($WEB =~ s/<meta (.*?)>// && $whilecounter < 256) {
$whilecounter++;
if ($1) {
$META = qq~<meta $1>~;
}
}
while ($WEB =~ s/<script_head (.*?)><\/script>// && $whilecounter < 256) {
$whilecounter++;
if ($1) {
$SCRIPT .= qq~<script $1></script>\n~;
}
}
while ($WEB =~ s/<link_head (.*?)>// && $whilecounter < 256) {
$whilecounter++;
if ($1) {
$SCRIPT .= qq~<link $1>\n~;
}
}

$WEB =~ s~<\!script~<script~gi;

$title =~ s/(<|>)//g;

my (
$community,
$admin,
$home_link,
) = &_get1("",\%vars);

my $whilecounter;
while ($WEB =~ s~<onload="(.*?)">~~ && $whilecounter < 256) {
$whilecounter++;
$ADD_ONLOAD .= "$1;";
}

if (!$vars{'sid'} && (!$vars{'mode'} || $vars{'mode'} =~ /^(home)$/)) {
# $FOCUS_ONLOAD = qq~document.loginform.username.focus()~;
}

if ($ADD_ONLOAD || $FOCUS_ONLOAD) {
$ONLOAD = qq~ onload="$ADD_ONLOAD;$FOCUS_ONLOAD" ~;
}

if ($vars{'ono_render_body_style'}) {
$STYLE = qq~ style="$vars{'ono_render_body_style'}"~;
}

print qq~<title>$title</title>
$META
$SCRIPT
</head>
<body$STYLE$ONLOAD>
$WEB
<script>onojs_init_post();</script>
</body>
</html>
~;

}

###############################################################################
# render myself
###############################################################################

sub render_myself {

#: This will print the code of the current front end script. Note that this
#: function is just for demo and testing purposes.

my $WEB = qq~<div class="bold mb10">$ENV{'SCRIPT_NAME'}</div>~;

foreach my $line (ONO::IO->list($ENV{'SCRIPT_NAME'})) {

$line =~ s~(\n|\r|\t)~~g;

$line =~ s~&~&~g;
$line =~ s~<~<~g;
$line =~ s~<~>~g;

if ($line =~ /[A-Za-z0-9]/ || $line =~ /(\(|\)|\{|\})/) {
$WEB .= qq~<div>$line</div>~;
} else {
$WEB .= qq~<div class="p5"></div>~;
}

}

return qq~<div class="box_code mb10">$WEB</div>~;

}

###############################################################################
# internal subs
###############################################################################

sub _get1 {

#: Internal function, used by render();

my $vars_ref = $_[1];
my %vars = %$vars_ref;

$ENV{'SCRIPT_NAME'} =~ m~/cgi-bin/local/perl/(.*?)/~;
my $community = $1;
my $admin;

my $top = 72;
if ($ENV{'SCRIPT_NAME'} =~ m~/cgi-bin/local/perl/(.*?)/admin/index\.pl~) {
$top = 92;
$admin = "/admin";
}

my $home_link = qq~/cgi-bin/local/perl/$community$admin/index.pl~;
if ($vars{'render_home_link'}) {
$home_link = $vars{'render_home_link'};
}

return ($community,$admin,$top,$home_link);

}

###############################################################################
# end of script
###############################################################################

1;

__END__