Files
Froxlor/lib/classes/aps/class.ApsInstaller.php
2013-11-20 15:02:49 +01:00

692 lines
24 KiB
PHP

<?php
/**
* Implementation of the Application Packaging Standard from SwSoft/Parallels
* http://apsstandard.com
*
* This file is part of the Froxlor project.
* Copyright (c) 2003-2009 the SysCP Team (see authors).
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Florian Lippert <flo@syscp.org> (2003-2009)
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package APS
*
* @todo logging
* run with user uid/gid
* folder truncation/tar all files
*/
class ApsInstaller extends ApsParser {
private $DomainPath = '';
private $Domain = '';
private $RealPath = '';
private $RootDir = '';
private $Hosts = '';
private $aps_version = '1.0';
/**
* constructor of class. setup some basic variables
*
* @param settings array with the global settings from froxlor
*/
public function __construct($settings) {
$this->RootDir = dirname(dirname(dirname(dirname(__FILE__)))) . '/';
$this->Hosts = $settings['system']['mysql_access_host'];
}
/**
* main function of class which handles all
*/
public function InstallHandler() {
chdir($this->RootDir);
$result_stmt = Database::query("
SELECT * FROM `" . TABLE_APS_TASKS . "` AS `t`
INNER JOIN `" . TABLE_APS_INSTANCES . "` AS `i` ON `t`.`InstanceID` = `i`.`ID`
INNER JOIN `" . TABLE_APS_PACKAGES . "` AS `p` ON `i`.`PackageID` = `p`.`ID`
INNER JOIN `" . TABLE_PANEL_CUSTOMERS . "` AS `c` ON `i`.`CustomerID` = `c`.`customerid`
WHERE `TASK` NOT IN (" . TASK_SYSTEM_UPDATE . ", " . TASK_SYSTEM_DOWNLOAD . ")
");
while ($Row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
//check for existing aps xml file
if (!file_exists($this->RootDir . 'packages/' . $Row['Path'] . '/APP-META.xml')) {
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_APS_INSTANCES . "` SET `Status` = " . INSTANCE_ERROR . " WHERE `ID` = :id
");
Database::pexecute($upd_stmt, array('id' => $Row['InstanceID']));
continue;
}
//get contents and parse them
$XmlContent = file_get_contents($this->RootDir . 'packages/' . $Row['Path'] . '/APP-META.xml');
$Xml = new SimpleXMLElement($XmlContent);
$this->aps_version = isset($Xml->attributes()->version) ? (string)$Xml->attributes()->version : '1.0';
//check for unparseable xml data
if ($Xml == false) {
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_APS_INSTANCES . "` SET `Status` = " . INSTANCE_ERROR . " WHERE `ID` = :id
");
Database::pexecute($upd_stmt, array('id' => $Row['InstanceID']));
continue;
}
$Task = $Row['Task'];
$this->DomainPath = '';
$this->Domain = '';
$this->RealPath = '';
//lock instance so installation cannot be canceled from the panel
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_APS_INSTANCES . "` SET `Status` = " . INSTANCE_TASK_ACTIVE . " WHERE `ID` = :id
");
Database::pexecute($upd_stmt, array('id' => $Row['InstanceID']));
//setup environment with data for domain/installation location
self::PrepareBasics($Row);
//create database if necessary and setup environment variables
self::PrepareDatabase($Xml, $Row, $Task);
//unpack installation scripts and package files if necessary
if (self::PrepareFiles($Xml, $Row, $Task)) {
//setup environment variables fetched from installation wizard
self::PrepareWizardData($Xml, $Row, $Task);
//run installation scripts from packages
self::RunInstaller($Xml, $Row, $Task);
}
//remove installation scripts
self::CleanupData($Xml, $Row, $Task);
unset($Xml);
}
}
/**
* run the installation script and log errors if there are some
*
* @param xml instance of a valid xml object with a parsed APP-META.xml file
* @param row current entry from the database for app to handle
* @param task numeric code to specify what to do
* @return success true/error false
*/
private function RunInstaller($Xml, $Row, $Task) {
//installation
if ($Task == TASK_INSTALL) {
//setup right path and run installation script
if (!is_dir($this->RealPath . $this->DomainPath . '/install_scripts/')) {
echo 'Directory: '. $this->RealPath . $this->DomainPath . '/install_scripts/ does not exist';
return;
}
chdir($this->RealPath . $this->DomainPath . '/install_scripts/');
// make configure-script executable
if ($this->aps_version != '1.0') {
$scriptname = (string)$Xml->service->provision->{'configuration-script'}['name'];
} else {
$scriptname = 'configure';
}
chmod($this->RealPath . $this->DomainPath . '/install_scripts/'.$scriptname, 0755);
$Return = array();
// first 'true' to indicate that we want the return-status from exec.
// after exec() is called, the value will be the return-status of the
// program executed
$ReturnStatus = true;
$Return = safe_exec('php ' . escapeshellarg($this->RealPath . $this->DomainPath . '/install_scripts/'.$scriptname) . ' install', $ReturnStatus);
if ($ReturnStatus != 0) {
//write output of script on error into database for admin
$Buffer = '';
$Count = 0;
foreach ($Return as $Line) {
$Count+= 1;
$Buffer.= $Line;
if ($Count != count($Return)) {
$Buffer.= "\n";
}
}
//FIXME error logging
echo ("error : installer\n" . $Buffer . "\n");
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_APS_INSTANCES . "` SET `Status` = " . INSTANCE_ERROR . " WHERE `ID` = :id
");
Database::pexecute($upd_stmt, array('id' => $Row['InstanceID']));
return false;
} else {
//installation succeeded
//chown all files if installtion script has created some new files. otherwise customers cannot edit the files via ftp
safe_exec('chown -R ' . (int)$Row['guid'] . ':' . (int)$Row['guid'] . ' ' . escapeshellarg($this->RealPath . $this->DomainPath . '/'));
//update database
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_APS_INSTANCES . "` SET `Status` = " . INSTANCE_SUCCESS . " WHERE `ID` = :id
");
Database::pexecute($upd_stmt, array('id' => $Row['InstanceID']));
return true;
}
}
}
/**
* remove installation scripts from filesystem and remove tasks and update the database
*
* @param xml instance of a valid xml object with a parsed APP-META.xml file
* @param row current entry from the database for app to handle
* @param task numeric code to specify what to do
*/
private function CleanupData($Xml, $Row, $Task) {
chdir($this->RootDir);
if ($Task == TASK_INSTALL) {
//cleanup installation
self::UnlinkRecursive($this->RealPath . $this->DomainPath . '/install_scripts/');
//remove task
$del_stmt = Database::prepare("
DELETE FROM `" . TABLE_APS_TASKS . "` WHERE `Task` = " . TASK_INSTALL . " AND `InstanceID` = :id
");
Database::pexecute($del_stmt, array('id' => $Row['InstanceID']));
} elseif($Task == TASK_REMOVE) {
// check for database
if ($this->aps_version == '1.0') {
// the good ole way
$XmlDb = $Xml->requirements->children('http://apstandard.com/ns/1/db');
} else {
// since 1.1
$Xml->registerXPathNamespace('db', 'http://apstandard.com/ns/1/db');
$XmlDb = new DynamicProperties;
$XmlDb->db = new DynamicProperties;
$XmlDb->db->id = getXPathValue($Xml, '//db:id');
}
if ($XmlDb->db->id) {
//drop database permissions
Database::needRoot(true);
// FIXME ### here
$Database = 'web' . $Row['CustomerID'] . 'aps' . $Row['InstanceID'];
foreach (array_map('trim', explode(',', $this->Hosts)) as $DatabaseHost) {
if (Database::getAttribute(PDO::ATTR_SERVER_VERSION) < '5.0.2') {
// Revoke privileges (only required for MySQL 4.1.2 - 5.0.1)
$stmt = Database::prepare("REVOKE ALL PRIVILEGES, GRANT OPTION FROM :databasename");
Database::pexecute($stmt, array("databasename" => $DatabaseHost));
}
$host_res_stmt = Database::prepare("
SELECT `Host` FROM `mysql`.`user`
WHERE `User`= :databasename
");
Database::pexecute($host_res_stmt, array("databasename" => $DatabaseHost));
while ($host = $host_res_stmt->fetch(PDO::FETCH_ASSOC)) {
// as of MySQL 5.0.2 this also revokes privileges. (requires MySQL 4.1.2+)
$stmt = Database::prepare("DROP USER :databasename@:host");
Database::pexecute($stmt, array("databasename" => $DatabaseHost, "host" => $host['Host']));
}
}
// drop database
$stmt = Database::prepare("DROP DATABASE IF EXISTS `" . $DatabaseHost . "`");
Database::pexecute($stmt, array(), false);
$stmt = Database::prepare("FLUSH PRIVILEGES");
Database::pexecute($stmt);
Database::needRoot(false);
// remove database from customer-mysql overview, #272
$stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DATABASES . "`
WHERE `customerid` = :customerid
AND `databasename` = :databasename
AND `apsdb` = '1'
");
Database::pexecute($stmt, array("customerid" => $Row['CustomerID'], "databasename" => $Database));
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "`
SET `mysqls_used` = `mysqls_used` - 1
WHERE `customerid` = :customerid"
);
Database::pexecute($stmt, array("customerid" => $Row['CustomerID']));
}
// remove task & delete package instance + settings
$del_stmt = Database::prepare("
DELETE FROM `" . TABLE_APS_TASKS . "` WHERE `Task` = '" . TASK_REMOVE . "'
AND `InstanceID` = :instanceid
");
Database::pexecute($del_stmt, array('instanceid' => $Row['InstanceID']));
$del_stmt = Database::prepare("
DELETE FROM `" . TABLE_APS_INSTANCES . "` WHERE `ID` = :instanceid
");
Database::pexecute($del_stmt, array('instanceid' => $Row['InstanceID']));
$del_stmt = Database::prepare("
DELETE FROM `" . TABLE_APS_SETTINGS . "` WHERE `InstanceID` = :instanceid
");
Database::pexecute($del_stmt, array('instanceid' => $Row['InstanceID']));
if ($this->RealPath != ''
&& checkDisallowedPaths($this->RealPath)
) {
//remove data, #273
if ($this->DomainPath != ''
&& $this->DomainPath != '/'
) {
self::UnlinkRecursive($this->RealPath . $this->DomainPath . '/');
} else {
// save awstats/webalizer folder if it's the docroot
self::UnlinkRecursive($this->RealPath . $this->DomainPath . '/', true);
// place standard-index file
$loginname = getLoginNameByUid($Row['CustomerID']);
if ($loginname !== false) {
storeDefaultIndex($loginname, $this->RealPath . $this->DomainPath . '/');
}
}
}
}
}
/**
* setup all environment variables from the wizard, they're all needed by the installation script
*
* @param xml instance of a valid xml object with a parsed APP-META.xml file
* @param row current entry from the database for app to handle
* @param task numeric code to specify what to do
*/
private function PrepareWizardData($Xml, $Row, $Task) {
//data collected by wizard
//FIXME install_only parameter/reconfigure
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_APS_SETTINGS . "` WHERE `InstanceID` = :instanceid
");
Database::pexecute($result_stmt, array('instanceid' => $Row['InstanceID']));
while ($Row2 = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
//skip APS internal data
if ($Row2['Name'] == 'main_location'
|| $Row2['Name'] == 'main_domain'
|| $Row2['Name'] == 'main_database_password'
|| $Row2['Name'] == 'license'
) {
continue;
}
putenv('SETTINGS_' . $Row2['Name'] . '=' . $Row2['Value']);
}
}
/**
* extract all needed files from the aps packages
*
* @param xml instance of a valid xml object with a parsed APP-META.xml file
* @param row current entry from the database for app to handle
* @param task numeric code to specify what to do
* @return success true/error false
*/
private function PrepareFiles($Xml, $Row, $Task) {
if ($this->aps_version != '1.0') {
$mapping = $Xml->service->provision->{'url-mapping'}->mapping;
$mapping_path = $Xml->service->provision->{'url-mapping'}->mapping['path'];
$mapping_url = $Xml->service->provision->{'url-mapping'}->mapping['url'];
} else {
$mapping = $Xml->mapping;
$mapping_path = $Xml->mapping['path'];
$mapping_url = $Xml->mapping['url'];
}
if ($this->RealPath == '' || !checkDisallowedPaths($this->RealPath)) {
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_APS_INSTANCES . "` SET `Status` = " . INSTANCE_ERROR . " WHERE `ID` = :id
");
Database::pexecute($upd_stmt, array('id' => $Row['InstanceID']));
return false;
}
if ($Task == TASK_INSTALL) {
//FIXME truncate customer directory
//remove files from: $this->RealPath . $this->DomainPath . '/*'
if (!file_exists($this->RealPath . $this->DomainPath . '/')) {
mkdir($this->RealPath . $this->DomainPath . '/', 0777, true);
}
//extract all files and chown them to the customer guid
if (self::ExtractZip($this->RootDir . 'packages/' . $Row['Path'] . '/' . $Row['Path'], $mapping_path, $this->RealPath . $this->DomainPath . '/') == false
|| self::ExtractZip($this->RootDir . 'packages/' . $Row['Path'] . '/' . $Row['Path'], 'scripts', $this->RealPath . $this->DomainPath . '/install_scripts/') == false
) {
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_APS_INSTANCES . "` SET `Status` = " . INSTANCE_ERROR . " WHERE `ID` = :id
");
Database::pexecute($upd_stmt, array('id' => $Row['InstanceID']));
//FIXME clean up already installed data
//remove files from: $this->RealPath . $this->DomainPath . '/*'
return false;
}
safe_exec('chown -R ' . (int)$Row['guid'] . ':' . (int)$Row['guid'] . ' ' . escapeshellarg($this->RealPath . $this->DomainPath . '/'));
} else {
if (self::ExtractZip($this->RootDir . 'packages/' . $Row['Path'] . '/' . $Row['Path'], 'scripts', $this->RealPath . $this->DomainPath . '/install_scripts/') == false) {
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_APS_INSTANCES . "` SET `Status` = " . INSTANCE_ERROR . " WHERE `ID` = :id
");
Database::pexecute($upd_stmt, array('id' => $Row['InstanceID']));
//clean up already installed data
self::UnlinkRecursive($this->RealPath . $this->DomainPath . '/install_scripts/');
return false;
}
//set right file owner
safe_exec('chown -R ' . (int)$Row['guid'] . ':' . (int)$Row['guid'] . ' ' . escapeshellarg($this->RealPath . $this->DomainPath . '/'));
}
//recursive mappings
self::PrepareMappings($mapping, $mapping_url, $this->RealPath . $this->DomainPath . '/');
return true;
}
/**
* setup path environment variables for the installation script
*
* @param parentmapping instance of parsed xml file, current mapping position
* @param url relative path for application specifying the current path within the mapping tree
* @param path absolute path for application specifying the current path within the mapping tree
*/
private function PrepareMappings($ParentMapping, $Url, $Path) {
//check for special PHP permissions
//must be done with xpath otherwise check not possible (XML parser problem with attributes)
if ($ParentMapping && $ParentMapping !== null) {
$ParentMapping->registerXPathNamespace('p', 'http://apstandard.com/ns/1/php');
$Result = $ParentMapping->xpath('p:permissions');
if (is_array($Result)
&& isset($Result[0])
&& is_array($Result[0])
) {
if (isset($Result[0]['writable'])
&& $Result[0]['writable'] == 'true'
) {
// fixing file permissions to writeable
if (is_dir($Path)) {
chmod($Path, 0775);
} else {
chmod($Path, 0664);
}
}
if (isset($Result[0]['readable'])
&& $Result[0]['readable'] == 'false'
) {
//fixing file permissions to non readable
if (is_dir($Path)) {
chmod($Path, 0333);
} else {
chmod($Path, 0222);
}
}
}
}
//set environment variables
$EnvVariable = str_replace("/", "_", $Url);
putenv('WEB_' . $EnvVariable . '_DIR=' . $Path);
//resolve deeper mappings
if ($ParentMapping && $ParentMapping !== null) {
foreach ($ParentMapping->mapping as $Mapping) {
//recursive check of other mappings
if ($Url == '/') {
self::PrepareMappings($Mapping, $Url . $Mapping['url'], $Path . $Mapping['url']);
} else {
self::PrepareMappings($Mapping, $Url . '/' . $Mapping['url'], $Path . '/' . $Mapping['url']);
}
}
}
}
/**
* setup domain environment variables for the installation script
*
* @param xml instance of a valid xml object with a parsed APP-META.xml file
*/
private function PrepareBasics($Row) {
//domain
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_APS_SETTINGS . "` WHERE `InstanceID` = :instanceid AND `Name` = 'main_domain'
");
$Row3 = Database::pexecute_first($result_stmt, array('instanceid' => $Row['InstanceID']));
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :domainid
");
$Row3 = Database::pexecute_first($result_stmt, array('instanceid' => $Row3['Value']));
$this->Domain = $Row3['domain'];
$this->RealPath = $Row3['documentroot'];
//location
$result3_stmt = Database::prepare("
SELECT * FROM `" . TABLE_APS_SETTINGS . "` WHERE `InstanceID` = :instanceid AND `Name` = 'main_location'
");
$Row3 = Database::pexecute_first($result_stmt, array('instanceid' => $Row['InstanceID']));
$this->DomainPath = $Row3['Value'];
//if application is directly installed on domain remove / at the end
if ($this->DomainPath == '') {
$this->RealPath = substr($this->RealPath, 0, strlen($this->RealPath) - 1);
}
//url environment variables
putenv('BASE_URL_HOST=' . $this->Domain);
putenv('BASE_URL_PATH=' . $this->DomainPath . '/');
putenv('BASE_URL_SCHEME=http');
}
/**
* create a database if necessary and setup environment variables
*
* @param xml instance of a valid xml object with a parsed APP-META.xml file
* @param row current entry from the database for app to handle
* @param task numeric code to specify what to do
*/
private function PrepareDatabase($Xml, $Row, $Task) {
$XmlDb = $Xml->requirements->children('http://apstandard.com/ns/1/db');
if ($this->aps_version == '1.0') {
// the good ole way
$XmlDb = $Xml->requirements->children('http://apstandard.com/ns/1/db');
} else {
// since 1.1
$Xml->registerXPathNamespace('db', 'http://apstandard.com/ns/1/db');
$XmlDb = new DynamicProperties;
$XmlDb->db = new DynamicProperties;
$XmlDb->db->id = getXPathValue($Xml, '//db:id');
}
if ($XmlDb->db->id) {
//database management
$NewDatabase = 'web' . $Row['CustomerID'] . 'aps' . $Row['InstanceID'];
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_APS_SETTINGS . "` WHERE `InstanceID` = :instanceid AND `Name` = 'main_database_password'
");
$Row3 = Database::pexecute_first($result_stmt, array('instanceid' => $Row['InstanceID']));
$DbPassword = $Row3['Value'];
if ($Task == TASK_INSTALL) {
Database::needRoot(true);
$drp_stmt = Database::prepare("DROP DATABASE IF EXISTS :newdb");
Database::pexecute($drp_stmt, array('newdb' => $NewDatabase));
$crt_stmt = Database::prepare("CREATE DATABASE IF NOT EXISTS :newdb");
Database::pexecute($crt_stmt, array('newdb' => $NewDatabase));
foreach (array_map('trim', explode(',', $this->Hosts)) as $DatabaseHost) {
$stmt = Database::prepare("GRANT ALL PRIVILEGES ON `" . $NewDatabase . "`.*
TO :username@:host
IDENTIFIED BY 'password'"
);
Database::pexecute($stmt, array("username" => $NewDatabase, "host" => $DatabaseHost));
$stmt = Database::prepare("SET PASSWORD FOR :username@:host = PASSWORD(:password)");
Database::pexecute($stmt, array("username" => $NewDatabase, "host" => $DatabaseHost, "password" => $DbPassword));
}
Database::query('FLUSH PRIVILEGES');
Database::needRoot(false);
// add database to customers databases, #272
$databasedescription = $Xml->name.' '.$Xml->version.' (Release ' . $Xml->release . ')';
$ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_DATABASES . "`
(`customerid`, `databasename`, `description`, `dbserver`, `apsdb`)
VALUES
(:customerid, :databasename, :desc, '0', '1')
");
$ins_data = array(
'customerid' => $Row['CustomerID'],
'databasename' => $NewDatabase,
'desc' => $databasedescription
);
Database::pexecute($ins_stmt, $ins_data);
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET
`mysqls_used`=`mysqls_used`+1 WHERE `customerid` = :customerid
");
Database::pexecute($upd_stmt, array('customerid' => $Row['CustomerID']));
}
//get first mysql access host
$AccessHosts = array_map('trim', explode(',', $this->Hosts));
//environment variables
putenv('DB_' . $XmlDb->db->id . '_TYPE=mysql');
putenv('DB_' . $XmlDb->db->id . '_NAME=' . $NewDatabase);
putenv('DB_' . $XmlDb->db->id . '_LOGIN=' . $NewDatabase);
putenv('DB_' . $XmlDb->db->id . '_PASSWORD=' . $DbPassword);
putenv('DB_' . $XmlDb->db->id . '_HOST=' . $AccessHosts[0]);
putenv('DB_' . $XmlDb->db->id . '_PORT=3306');
putenv('DB_' . $XmlDb->db->id . '_VERSION=' . Database::getAttribute(PDO::ATTR_SERVER_VERSION));
}
}
/**
* extract complete directories from a zipfile
*
* @param filename path to zipfile to extract
* @param directory which directory in zipfile to extract
* @param destination destination directory for files to extract
* @return success true/error false
*/
private function ExtractZip($Filename, $Directory, $Destination) {
if (!file_exists($Filename)) {
return false;
}
// fix slash notation for correct paths
if (substr($Directory, -1, 1) == '/') {
$Directory = substr($Directory, 0, strlen($Directory) - 1);
}
if (substr($Destination, -1, 1) != '/') {
$Destination.= '/';
}
//open zipfile to read its contents
$ZipHandle = zip_open(realpath($Filename));
if (is_resource($ZipHandle)) {
while ($ZipEntry = zip_read($ZipHandle)) {
if (substr(zip_entry_name($ZipEntry), 0, strlen($Directory)) == $Directory) {
//fix relative path from zipfile
$NewPath = zip_entry_name($ZipEntry);
$NewPath = substr($NewPath, strlen($Directory));
//directory
if (substr($NewPath, -1, 1) == '/') {
if(!file_exists($Destination . $NewPath))mkdir($Destination . $NewPath, 0777, true);
} else {
//files
if (zip_entry_open($ZipHandle, $ZipEntry)) {
// handle new directory
$dir = dirname($Destination.$NewPath);
if (!file_exists($dir)) {
mkdir ($dir, 0777, true);
}
$File = fopen($Destination . $NewPath, "wb");
if ($File) {
while ($Line = zip_entry_read($ZipEntry)) {
fwrite($File, $Line);
}
fclose($File);
} else {
return false;
}
}
}
}
}
zip_close($ZipHandle);
return true;
} else {
$ReturnLines = array();
// first 'true' to indicate that we want the return-status from exec.
// after exec() is called, the value will be the return-status of the
// program executed
$ReturnVal = true;
//on 64 bit systems the zip functions can fail -> use exec to extract the files
$ReturnLines = safe_exec('unzip -o -qq ' . escapeshellarg(realpath($Filename)) . ' ' . escapeshellarg($Directory . '/*') . ' -d ' . escapeshellarg(sys_get_temp_dir()), $ReturnVal);
if ($ReturnVal == 0) {
//fix absolute structure of extracted data
if (!file_exists($Destination)) {
mkdir($Destination, 0777, true);
}
safe_exec('cp -Rf ' . sys_get_temp_dir() . '/' . $Directory . '/*' . ' ' . escapeshellarg($Destination));
self::UnlinkRecursive(sys_get_temp_dir() . '/' . $Directory . '/');
return true;
} else {
return false;
}
}
return false;
}
}