.
*
* ------------------------------------------------------------------------
*
* This file is used to manage the files to deploy.
*
* ------------------------------------------------------------------------
*
* @package FusionInventory
* @author Alexandre Delaunay
* @author David Durieux
* @copyright Copyright (c) 2010-2016 FusionInventory team
* @license AGPL License 3.0 or (at your option) any later version
* http://www.gnu.org/licenses/agpl-3.0-standalone.html
* @link http://www.fusioninventory.org/
* @link https://github.com/fusioninventory/fusioninventory-for-glpi
*
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access directly to this file");
}
/**
* Manage the files to deploy.
*/
class PluginFusioninventoryDeployFile extends PluginFusioninventoryDeployPackageItem {
public $shortname = 'files';
public $json_name = 'associatedFiles';
/**
* The right name for this class
*
* @var string
*/
static $rightname = 'plugin_fusioninventory_package';
const REGISTRY_NO_DB_ENTRY = 0x1;
const REGISTRY_NO_MANIFEST = 0x2;
/**
* Get the 2 types to add files
*
* @return array
*/
function getTypes() {
return [
'Computer' => __("Upload from computer", 'fusioninventory'),
'Server' => __("Upload from server", 'fusioninventory')
];
}
/**
* Display list of files
*
* @global array $CFG_GLPI
* @param object $package PluginFusioninventoryDeployPackage instance
* @param array $data array converted of 'json' field in DB where stored actions
* @param string $rand unique element id used to identify/update an element
*/
function displayList(PluginFusioninventoryDeployPackage $package, $data, $rand) {
global $CFG_GLPI;
$package_id = $package->getID();
$canedit = $package->canUpdateContent();
// compute short shas to find the corresponding entries in database
$short_shas = [];
foreach ($data['jobs']['associatedFiles'] as $sha512) {
$short_shas[] = substr($sha512, 0, 6);
}
// find corresponding file entries
$files = $this->find(['shortsha512' => $short_shas]);
// do a quick mapping between database id and short shas
$files_mapping = [];
foreach ($files as $file) {
$files_mapping[$file['shortsha512']] = $file['id'];
}
echo "
";
$i = 0;
foreach ($data['jobs']['associatedFiles'] as $sha512) {
$short_sha = substr($sha512, 0, 6);
$fileregistry_error = 0;
// check if the files is registered in database
if (!array_key_exists($short_sha, $files_mapping)) {
$fileregistry_error |= self::REGISTRY_NO_DB_ENTRY;
}
if (!$this->checkPresenceManifest($sha512)) {
$fileregistry_error |= self::REGISTRY_NO_MANIFEST;
}
// get database entries
if (!$fileregistry_error) {
$file_id = $files_mapping[$short_sha];
$file_name = $files[$file_id]['name'];
$file_size = $files[$file_id]['filesize'];
//mimetype icon
if (isset($files[$file_id]['mimetype'])) {
$file_mimetype =
str_replace('/', '__', $files[$file_id]['mimetype']);
} else {
$file_mimetype = null;
}
} else {
// get file's name from what has been saved in json
$file_name = $data['associatedFiles'][$sha512]['name'];
$file_size = null;
$file_mimetype = null;
}
$file_uncompress = $data['associatedFiles'][$sha512]['uncompress'];
$file_p2p = $data['associatedFiles'][$sha512]['p2p'];
$file_p2p_retention_duration =
$data['associatedFiles'][$sha512]['p2p-retention-duration'];
// start new line
$pics_path = Plugin::getWebDir('fusioninventory')."/pics/";
echo Search::showNewLine(Search::HTML_OUTPUT, ($i%2));
if ($canedit) {
echo "";
Html::showCheckbox(['name' => 'file_entries['.$i.']', 'value' => 0]);
echo " ";
}
echo "";
if (!empty($file_mimetype)
&& file_exists(PLUGIN_FUSIONINVENTORY_DIR."/pics/extensions/$file_mimetype.png")) {
echo " ";
} else {
echo " ";
}
//filename
echo " ";
if ($canedit) {
echo "";
}
echo $file_name;
if ($canedit) {
echo " ";
}
//p2p icon
if (isset($file_p2p)
&& $file_p2p != 0) {
echo "";
echo " ";
echo "".$file_p2p_retention_duration." ";
echo " ";
}
//uncompress icon
if (isset($file_uncompress)
&& $file_uncompress != 0) {
echo " ";
}
//sha fingerprint
$sha_status = "good";
if ($fileregistry_error != 0) {
$sha_status = "bad";
}
echo "";
echo "
".$sha512;
if ($fileregistry_error & self::REGISTRY_NO_DB_ENTRY) {
echo "
".
__("This file is not correctly registered in database.")." ".
__("You can fix it by uploading or selecting the good one.");
echo "
";
}
if ($fileregistry_error & self::REGISTRY_NO_MANIFEST) {
echo "
".
__("This file doesn't have any manifest file associated.")." ".
__("You must upload the file.");
echo "
";
}
echo "
";
echo "
";
//filesize
if (!$fileregistry_error) {
echo "";
echo __('Size').": ".$this->processFilesize($file_size);
echo "
";
}
echo " ";
if ($canedit) {
echo "
";
}
$i++;
}
if ($canedit) {
echo "";
echo Html::getCheckAllAsCheckbox("filesList$rand", mt_rand());
echo " ";
}
echo "
";
if ($canedit) {
echo " ";
echo " ";
}
}
/**
* Display different fields relative the file selected
*
* @global array $CFG_GLPI
* @param array $config
* @param array $request_data
* @param string $rand unique element id used to identify/update an element
* @param string $mode mode in use (create, edit...)
* @return boolean
*/
function displayAjaxValues($config, $request_data, $rand, $mode) {
$fi_path = Plugin::getWebDir('fusioninventory');
$pfDeployPackage = new PluginFusioninventoryDeployPackage();
if (isset($request_data['packages_id'])) {
$pfDeployPackage->getFromDB($request_data['packages_id']);
} else {
$pfDeployPackage->getEmpty();
}
$p2p = 0;
$p2p_retention_duration = 0;
$uncompress = 0;
if ($mode === self::CREATE) {
$source = $request_data['value'];
/**
* No need to continue if there is no selected source
*/
if ($source === '0') {
return;
}
} else {
$p2p = $config['data']['p2p'];
$p2p_retention_duration = $config['data']['p2p-retention-duration'];
$uncompress = $config['data']['uncompress'];
}
echo "";
/*
* Display file upload input only in 'create' mode
*/
echo "";
echo "".__("File", 'fusioninventory')." ";
echo "";
if ($mode === self::CREATE) {
switch ($source) {
case "Computer":
echo " ";
echo " ".$this->getMaxUploadSize()." ";
break;
case "Server":
echo " ";
echo " ";
Ajax::createModalWindow("fileModal$rand",
$fi_path."/ajax/deployfilemodal.php",
['title' => __('Select the file on server', 'fusioninventory'),
'extraparams' => [
'rand' => $rand
]]);
break;
}
} else {
/*
* Display only name in 'edit' mode
*/
echo $config['data']['name'];
}
echo " ";
echo " ";
echo "";
echo "".__("Uncompress", 'fusioninventory')." ";
echo "";
Html::showCheckbox(['name' => 'uncompress', 'checked' => $uncompress]);
echo " ";
echo " ";
echo "";
echo "".__("P2P", 'fusioninventory').
" ";
echo "";
Html::showCheckbox(['name' => 'p2p', 'checked' => $p2p]);
echo " ";
echo " ";
echo "";
echo "".__("retention", 'fusioninventory').
" - ".__("Minute(s)", 'fusioninventory')." ";
echo "";
echo " ";
echo " ";
echo " ";
$this->addOrSaveButton($pfDeployPackage, $mode);
echo "
";
}
/**
* Show files / directory on server.
* This is used when get a file on the server
*
* @global array $CFG_GLPI
* @param string $rand unique element id used to identify/update an element
*/
static function showServerFileTree($rand) {
echo "";
echo "
";
echo "";
}
/**
* Get files / directories on server
*
* @param string $node
*/
static function getServerFileTree($node) {
$nodes = [];
$pfConfig = new PluginFusioninventoryConfig();
$dir = $pfConfig->getValue('server_upload_path');
$security_problem = false;
if ($node != "-1") {
if (strstr($node, "..")) {
$security_problem = true;
}
$matches = [];
preg_match("/^(".str_replace("/", "\/", $dir).")(.*)$/", $node, $matches);
if (count($matches) != 3) {
$security_problem = true;
}
}
if (!$security_problem) {
// leaf node
if ($node != -1) {
$dir = $node;
}
if (($handle = opendir($dir))) {
$folders = $files = [];
//list files in dir selected
//we store folders and files separately to sort them alphabeticaly separatly
while (false !== ($entry = readdir($handle))) {
if ($entry != "." && $entry != "..") {
$filepath = $dir."/".$entry;
if (is_dir($filepath)) {
$folders[$filepath] = $entry;
} else {
$files[$filepath] = $entry;
}
}
}
//sort folders and files (and maintain index association)
asort($folders);
asort($files);
//add folders in json
foreach ($folders as $filepath => $entry) {
$path['text'] = $entry;
$path['id'] = $filepath;
$path['draggable'] = false;
$path['leaf'] = false;
$path['cls'] = 'folder';
$nodes[] = $path;
}
//add files in json
foreach ($files as $filepath => $entry) {
$path['text'] = $entry;
$path['id'] = $filepath;
$path['draggable'] = false;
$path['leaf'] = true;
$path['cls'] = 'file';
$nodes[] = $path;
}
closedir($handle);
}
}
print json_encode($nodes);
}
/**
* Add a new item in files of the package
*
* @param array $params list of fields with value of the file
*/
function add_item($params) {
switch ($params['filestype']) {
case 'Server':
return $this->uploadFileFromServer($params);
break;
default:
return $this->uploadFileFromComputer($params);
}
return false;
}
/**
* Remove an item
*
* @param array $params
* @return boolean
*/
function remove_item($params) {
if (!isset($params['file_entries'])) {
return false;
}
$shasToRemove = [];
//get current order json
$data = json_decode($this->getJson($params['packages_id']), true);
$files = $data['jobs']['associatedFiles'];
//remove selected checks
foreach ($params['file_entries'] as $index => $checked) {
if ($checked >= "1" || $checked == "on") {
//get sha512
$sha512 = $data['jobs']['associatedFiles'][$index];
//remove file
unset($files[$index]);
unset($data['associatedFiles'][$sha512]);
$shasToRemove[] = $sha512;
}
}
$data['jobs']['associatedFiles'] = array_values($files);
//update order
$this->updateOrderJson($params['packages_id'], $data);
//remove files in repo
foreach ($shasToRemove as $sha512) {
$this->removeFileInRepo($sha512);
}
return true;
}
/**
* Save the item in files
*
* @param array $params list of fields with value of the file
*/
function save_item($params) {
//get current order json
$data = json_decode($this->getJson($params['id']), true);
//get sha512
$sha512 = $data['jobs'][$this->json_name][$params['index']];
//get file in json
$file = $data[$this->json_name][$sha512];
//remove value in json
unset($data[$this->json_name][$sha512]);
//update values
$file['p2p'] = isset($params['p2p']) ? $params['p2p'] : 0;
$file['p2p-retention-duration'] = $params['p2p-retention-duration'];
$file['uncompress'] = isset($params['uncompress']) ? $params['uncompress'] : 0;
//add modified entry
$data[$this->json_name][$sha512] = $file;
//update order
$this->updateOrderJson($params['id'], $data);
}
/**
* Upload file from user computer
*
* @param array $params
* @return boolean
*/
function uploadFileFromComputer($params) {
if (isset($params["id"])) {
//file uploaded?
if (isset($_FILES['file']['tmp_name'])
&& !empty($_FILES['file']['tmp_name'])) {
$file_tmp_name = $_FILES['file']['tmp_name'];
}
if (isset($_FILES['file']['name'])
&& !empty($_FILES['file']['name'])) {
$filename = $_FILES['file']['name'];
}
//file upload errors
if (isset($_FILES['file']['error'])) {
$error = true;
switch ($_FILES['file']['error']) {
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$msg = __("Transfer error: the file size is too big", 'fusioninventory');
break;
case UPLOAD_ERR_PARTIAL:
$msg = __("The uploaded file was only partially uploaded", 'fusioninventory');
break;
case UPLOAD_ERR_NO_FILE:
$msg = __("No file was uploaded", 'fusioninventory');
break;
case UPLOAD_ERR_NO_TMP_DIR:
$msg = __("Missing a temporary folder", 'fusioninventory');
break;
case UPLOAD_ERR_CANT_WRITE:
$msg = __("Failed to write file to disk", 'fusioninventory');
break;
case UPLOAD_ERR_EXTENSION:
$msg = __("PHP extension stopped the file upload", 'fusioninventory');
break;
case UPLOAD_ERR_OK:
//no error, continue
$error = false;
}
if ($error) {
Session::addMessageAfterRedirect($msg);
return false;
}
}
//prepare file data for insertion in repo
$data = [
'id' => $params['id'],
'file_tmp_name' => $file_tmp_name,
'mime_type' => $_FILES['file']['type'],
'filesize' => $_FILES['file']['size'],
'filename' => $filename,
'p2p' => isset($params['p2p']) ? $params['p2p'] : 0,
'uncompress' => isset($params['uncompress']) ? $params['uncompress'] : 0,
'p2p-retention-duration' => (is_numeric($params['p2p-retention-duration'])
? $params['p2p-retention-duration']: 0)
];
//Add file in repo
if ($filename && $this->addFileInRepo($data)) {
Session::addMessageAfterRedirect(__('File saved!', 'fusioninventory'));
return true;
} else {
Session::addMessageAfterRedirect(__('Failed to copy file', 'fusioninventory'));
return false;
}
}
Session::addMessageAfterRedirect(__('File missing', 'fusioninventory'));
return false;
}
/**
* Upload file from temp folder in server
*
* @param array $params
* @return boolean
*/
function uploadFileFromServer($params) {
if (preg_match('/\.\./', $params['filename'])) {
die;
}
if (isset($params["id"])) {
$file_path = $params['filename'];
$filename = basename($file_path);
if (function_exists('finfo_open')
&& ($finfo = finfo_open(FILEINFO_MIME))) {
$mime_type = finfo_file($finfo, $file_path);
finfo_close($finfo);
} else if (function_exists('mime_content_type')) {
$mime_type = mime_content_type($file_path);
}
$filesize = filesize($file_path);
//prepare file data for insertion in repo
$data = [
'file_tmp_name' => $file_path,
'mime_type' => $mime_type,
'filesize' => $filesize,
'filename' => $filename,
'p2p' => isset($params['p2p']) ? $params['p2p'] : 0,
'uncompress' => isset($params['uncompress']) ? $params['uncompress'] : 0,
'p2p-retention-duration' => (
isset($params['p2p-retention-duration'])
&& is_numeric($params['p2p-retention-duration'])
? $params['p2p-retention-duration']
: 0
),
'id' => $params['id']
];
//Add file in repo
if ($filename && $this->addFileInRepo($data)) {
Session::addMessageAfterRedirect(__('File saved!', 'fusioninventory'));
return true;
} else {
Session::addMessageAfterRedirect(__('Failed to copy file', 'fusioninventory'));
return false;
}
}
Session::addMessageAfterRedirect(__('File missing', 'fusioninventory'));
return false;
}
/**
* Get directories based on sha512
*
* @param string $sha512
* @return string the directories based on sha512
*/
function getDirBySha512 ($sha512) {
$first = substr($sha512, 0, 1);
$second = substr($sha512, 0, 2);
return "$first/$second";
}
/**
* Create a configuration request data
*
* @since 9.2
*/
public function getItemConfig(PluginFusioninventoryDeployPackage $package, $request_data) {
$element = $package->getSubElement($this->json_name, $request_data['index']);
$config = [];
if ($element) {
$config = [
'hash' => $element,
'data' => $package->getAssociatedFile($element),
];
}
return $config;
}
/**
* Move uploaded file part in right/final directory
*
* @param string $filePath path of the file + filename
* @param boolean $skip_creation
* @return string
*/
function registerFilepart ($filePath, $skip_creation = false) {
$sha512 = hash_file('sha512', $filePath);
if (!$skip_creation) {
$dir = PLUGIN_FUSIONINVENTORY_REPOSITORY_DIR.$this->getDirBySha512($sha512);
if (!file_exists ($dir)) {
mkdir($dir, 0777, true);
}
copy ($filePath, $dir.'/'.$sha512);
}
return $sha512;
}
/**
* Add file in the fusioninventory repository
*
* @param array $params
* @return boolean
*/
function addFileInRepo($params) {
$filename = addslashes($params['filename']);
$file_tmp_name = $params['file_tmp_name'];
$maxPartSize = 1024*1024;
$tmpFilepart = tempnam(GLPI_PLUGIN_DOC_DIR."/fusioninventory/", "filestore");
$sha512 = hash_file('sha512', $file_tmp_name);
$short_sha512 = substr($sha512, 0, 6);
$file_present_in_repo = false;
if ($this->checkPresenceFile($sha512)) {
$file_present_in_repo = true;
}
$file_present_in_db =
(!empty($this->find(['shortsha512' => $short_sha512])));
$new_entry = [
'name' => $filename,
'p2p' => $params['p2p'],
'p2p-retention-duration' => $params['p2p-retention-duration'],
'uncompress' => $params['uncompress'],
];
$fdIn = fopen($file_tmp_name, 'rb');
if (!$fdIn) {
return false;
}
$fdPart = null;
$multiparts = [];
do {
clearstatcache();
if (file_exists($tmpFilepart)) {
if (feof($fdIn) || filesize($tmpFilepart)>= $maxPartSize) {
$part_sha512 = $this->registerFilepart($tmpFilepart,
$file_present_in_repo);
unlink($tmpFilepart);
$multiparts[] = $part_sha512;
}
}
if (feof($fdIn)) {
break;
}
$data = fread($fdIn, 1024*1024);
$fdPart = gzopen ($tmpFilepart, 'a');
gzwrite($fdPart, $data, strlen($data));
gzclose($fdPart);
} while (1);
//create manifest file
if (!$file_present_in_repo) {
$handle = fopen(
PLUGIN_FUSIONINVENTORY_MANIFESTS_DIR.$sha512, "w+"
);
if ($handle) {
foreach ($multiparts as $sha) {
fwrite($handle, $sha."\n");
}
fclose($handle);
}
}
//TODO: Add a new files interface to list, create, manage entities and visibility
// entity on a file is just anticipated and will be fully used later
if (!$file_present_in_db) {
$entry = [
"name" => $filename,
"filesize" => $params['filesize'],
"mimetype" => $params['mime_type'],
"sha512" => $sha512,
"shortsha512" => $short_sha512,
"comments" => "",
"date_mod" => date('Y-m-d H:i:s'),
"entities_id" => 0,
"is_recursive" => 1
];
$this->add($entry);
}
//get current package json
$data = json_decode($this->getJson($params['id']), true);
//add new entry
$data[$this->json_name][$sha512] = $new_entry;
if (!in_array($sha512, $data['jobs'][$this->json_name])) {
$data['jobs'][$this->json_name][] = $sha512;
}
//update package
$this->updateOrderJson($params['id'], $data);
return true;
}
/**
* Remove file from the repository
*
* @param string $sha512 sha512 of the file
* @return boolean
*/
function removeFileInRepo($sha512) {
$pfDeployPackage = new PluginFusioninventoryDeployPackage();
// try to find file in other packages
$rows = $pfDeployPackage->find(
['json' => ['LIKE', '%'.substr($sha512, 0, 6 ).'%'],
'json' => ['LIKE', '%'.$sha512.'%']]);
//file found in other packages, do not remove parts in repo
if (count($rows) > 0) {
return false;
}
//get sha512 parts in manifest
if (!file_exists(PLUGIN_FUSIONINVENTORY_MANIFESTS_DIR.$sha512)) {
return true;
}
$multiparts = file(PLUGIN_FUSIONINVENTORY_MANIFESTS_DIR.$sha512);
//parse all files part
foreach ($multiparts as $part_sha512) {
$firstdir = PLUGIN_FUSIONINVENTORY_REPOSITORY_DIR.substr($part_sha512, 0, 1)."/";
$fulldir = PLUGIN_FUSIONINVENTORY_REPOSITORY_DIR.$this->getDirBySha512($part_sha512).'/';
//delete file parts
unlink(trim($fulldir.$part_sha512));
//delete folders if empty
if (is_dir($fulldir)) {
$count_second_folder = count(scandir($fulldir)) - 2;
if ($count_second_folder === 0) {
rmdir($fulldir);
}
}
if (is_dir($firstdir)) {
$count_first_folder = count(scandir($firstdir)) - 2; // -2 for . and ..
if ($count_first_folder === 0) {
rmdir($firstdir);
}
}
}
//remove manifest
if (file_exists(PLUGIN_FUSIONINVENTORY_MANIFESTS_DIR.$sha512)) {
unlink(PLUGIN_FUSIONINVENTORY_MANIFESTS_DIR.$sha512);
}
return true;
}
/**
* Check if the manifest relative to the sha512 exist
*
* @param string $sha512 sha512 of the file
* @return boolean
*/
function checkPresenceManifest($sha512) {
if (!file_exists(PLUGIN_FUSIONINVENTORY_MANIFESTS_DIR.$sha512)) {
return false;
}
return true;
}
/**
* Check if the file relative to the sha512 exist
*
* @param string $sha512 sha512 of the file
* @return boolean
*/
function checkPresenceFile($sha512) {
//Do not continue if the manifest is not found
if (!$this->checkPresenceManifest($sha512)) {
return false;
}
//Does the file needs to be created ?
// Even if fileparts exists, we need to be sure
// the manifest file is created
$fileparts_ok = true;
$fileparts_cnt = 0;
$handle = fopen(PLUGIN_FUSIONINVENTORY_MANIFESTS_DIR.$sha512, "r");
if ($handle) {
while (($buffer = fgets($handle)) !== false) {
$fileparts_cnt++;
$path = $this->getDirBySha512($buffer)."/".trim($buffer, "\n");
//Check if the filepart exists
if (!file_exists(PLUGIN_FUSIONINVENTORY_REPOSITORY_DIR.$path)) {
$fileparts_ok = false;
break;
}
}
fclose($handle);
}
// Does the file is empty ?
if ($fileparts_cnt == 0) {
return false;
}
//Does the file needs to be replaced ?
if (!$fileparts_ok) {
return false;
}
//Nothing to do because the manifest and associated fileparts seems to be fine.
return true;
}
/**
* Get the maximum size the php can accept for upload file
*
* @return string
*/
function getMaxUploadSize() {
$max_upload = (int)(ini_get('upload_max_filesize'));
$max_post = (int)(ini_get('post_max_size'));
$memory_limit = (int)(ini_get('memory_limit'));
return __('Max file size', 'fusioninventory')
." : ".min($max_upload, $max_post, $memory_limit).__('Mio', 'fusioninventory');
}
/**
* List number of files not used in packages
*/
function numberUnusedFiles() {
echo "";
echo "";
echo "";
echo __('Unused file', 'fusioninventory');
echo " ";
echo "";
echo __('Size', 'fusioninventory');
echo " ";
echo " ";
$a_files = $this->find();
foreach ($a_files as $data) {
$cnt = countElementsInTable('glpi_plugin_fusioninventory_deploypackages',
['json' => ['LIKE', '%"' . $data['sha512'] . '"%']]);
if ($cnt == 0) {
echo "";
echo "";
echo $data['name'];
echo " ";
echo "";
echo round($data['filesize'] / 1000000, 1)." ".__('Mio');
echo " ";
echo " ";
}
}
echo "
";
}
/**
* Delete the files not used in packages
*/
function deleteUnusedFiles() {
$a_files = $this->find();
foreach ($a_files as $data) {
$cnt = countElementsInTable('glpi_plugin_fusioninventory_deploypackages',
['json' => ['LIKE', '%"' . $data['sha512'] . '"%']]);
if ($cnt == 0) {
$this->delete($data);
$manifest_filename = PLUGIN_FUSIONINVENTORY_MANIFESTS_DIR.$data['sha512'];
if (file_exists($manifest_filename)) {
$handle = @fopen($manifest_filename, "r");
if ($handle) {
while (!feof($handle)) {
$buffer = trim(fgets($handle));
if ($buffer != '') {
$part_path = $this->getDirBySha512($buffer)."/".$buffer;
unlink(PLUGIN_FUSIONINVENTORY_REPOSITORY_DIR.$part_path);
}
}
fclose($handle);
}
unlink($manifest_filename);
}
}
}
}
}