ONO::Lib::Basic

package ONO::Lib::Basic;
################################################################################
# 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;

###############################################################################
# ADD / REMOVE STUFF
###############################################################################

sub add_leading_zeroes {

#: Add leading zeroes to a string.

my (
$self,
$num,
$length,
) = @_;

if ($length > 256) {
$length = 256;
}

my $whilecounter;
while (length $num < $length && $whilecounter < 256) {
$whilecounter++;
$num = "0$num";
}

return $num;

}

sub add_tailing_zeroes {

#: Add tailing zeroes to a string.

my $num = $_[1];

for (my $i = 0; $i < $_[2]; $i++) {
$num .= "0";
}

return $num

}

sub remove_leading_spaces {

#: Remove leading spaces from a string.
#: See also remove_bad_spaces() and remove_bad_spaces_sentence().

my $string = $_[1];

$string =~ s/^\s+//;

return $string;

}

sub remove_leading_zeroes {

#: Remove leading zeroes from a string.
#: See also remove_leading_zeroes_comma().

my $num = $_[1];

$num =~ s/^0+(?=[0-9])//;

return $num;

}

sub remove_leading_zeroes_comma {

#: Remove leading zeroes from a string, respecting comma numbers.

my $num = $_[1];

if ($num =~ /(\.|\,)/) {

my $whilecounter;
while ($num =~ s/^0// && $whilecounter < 256) {
$num =~ s/^0//;
$whilecounter++;
}
if ($num =~ /^(\.|\,)/) {
$num = "0$num";
}

} else {

$num =~ s/^0+(?=[0-9])//;

}

return $num;

}

sub remove_tailing_spaces {

#: Remove tailing spaces from a string.
#: See also remove_bad_spaces() and remove_bad_spaces_sentence().

my $string = $_[1];

$string =~ s/\s+$//;

return $string;

}

###############################################################################
# NUMBERS
###############################################################################

sub num_human_readable {

#: Make numbers human readable by adding separators.
#: This does NOT work with comma numbers!

my $num = $_[1];
my $lang = $_[2];
my $length = length $num;

my $sep = ",";
if ($lang =~ /^(de|fr|lu)$/) {
$sep = ".";
}

if ($length > 3) {
$num = substr($num,0,$length-3).$sep.substr($num,$length-3,3);
if ($length > 6) {
$num = substr($num,0,$length-6).$sep.substr($num,$length-6,8);
}
}

return $num;

}

sub num_round_up {

#: Not implemented yet.
#: See also num_round_down().

}

sub num_round_down {

#: Round number down.
#: See also num_round_up().

my $num = $_[1];
my $len = $_[2];
my $length = length $num;

if (!$len) {
$len = 3;
}

if ($length > $len) {
$num = substr($num,0,$length-$len);
$num = &add_tailing_zeroes("",$num,$len);
}

return $num;

}

###############################################################################
# ARRAY STUFF
###############################################################################

sub array_flip {

#: Flip two strings in an array (a,b -> b,a).
#: See also array_flip_random().

my @array = @_;
return ($array[2],$array[1]);

}

sub array_flip_random {

#: Flip two strings in an array (a,b -> b,a) width a 50/50 chance.
#: See also array_flip().

my @array = @_;
if (int(rand(100)) > 49) {
return ($array[1],$array[2]);
} else {
return ($array[2],$array[1]);
}

}

sub array_limit {

#: Limit the number of elements in an array.

my $limit = $_[1];

my (@array,$counter);

foreach my $item (@_) {
$counter++;
if ($counter > 2 && $counter < $limit+3) {
@array = (@array,$item);
}
}

return @array;

}

sub array_randomize {

#: Randomize array.
#: Same as randomize_array().

my (
$self,
@array
) = @_;
return &randomize_array("",@array);

}

sub array_sortbylength {

#: Sort an array by the length of its elements.

my (
$self,
@array
) = @_;

return reverse sort { length $a <=> length $b } @array;

}

sub array_sortbynumber {

#: Sort an array by number.

my (
$self,
@array
) = @_;

return sort { $a <=> $b } @array;

}

###############################################################################
# CLEAN STRINGS
###############################################################################

