
# script_joomla_desc()
sub script_joomla_desc
{
return "Joomla";
}

sub script_joomla_uses
{
return ( "php" );
}

# script_joomla_longdesc()
sub script_joomla_longdesc
{
return "Joomla is an open source content management system";
}

# script_joomla_versions([for-install])
sub script_joomla_versions
{
my ($install) = @_;
if ($install) {
	return ( "4.2.6", "3.10.11" );
	}
else {
	return ( "4.2.6", "3.10.11", "2.5.28", "1.5.26", "1.0.15" );
	}
}

sub script_joomla_abandoned
{
local ($ver) = @_;
return $ver < 2 ? 1 : 0;
}

sub script_joomla_version_desc
{
local ($ver) = @_;
return $ver < 3 ? "$ver (Unsupported)" : $ver;
}

sub script_joomla_release
{
return 8;		# Real version location moved
}

sub script_joomla_php_fullver
{       
local ($d, $ver, $sinfo) = @_;
return &compare_versions($ver, 4) >= 0 ? "7.2" : "5.3";
}

# script_joomla_can_upgrade(&sinfo, newver)
sub script_joomla_can_upgrade
{
local ($sinfo, $newver) = @_;
if ($newver >= 1.5 && $sinfo->{'version'} < 1.5) {
	# Cannot upgrade from 1.4 to 1.5
	return 0;
	}
if ($newver >= 1.6 && $sinfo->{'version'} < 1.6) {
	# Cannot upgrade from 1.5 to 1.6
	return 0;
	}
if ($newver >= 3.0 && $sinfo->{'version'} < 3.0) {
	# Cannot upgrade from < 3.0 and 3.x
	return 0;
	}
if ($newver >= 4.0 && $sinfo->{'version'} < 4.0) {
	# Cannot upgrade from 3 to 4
	return 0;
	}
return 1;
}

sub script_joomla_category
{
return "CMS";
}

sub script_joomla_php_vers
{
local ($d, $ver) = @_;
return $ver >= 1.5 ? ( 5 ) : ( 4, 5 );
}

#sub script_joomla_php_vars
#{
#return ( [ 'magic_quotes_gpc', 'On' ] );
#}

sub script_joomla_php_modules
{
local ($d, $ver, $phpver, $opts) = @_;
local ($dbtype, $dbname) = split(/_/, $opts->{'db'}, 2);
return $dbtype eq "mysql" ? ("mysql") : ("pgsql");
}

sub script_joomla_perl_modules
{
return ( "Digest::MD5" );
}

sub script_joomla_dbs
{
return ("mysql", "postgres");
}

# script_joomla_params(&domain, version, &upgrade-info)
# Returns HTML for table rows for options for installing joomla
sub script_joomla_params
{
local ($d, $ver, $upgrade) = @_;
local $rv;
local $hdir = &public_html_dir($d, 1);
if ($upgrade) {
	# Options are fixed when upgrading
	local ($dbtype, $dbname) = split(/_/, $upgrade->{'opts'}->{'db'}, 2);
	$rv .= &ui_table_row("Database for Joomla tables", $dbname);
	local $dir = $upgrade->{'opts'}->{'dir'};
	$dir =~ s/^$d->{'home'}\///;
	$rv .= &ui_table_row("Install directory", $dir);
	}
else {
	# Show editable install options
	local @dbs = &domain_databases($d, [ "mysql", "postgres" ]);
	$rv .= &ui_table_row("Database for Joomla tables",
		     &ui_database_select("db", undef, \@dbs, $d, "joomla"));
	$rv .= &ui_table_row("Install sub-directory under <tt>$hdir</tt>",
			     &ui_opt_textbox("dir", &substitute_scriptname_template("joomla", $d), 30, "At top level"));
	$rv .= &ui_hidden("lang", "en");
	if ($ver < 4) {
		$rv .= &ui_table_row("Install sample content?",
				     &ui_yesno_radio("sample", 1));
		}
	}
return $rv;
}