sub cleanstring {

#: Clean up a string, allowing only numbers, letters, umlauts, and some special characters.

my $string = $_[1];

$string =~ s~[^A-Za-z0-9\ \'\-\_\,\;\.\:\Ä\Ë\Ö\Ü\ä\à\â\ã\é\è\ë\ê\ï\î\ö\ô\ü\ç]~~g;

return $string;

}

sub cleanmail {

#: Clean up an e-mail address.
#: Other characters are technically allowed, but ONO doesn't like or allow them!

my $string = lc $_[1];

$string =~ s~[^a-z0-9\-\_\@\.]~~g;

return $string;

}

sub cleanurl {

#: Clean up an URL.

my $string = $_[1];
my $add_http = $_[2];

$string =~ s~[^A-Za-z0-9\_\-\.\,\:\?\&\;\/\%\#]~~g;

if ($add_http && $string !~ m~^(http|https)://~) {
$string = "http://$string";
}

return $string;

}

###############################################################################
# COMPACT HTML CODE
###############################################################################

sub compact_code {

#: Compress HTML code.
#: Same as html_compress().

return &html_compress("",$_[1]);

}

sub html_compress {

#: Compress HTML code, by removing non-required spaces and line feeds,
#: returns, and tabs.

my $HTML = $_[1];

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

my $whilecounter;
while ($whilecounter < 256 && $HTML =~ s/ / /g) {
$whilecounter++;
$HTML =~ s/ / /g;
$HTML =~ s/ / /g;
$HTML =~ s/ / /g;
$HTML =~ s/ / /g;
$HTML =~ s/ / /g;
}

$HTML =~ s~\> \<~\>\<~g;

return $HTML;

}

###############################################################################
# CSS CODE GENERATORS
###############################################################################

sub css_rotate {

#: Rotate an HTML element using CSS code.

return qq~-ms-transform:rotate($_[1]deg);-webkit-transform:rotate($_[1]deg);transform:rotate($_[1]deg);~;

}

###############################################################################
# DOMAIN EXAMPLE
###############################################################################

sub domain_example {

#: Generate a localized example domain.

my $BLK_ref = $_[1];
my %BLK = %$BLK_ref;
my $tld = "com";
if ($BLK{'lang'} =~ /^(lu|de|fr)$/) {
$tld = $BLK{'lang'};
}

return lc "www.$BLK{'example'}.$tld";

}

###############################################################################
# EMAIL TOOLS
###############################################################################

sub email_valid {

#: Check if email address is valid.

if (&valid_email($_[1])) {
return 1;
} else {
return 0;
}

}

sub email_example {

#: Generate a localized example email address.

my $BLK_ref = $_[1];
my %BLK = %$BLK_ref;
my $tld = "com";
if ($BLK{'lang'} =~ /^(lu|de|fr)$/) {
$tld = $BLK{'lang'};
}

return lc "$BLK{'you'}\@$BLK{'example'}.$tld";

}

###############################################################################
# INPUT FIELD CLEANUP
###############################################################################

sub inputfield {

#: Clean up an input field, and trim it if required.

my $input = $_[1];

$input =~ s~"~"~g;
$input =~ s~\[\[~###OPEN###~g;
$input =~ s~\]\]~###CLOSE###~g;

if ($_[2]) {
$input = substr($input,0,$_[2]);
}

return $input;

}

sub inputtext {

#: Clean up an text field.

my $input = $_[1];

$input =~ s~"~"~g;
$input =~ s~\[\[~###OPEN###~g;
$input =~ s~\]\]~###CLOSE###~g;

return $input;

}

###############################################################################
# PATH BREAKS
###############################################################################

sub pathbreaks {

#: Make a path breakable, to display it on responsive pages for example.
#: See also pathbreaks_mail().

my $path = $_[1];
my $http;

if ($path =~ s~^(http|https)(://)~~) {
$http = "$1$2";
}

$path =~ s~//~/~;
$path =~ s~(_|-|\/)~$1​~g;

return "$http$path";

}

sub pathbreaks_mail {

#: Make an email address breakable, to display it on responsive pages for example.
#: This will automatically turn it into a clickable link.
#: See also pathbreaks().

return qq~<a href="mailto:$_[1]" class="$_[2]">~.&pathbreaks("",$_[1]).qq~</a>~;

}

###############################################################################
# RANDOM STUFF
###############################################################################

sub randomize_array {

#: Randomize array.
#: Same as array_randomize().

my (
$self,
@array
) = @_;

my (@new,$whilecounter);

while (@array && $whilecounter < 8192) {
$whilecounter++;
my $i = int(rand() * @array);
push @new, $array[$i];
splice(@array, $i, 1);
}

return @new;

}

sub random_100 {

#: Return a random number between 0 and 99.

return int(rand(100));

}

sub random_percent {

#: Will return 1 on an X-to-100 percent chance

my $threshold = $_[1];

if (&random_100 > 99-$threshold) {
return 1;
} else {
return 0;
}

}

###############################################################################
# REMOVE DOUBLE CHARS
###############################################################################

sub remove_bad_spaces {

#: Remove bad spaces from a string, which includes leading and tailing spaces,
#: as well as double spaces anywhere.
#: See also remove_bad_spaces_sentence().

my (
$self,
$input,
$max,
) = @_;

$max = &_remove_max("",$max);

my $whilecounter;
while (($input =~ s~ ~ ~g || $input =~ s~^ ~~g || $input =~ s~ $~~g) && $whilecounter < $max) {
$whilecounter++;
$input =~ s~ ~ ~g;
$input =~ s~ ~ ~g;
$input =~ s~^ ~~g;
$input =~ s~ $~~g;
}

return $input;

}

sub remove_bad_spaces_sentence {

#: Remove bad spaces from a sentence or a text, respecing dots, commas, etc.
#: This automatically includes the remove_bad_spaces routine.
#: See also remove_bad_spaces().

my (
$self,
$input,
$max,
) = @_;

$max = &_remove_max("",$max);

$input = &remove_bad_spaces("",$input,$max);

my $whilecounter;
while (($input =~ s~ (\.|\!|\?)$~$1~g || $input =~ s~ (\.|\,|\;)~$1~g) && $whilecounter < $max) {
$whilecounter++;
$input =~ s~ (\.|\!|\?)$~$1~g;
$input =~ s~ (\.|\,|\;)~$1~g;
}

return $input;

}

sub remove_double_returns {

my (
$self,
$input,
$max,
$ext,
) = @_;

#: Automatically remove returns and tabs, optionally remove leading and tailing returns

$max = &_remove_max("",$max);

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

my $whilecounter;
while ($input =~ s~\n\n~\n~g && $whilecounter < $max) {
$whilecounter++;
$input =~ s~\n\n\n~\n~g;
$input =~ s~\n\n~\n~g;
}

if ($ext) {
$input =~ s~^\n~~;
$input =~ s~\n$~~;
}

return $input;

}

sub remove_doubles {

my (
$self,
$input,
$char,
$max,
$ext,
) = @_;

#: Remove doubles or a specific char.

$max = &_remove_max("",$max);

my $whilecounter;
while ($input =~ s~$char$char~$char~g && $whilecounter < $max) {
$whilecounter++;
$input =~ s~$char$char$char~$char~g;
$input =~ s~$char$char~$char~g;
}

if ($ext) {
$input =~ s~^$char~~;
$input =~ s~${char}$~~;
}

return $input;

}

sub _remove_max {

#: Internal subroutine.

my $max = $_[1];

if (!$max || $max > 65536) {
$max = 1024;
}

return $max;

}

###############################################################################
# SAFE URLs
###############################################################################

sub safeurl {

#: Make a string safe to be displayed on an HTML page.

my $URL = $_[1];
$URL =~ s~\<~<~gi;
$URL =~ s~\>~>~gi;
$URL =~ s~\"~"~gi;
return $URL;

}

###############################################################################
# SHORT STRING
###############################################################################

sub shortstring {

my (
$self,
$input,
$max,
$switches,
) = @_;

#: Limit the length of a string, replace the middle of the string by dots if required.
#:
#: -c cleanup, will remove double stuff
#: -P remove path, only keep everything behind the last slash (always applied, even if string not long)

if (!$max) {
$max = 48;
}

if ($switches =~ /c/) {
$input =~ s~\'\'~\'~g;
}
if ($switches =~ /P/) {
$input =~ s~^(.*)/~~;
}

if (length $input > $max) {

return substr($input,0,int($max/2))." ... ".substr($input,(length $input)-int($max/2),int($max/2));

} else {

return $input;

}

}

###############################################################################
# SORT STRING
###############################################################################

sub sortstring {

#: Generate a sort string, which can be used to sort arrays for example.
#: See also sortstring_wrapped().

my $sort = lc $_[1];

$sort =~ s~[^a-z0-9]~~gi;

return $sort;

}

sub sortstring_wrapped {

#: Generate a sort string, make it usable within HTML code.
#: See also sortstring().

return "<!-- sort:".&sortstring("",$_[1])." -->";

}

sub sort_case_insensitive {

#: Sort an array, case insensitive.

my ($dump,@list) = @_;

return sort { lc($a) cmp lc($b) } @list;

}

###############################################################################
# SUBSTR
###############################################################################

sub substr_first_digit {

#: Return the first digit of a string.

return substr($_[1],0,1);

}

sub substr_last_digit {

#: Return the last digit of a string.

return substr($_[1],(length $_[1])-1,1);

}

###############################################################################
# TEXT FIELD
###############################################################################

sub textfield {

#: Clean up text field.
#: See also inputtext().

return &inputtext("",$_[1]);

}

###############################################################################
# UPPER CASE
###############################################################################

sub uc {

#: A better upper case, which also supports some international strings (umlauts).

my $input = $_[1];

$input =~ s~(ë|é|è|ê)~E~g;
$input =~ s~(ä|à|â)~A~g;
$input =~ s~(ï|î)~I~g;
$input =~ s~(ö|ô)~O~g;

return uc $input;

}

sub ucfirstletters {

#: Uppercase the first letter of each word in a string.
#: See also ucfirstletters_mkspaces().

my $input = $_[1];

$input =~ s/([\w']+)/\u\L$1/g;

return $input;

}

sub ucfirstletters_mkspaces {

#: Uppercase the first letter of each word in a string,
#: but first replace underscores by spaces.
#: See also ucfirstletters().

my $input = $_[1];
$input =~ s~_~ ~g;

return &ucfirstletters("",$input);

}

###############################################################################
# UMLAUTS
###############################################################################

sub umlaut {

#: Count the number of different umlauts in a string.
#: Can be used to test if a string contains umlauts at all (returns zero if not).

my $umlaut;

foreach my $um (¨auts) {
if ($_[1] =~ /$um/) {
$umlaut++;
}
}

return $umlaut;

}

sub umlauts {

#: Array of often used umlaut characters.

return ('Ä','Ë','Ö','Ü', 'ä','à','â','ã', 'é','è','ë','ê', 'ï','î', 'ö','ô', 'ü','û', 'ç','ß');

}

sub umlaut_make_uppercase {

#: Make character or string uppercase, also make umlauts uppercase.
#: Note that this is NOT EXACTLY the reverse of umlaut_make_lowercase.

my $UC = uc $_[1];

$UC =~ s~ä~Ä~g;
$UC =~ s~ë~Ë~g;
$UC =~ s~ï~Ï~g;
$UC =~ s~ö~Ö~g;
$UC =~ s~ü~Ü~g;

$UC =~ s~(à|â|ã)~A~g;
$UC =~ s~(é|è|ê)~E~g;
$UC =~ s~î~I~g;
$UC =~ s~ô~O~g;
$UC =~ s~û~U~g;

return $UC;

}

sub umlaut_make_lowercase {

#: Make character or string lowercase, also make umlauts uppercase.
#: Note that this is NOT EXACTLY the reverse of umlaut_make_uppercase.

my $LC = lc $_[1];

$LC =~ s~Ä~ä~g;
$LC =~ s~Ë~ë~g;
$LC =~ s~Ï~ï~g;
$LC =~ s~Ö~ö~g;
$LC =~ s~Ü~ü~g;

$LC =~ s~É~é~g;

$LC =~ s~Î~î~g;
$LC =~ s~Ô~ô~g;

return $LC;

}

sub umlaut_string_length {

#: Count the number of elements in umlaut_string_to_array().

my @array = ¨aut_string_to_array("",$_[1]);
my $length = @array;
return $length;

}

sub umlaut_string_to_array {

#: Return an array of all letters used in an array, respecting umlauts.

my $input = $_[1];

my (@letters,$is_umlaut);

for (my $a = 0; $a < length $input; $a++) {

if ($is_umlaut) {

$is_umlaut = 0;

} else {

my $letter = substr($input,$a,1);
my $umlaut = substr($input,$a,2);

if ($umlaut !~ /[A-Za-z]/) {
if (¨aut("",$umlaut)) {
$letter = $umlaut;
$is_umlaut = 1;
}
}

@letters = (@letters,$letter);

}

}

return @letters;

}

sub umlaut_simplify {

my (
$self,
$input,
$mode
) = @_;

#: Deal with umlauts in a string in various ways, using different modes:
#:
#: -0 default mode
#: -1 use single letters only (warning: 'ß' will also be replaced by single 's')
#: -2 same as 0, but will replace 'ß' by 'SS'
#: -3 mark umlauts: ä -> ae_, useful for exported speech files for example

my $input = $_[1];

if ($mode == 3) {

$input =~ s~Ä~Ae_~g;
$input =~ s~ä~ae_~g;

$input =~ s~Ö~Oe_~g;
$input =~ s~ö~oe_~g;

$input =~ s~Ü~Ue_~g;
$input =~ s~ü~ue_~g;

$input =~ s~ß~ss_~g;

$input =~ s~É~E_~g;
$input =~ s~Î~I_~g;

$input =~ s~(à|â|ã)~a_~g;
$input =~ s~(é|è|ë|ê)~e_~g;
$input =~ s~(ï|î)~i_~g;

$input =~ s~ô~o_~g;
$input =~ s~û~u_~g;
$input =~ s~ç~c_~g;

} else {

if ($mode == 1) {

$input =~ s~Ä~A~g;
$input =~ s~ä~a~g;

$input =~ s~Ö~O~g;
$input =~ s~ö~o~g;

$input =~ s~Ü~U~g;
$input =~ s~ü~u~g;

$input =~ s~ß~s~g;

} else {

$input =~ s~Ä~Ae~g;
$input =~ s~ä~ae~g;

$input =~ s~Ö~Oe~g;
$input =~ s~ö~oe~g;

$input =~ s~Ü~Ue~g;
$input =~ s~ü~ue~g;

if ($mode == 2) {
$input =~ s~ß~SS~g;
} else {
$input =~ s~ß~ss~g;
}

}

$input =~ s~É~E~g;
$input =~ s~Î~I~g;

$input =~ s~(à|â|ã)~a~g;
$input =~ s~(é|è|ë|ê)~e~g;
$input =~ s~(ï|î)~i~g;

$input =~ s~ô~o~g;
$input =~ s~û~u~g;
$input =~ s~ç~c~g;

}

return $input;

}

sub umlaut_transform {

#: Transform crappy 2-byte string to real utf-8 umlaut code

return ¨aut_reverse("",¨aut_simplify("",$_[1],2));

}

sub umlaut_reverse {

#: Restore umlauts in simplified string.
#: This will ONLY work for german umlauts, and not for SS

my $input = $_[1];

$input =~ s~ue~ü~g;
$input =~ s~ae~ä~g;
$input =~ s~oe~ö~g;
$input =~ s~SS~ß~g;

return $input;

}

###############################################################################
# VALIDATORS
###############################################################################

sub valid_email {

#: Test if email address is valid.

# if ($_[1] =~ /^(.*?)\@(.*?)\.(.*?)$/) {
if ($_[1] =~ /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/) {
return 1;
} else {
return 0;
}

}

sub valid_url {

#: Test if URL is valid.

if ($_[1] =~ /^(.*?)\.(.*?)$/) {
my ($one,$two) = ($1,$2);
if ($one =~ /[A-Za-z0-9]/ && $two =~ /[A-Za-z0-9]/) {
return 1;
} else {
return 0;
}
} else {
return 0;
}

}

sub valid_ip {

#: Test if IP address is valid.

if ($_[1] =~ /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/) {
return 1;
} else {
return 0;
}

# if ($_[1] =~ /^(.*?)\.(.*?)\.(.*?)\.(.*?)$/) {
# my $err;
# foreach my $part ($1,$2,$3,$4) {
# if ($part > 255 || $part !~ /[0-9]/) {
# $err++;
# } else {
# my $test = $part;
# $test =~ s~[^0-9]~~g;
# if ($part ne $test) {
# $err++;
# }
# }
# }
# if ($err) {
# return 0;
# } else {
# return 1;
# }
# } else {
# return 0;
# }

}

sub valid_ip_range {

#: Test if IP range is valid.

if (&valid_ip("",$_[1])) {
return 1;
} else {
if ($_[1] =~ /^(.*?)\.(.*?)$/ && $_[1] =~ /[0-9]/ && $_[1] !~ /[A-Za-z\-\_\,\:\;]/ && $_[1] !~ /^\./ && $_[1] !~ /\.$/) {
if ($_[1] =~ /^(.*?)\.(.*?)\.(.*?)$/) {
if ($1 > -1 && $1 < 256 && $2 > -1 && $2 < 256 && $3 > -1 && $3 < 256) {
return 3;
} else {
return 0;
}
} else {
if ($_[1] =~ /^(.*?)\.(.*?)$/) {
if ($1 > -1 && $1 < 256 && $2 > -1 && $2 < 256) {
return 2;
} else {
return 0;
}
} else {
return 0;
}
}
} else {
return 0;
}
}

}

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

1;

__END__