# script_joomla_parse(&domain, version, &in, &upgrade-info)
# Returns either a hash ref of parsed options, or an error string
sub script_joomla_parse
{
local ($d, $ver, $in, $upgrade) = @_;
if ($upgrade) {
	# Options are always the same
	return $upgrade->{'opts'};
	}
else {
	local $hdir = &public_html_dir($d, 0);
	$in{'dir_def'} || $in{'dir'} =~ /\S/ && $in{'dir'} !~ /\.\./ ||
		return "Missing or invalid installation directory";
	local $dir = $in{'dir_def'} ? $hdir : "$hdir/$in{'dir'}";
	local ($newdb) = ($in->{'db'} =~ s/^\*//);
	return { 'db' => $in->{'db'},
		 'newdb' => $newdb,
		 'dir' => $dir,
		 'path' => $in->{'dir_def'} ? "/" : "/$in->{'dir'}",
		 'lang' => $in->{'lang'},
		 'sample' => $in->{'sample'}, };
	}
}

# script_joomla_check(&domain, version, &opts, &upgrade-info)
# Returns an error message if a required option is missing or invalid
sub script_joomla_check
{
local ($d, $ver, $opts, $upgrade) = @_;
$opts->{'dir'} =~ /^\// || return "Missing or invalid install directory";
$opts->{'db'} || return "Missing database";
if (-r "$opts->{'dir'}/configuration.php") {
	return "Joomla appears to be already installed in the selected directory";
	}
local ($dbtype, $dbname) = split(/_/, $opts->{'db'}, 2);
local $clash = &find_database_table($dbtype, $dbname, "jos_.*");
$clash && return "Joomla appears to be already using the selected database (table $clash)";
return undef;
}

# script_joomla_files(&domain, version, &opts, &upgrade-info)
# Returns a list of files needed by joomla, each of which is a hash ref
# containing a name, filename and URL
sub script_joomla_files
{
local ($d, $ver, $opts, $upgrade) = @_;
local $usver = $ver;
$usver =~ s/\./_/g;
local $dashver = $ver;
$dashver =~ s/\./-/g;
local $onedashver = $ver;
$onedashver =~ s/\./-/;
local @files = ( { 'name' => "source",
	   'file' => "Joomla_$ver-Full_Package.tar.gz",
	   'url' =>
	$ver < 1.5 ?
	  "https://downloads.joomla.org/us/cms/joomla10/$dashver/joomla_$dashver-stable-full_package-tar-gz?format=gz" :
	$ver < 2.5 ?
	  "https://downloads.joomla.org/us/cms/joomla15/$dashver/joomla_$dashver-stable-full_package-tar-gz?format=gz" :
	$ver < 3.0 ?
	  "https://downloads.joomla.org/us/cms/joomla25/$dashver/joomla_$dashver-stable-full_package-tar-gz?format=gz" :
	$ver >= 3.0 && $ver < 4.0 ?
	  "https://downloads.joomla.org/us/cms/joomla3/$dashver/joomla_$ver-Stable-Full_Package.tar.gz?format=gz" :
	$ver >= 4.0 ?
	  "https://downloads.joomla.org/cms/joomla4/$dashver/Joomla_$dashver-Stable-Full_Package.tar.gz?format=gz" :
	  undef,
	} );
return @files;
}

sub script_joomla_commands
{
return ("tar", "gunzip");
}

# script_joomla_install(&domain, version, &opts, &files, &upgrade-info)
# Actually installs joomla, and returns either 1 and an informational
# message, or 0 and an error
sub script_joomla_install
{
local ($d, $version, $opts, $files, $upgrade, $domuser, $dompass) = @_;

# Disallow major version upgrade
if ($ver >= 1.5 && $upgrade && $upgrade->{'version'} < 1.5) {
	return (0, "Joomla cannot be upgraded from versions before 1.5 to version 1.5 or later");
	}
if ($ver >= 1.6 && $upgrade && $upgrade->{'version'} < 1.6) {
	return (0, "Joomla cannot be upgraded from versions before 1.6 to version 1.6 or later");
	}

local ($out, $ex);
local ($dbtype, $dbname) = split(/_/, $opts->{'db'}, 2);
if ($opts->{'newdb'} && !$upgrade) {
	local $dbopts;
	if ($dbtype eq "mysql" && $version >= 1.5) {
		$dbopts = { 'charset' => 'utf8' };
		}
        local $err = &create_script_database($d, $opts->{'db'}, $dbopts);
        return (0, "Database creation failed : $err") if ($err);
        }
local $dbuser = &mysql_user($d);
local $dbpass = &mysql_pass($d);
local $dbhost = &get_database_host("mysql", $d);
local $dberr = &check_script_db_connection($dbtype, $dbname, $dbuser, $dbpass);
return (0, "Database connection failed : $dberr") if ($dberr);

# Extract tar file to temp dir and copy to target
local $temp = &transname();
local $err = &extract_script_archive($files->{'source'}, $temp, $d,
                                     $opts->{'dir'}, undef);
$err && return (0, "Failed to extract source : $err");
local $cfile = "$opts->{'dir'}/configuration.php";
local $url = &script_path_url($d, $opts);

# Fix htaccess files in extracted dir
if (!$config{'allow_symlinks'} && defined(&fix_script_htaccess_files)) {
	&fix_script_htaccess_files($d, $temp, 0, "htaccess.txt");
	}

my $token;
$joomla_session_cookie = undef;
my $ipage = "$opts->{'path'}/installation/index.php";
my ($iout, $ierror);
if (!$upgrade) {
	# Fetch the initial install page
	&get_http_connection($d, $ipage, \$iout, \$ierror,
                       \&joomla_cookie_callback, 0, undef, undef, undef, 0, 1);
        if ($ierror) {
                return (-1, "Failed to get PHP session cookie : $ierror");
                }
        elsif (!$joomla_session_cookie) {
                return (-1, "Failed to get PHP session cookie");
                }
	if ($iout =~ /type="hidden"\s+name="([a-f0-9]+)"\s+value="1"/) {
		$token = $1;
		}
	else {
		return (-1, "Missing install token on initial page");
		}
	}
my %headers = ( 'Cookie' => $joomla_session_cookie );

if (!$upgrade && $ver >= 4) {
	# Submit the main configuration form
	local @params = ( [ "format", "json" ],
			  [ "jform[language]", "en-US" ],
			  [ "jform[site_name]", 'Joomla' ],
			  [ "jform[admin_email]", $d->{'emailto'} ],
			  [ "jform[admin_user]", $domuser ],
			  [ "jform[admin_username]", $domuser ],
			  [ "jform[admin_password]", $dompass ],
			  [ "jform[db_type]",
			    $dbtype eq "mysql" ? "mysqli" : "postgres" ],
			  [ "jform[db_host]", $dbhost ],
			  [ "jform[db_user]", $dbuser ],
			  [ "jform[db_pass]", $dbpass ],
			  [ "jform[db_name]", $dbname ],
			  [ "jform[db_prefix]", "jos_" ],
			  [ "jform[db_encryption]", "0" ],
			  [ "jform[db_sslkey]", "" ],
			  [ "jform[db_sslcert]", "" ],
			  [ "jform[db_sslverifyservercert]", 'false' ],
			  [ "jform[db_sslca]", "" ],
			  [ "jform[db_sslcipher]", "" ],
			  [ "jform[db_old]", "remove" ],
			  [ "task", "installation.dbcheck" ],
			  [ $token, 1 ] );
	local $params = join("&", map { $_->[0].(@$_ > 1 ? "=".&urlize($_->[1]) : "") } @params);
	$iout = $ierror = undef;
	&post_http_connection($d, $ipage, $params, \$iout, \$ierror, \%headers);
	if ($ierror) {
		return (-1, "Joomla automatic configuration failed : $ierror");
		}
	elsif ($iout !~ /"token":"([a-f0-9]+)"/) {
		return (-1, "Joomla main configuration page produced unexpected response");
		}
	$token = $1;

	# Call the steps to perform the actual install
	foreach my $step ("installation.dbcheck", "installation.create",
			  "installation.populate1", "installation.populate2",
			  "installation.populate3", "installation.custom1",
			  "installation.custom2", "installation.config",
			  "installation.removeFolder") {
		local $params = "format=json&${token}=1";
		$iout = $ierror = undef;
		&post_http_connection($d, $ipage."?task=$step", $params, \$iout, \$ierror, \%headers);

		if ($ierror) {
			return (-1, "Joomla installation step $step failed : $ierror");
			}
		elsif ($iout !~ /"token":"([a-f0-9]+)"/) {
			return (-1, "Joomla installation step $step produced unexpected response");
			}
		$token = $1;
		}
	}

elsif (!$upgrade && $ver >= 3) {
	# Submit the main configuration form
	local @params = ( [ "format: json" ],
			  [ "jform[language]", "en-US" ],
			  [ "jform[site_name]", $d->{'owner'} ],
			  [ "jform[admin_email]", $d->{'emailto'} ],
			  [ "jform[admin_user]", $domuser ],
			  [ "jform[admin_username]", $domuser ],
			  [ "jform[admin_password]", $dompass ],
			  [ "jform[admin_password2]", $dompass ],
			  [ "jform[site_offline]", 0 ],
			  [ "task", "site" ],
			  [ $token, 1 ] );
        local $params = join("&", map { $_->[0].(@$_ > 1 ? "=".&urlize($_->[1]) : "") } @params);
	$iout = $ierror = undef;
        &post_http_connection($d, $ipage, $params, \$iout, \$ierror, \%headers);
        if ($ierror) {
                return (-1, "Joomla automatic configuration failed : $ierror");
                }
	elsif ($iout !~ /"token":"([a-f0-9]+)"/) {
                return (-1, "Joomla main configuration page produced unexpected response");
		}
	$token = $1;

	# Submit database configuration form
	local @params = ( [ "format: json" ],
			  [ "jform[db_type]",
			    $dbtype eq "mysql" ? "mysqli" : "postgres" ],
			  [ "jform[db_host]", $dbhost ],
			  [ "jform[db_user]", $dbuser ],
			  [ "jform[db_pass]", $dbpass ],
			  [ "jform[db_name]", $dbname ],
			  [ "jform[db_prefix]", "jos_" ],
			  [ "jform[db_old]", "backup" ],
			  [ "task", "database" ],
			  [ $token, 1 ] );
        local $params = join("&", map { $_->[0].(@$_ > 1 ? "=".&urlize($_->[1]) : "") } @params);
	$iout = $ierror = undef;
        &post_http_connection($d, $ipage, $params, \$iout, \$ierror, \%headers);
        if ($ierror) {
                return (-1, "Joomla database configuration page failed : $ierror");
                }
	elsif ($iout !~ /"token":"([a-f0-9]+)"/) {
                return (-1, "Joomla database configuration page produced unexpected response");
		}
	$token = $1;

	# Submit finalization form
	local @params = ( [ "format: json" ],
			  [ "jform[sample_file]",
			    $opts->{'sample'} ? "sample_data.sql" : "" ],
			  [ "jform[summary_email]", 0 ],
			  [ "jform[summary_email_passwords]", 0 ],
			  [ "task", "summary" ],
			  [ $token, 1 ] );
        local $params = join("&", map { $_->[0].(@$_ > 1 ? "=".&urlize($_->[1]) : "") } @params);
	$iout = $ierror = undef;
        &post_http_connection($d, $ipage, $params, \$iout, \$ierror, \%headers);
        if ($ierror) {
                return (-1, "Joomla final installation page failed : $ierror");
                }
	elsif ($iout !~ /"token":"([a-f0-9]+)"/) {
                return (-1, "Joomla final installation page produced unexpected response");
		}
	$token = $1;

	# Call the steps to perform the actual install
	foreach my $step ("InstallDatabase_backup", "InstallDatabase",
			  "InstallSample", "InstallConfig") {
		local $params = "format: json&${token}=1";
		$iout = $ierror = undef;
		&post_http_connection($d, $ipage."?task=$step", $params, \$iout, \$ierror, \%headers);
		if ($ierror) {
			return (-1, "Joomla installation step $step failed : $ierror");
			}
		elsif ($iout !~ /"token":"([a-f0-9]+)"/) {
			return (-1, "Joomla installation step $step produced unexpected response");
			}
		$token = $1;
		}
	}

# Set permissions
foreach my $dir ("administrator", "cache", "components", "images", "language",
                 "mambots", "media", "modules", "templates", "plugins") {
	&make_file_php_writable($d, "$opts->{'dir'}/$dir", 0, 0);
	}

# Remove the installation directory and files.
&unlink_file_as_domain_user($d, "$opts->{'dir'}/installation");

# Return a URL for the user
local $rp = $opts->{'dir'};
$rp =~ s/^$d->{'home'}\///;
local $adminurl = $url."administrator/";
return (1, "Joomla installation complete. Go to <a target=_blank href='$adminurl'>$adminurl</a> to manage it.", "Under $rp", $url, $domuser, $dompass);
}

# script_joomla_uninstall(&domain, version, &opts)
# Un-installs a joomla installation, by deleting the directory.
# Returns 1 on success and a message, or 0 on failure and an error
sub script_joomla_uninstall
{
local ($d, $version, $opts) = @_;

# Remove the contents of the target directory
local $derr = &delete_script_install_directory($d, $opts);
return (0, $derr) if ($derr);

# Remove all jos_ tables from the database
&cleanup_script_database($d, $opts->{'db'}, "jos_");

# Take out the DB
if ($opts->{'newdb'}) {
        &delete_script_database($d, $opts->{'db'});
        }

return (1, "Joomla directory and tables deleted.");
}

sub script_joomla_db_conn_desc
{
my $db_conn_desc = 
    { 'configuration.php' =>
        {
           'dbpass' =>
           {
               'func'        => 'php_quotemeta',
               'func_params' => 1,
               'replace'     => [ '(.*public\s*)\$password\s*=\s*' =>
                                  'public $password = \'$$sdbpass\';' ],
           },
           'dbuser' =>
           {
               'replace'     => [ '(.*public\s*)\$user\s*=\s*' =>
                                  'public $user = \'$$sdbuser\';' ],
           },
        }
    };
return $db_conn_desc;
}

# script_joomla_latest(version)
# Returns a URL and regular expression or callback func to get the version
sub script_joomla_latest
{
local ($ver) = @_;
if ($ver >= 4.0) {
	return ( "https://downloads.joomla.org/cms/joomla4",
		 "Joomla\\!? (4\\.[0-9\\.]*)" );
	}
elsif ($ver >= 3.0) {
	return ( "https://downloads.joomla.org/cms/joomla3",
		 "Joomla\\!? (3\\.[0-9\\.]*)" );
	}
elsif ($ver >= 2.5) {
	return ( "https://downloads.joomla.org/cms/joomla25",
		 "Joomla\\!? (2\\.[0-9\\.]*)" );
	}
elsif ($ver >= 1.6) {
	return ( );
	}
elsif ($ver >= 1.5) {
	return ( "https://downloads.joomla.org/cms/joomla15",
		 "Joomla\\!? (1\\.[5][0-9\\.]*)" );
	}
else {
	return ( );
	}
}

sub script_joomla_site
{
return 'http://joomlacode.org/';
}

sub script_joomla_passmode
{
return (1, 12, '^(?=.*[\p{L}])[\p{L}\d]{12,}$');
}

# script_joomla_realversion(&domain, &opts)
# Returns the real version number of some script install, or undef if unknown
sub script_joomla_realversion
{
local ($d, $opts, $sinfo) = @_;
foreach my $f ("$opts->{'dir'}/administrator/manifests/files/joomla.xml",
	       "$opts->{'dir'}/libraries/joomla/version.php",
	       "$opts->{'dir'}/includes/version.php",
	       "$opts->{'dir'}/libraries/cms/version/version.php",
	       "$opts->{'dir'}/libraries/src/Version.php") {
	local $lref = &read_file_lines($f, 1);
	local ($rel, $dev);
	foreach my $l (@$lref) {
		if ($l =~ /<version>(.*?)<\/version>/) {
			$rel = $1;
			}
		elsif ($l =~ /(var|public|const)\s+\$?RELEASE\s+=\s+'([0-9\.]+)'/) {
			$rel = $2;
			}
		elsif ($l =~ /(var|public|const)\s+\$?DEV_LEVEL\s+=\s+'([0-9\.]+)'/) {
			$dev = $2;
			}
		}
	if ($rel) {
		$rel .= ".".$dev;
		return $rel;
		}
	}
return undef;
}

sub joomla_cookie_callback
{
if ($header{'set-cookie'} =~ /([^ ;]+)/ ||
    $WebminCore::header{'set-cookie'} =~ /([^ ;]+)/) {
        $joomla_session_cookie = $1;
        }
}

1;

