Files
iTop/webservices/export.php
Pierre Goiffon b7136c0b7a N°2214 Add PHP check in CLI scripts
It is quite common that the PHP interpreter that is launched in CLI is different that the one used by the webserver. So iTop code launched by CLI could run in a context that doesn't meet iTop requirements !

This adds in the following scripts the same control that is done on the setup wizard first step :
* cron.php
* backup, check-backup
* export, exportv2
* bulk import
* synchro-exec, synchro-import

If the check throws at least one error then the script is stopped with an appropriate message, and a log is made (IssueLog, Error level, CLI channel)

(cherry picked from commit c768e18e2b : no risk taken for 2.7.1, so cherry picked for 2.8.0)
2020-06-15 15:20:17 +02:00

369 lines
12 KiB
PHP

<?php
/**
* Copyright (C) 2013-2019 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop 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. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
require_once(__DIR__.'/../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/nicewebpage.class.inc.php');
require_once(APPROOT.'/application/ajaxwebpage.class.inc.php');
require_once(APPROOT.'/application/csvpage.class.inc.php');
require_once(APPROOT.'/application/xmlpage.class.inc.php');
require_once(APPROOT.'/application/clipage.class.inc.php');
require_once(APPROOT.'/application/excelexporter.class.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
const EXIT_CODE_ERROR = -1;
const EXIT_CODE_FATAL = -2;
try
{
// Do this before loging, in order to allow setting user credentials from within the file
utils::UseParamFile();
}
catch(Exception $e)
{
echo "Error: ".$e->GetMessage()."<br/>\n";
exit(EXIT_CODE_FATAL);
}
if (utils::IsModeCLI())
{
$oP = new CLIPage("iTop - Export");
SetupUtils::CheckPhpAndExtensionsForCli($oP, EXIT_CODE_FATAL);
$sAuthUser = utils::ReadParam('auth_user', null, true /* Allow CLI */, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, true /* Allow CLI */, 'raw_data');
if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd))
{
UserRights::Login($sAuthUser); // Login & set the user's language
}
else
{
$oP->p("Access restricted or wrong credentials ('$sAuthUser')");
$oP->output();
exit(EXIT_CODE_ERROR);
}
}
else
{
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
LoginWebPage::DoLogin(); // Check user rights and prompt if needed
}
ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker');
$oAppContext = new ApplicationContext();
$iActiveNodeId = utils::ReadParam('menu', -1);
$currentOrganization = utils::ReadParam('org_id', '');
if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive())
{
$oP = new CLIPage("iTop - Export");
$oP->p("The user account is not authorized to access the archives");
$oP->output();
exit(EXIT_CODE_ERROR);
}
$bLocalize = (utils::ReadParam('no_localize', 0) != 1);
$sFileName = utils::ReadParam('filename', '', true, 'string');
// Main program
$sExpression = utils::ReadParam('expression', '', true /* Allow CLI */, 'raw_data');
$sFields = trim(utils::ReadParam('fields', '', true, 'raw_data')); // CSV field list (allows to specify link set attributes, still not taken into account for XML export)
$bFieldsAdvanced = utils::ReadParam('fields_advanced', 0);
if (strlen($sExpression) == 0)
{
$sQueryId = trim(utils::ReadParam('query', '', true /* Allow CLI */, 'raw_data'));
if (strlen($sQueryId) > 0)
{
$oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId));
$oQueries = new DBObjectSet($oSearch);
if ($oQueries->Count() > 0)
{
$oQuery = $oQueries->Fetch();
$sExpression = $oQuery->Get('oql');
if (strlen($sFields) == 0)
{
$sFields = trim($oQuery->Get('fields'));
}
}
}
}
$sFormat = strtolower(utils::ReadParam('format', 'html', true /* Allow CLI */));
$aFields = explode(',', $sFields);
// Clean the list of columns (empty it if every string is empty)
foreach($aFields as $index => $sField)
{
$aFields[$index] = trim($sField);
if(strlen($aFields[$index]) == 0)
{
unset($aFields[$index]);
}
}
$oP = null;
if (!empty($sExpression))
{
try
{
$oFilter = DBObjectSearch::FromOQL($sExpression);
// Check and adjust column names
//
$aAliasToFields = array();
foreach($aFields as $index => $sField)
{
if (preg_match('/^(.*)\.(.*)$/', $sField, $aMatches))
{
$sClassAlias = $aMatches[1];
$sAttCode = $aMatches[2];
}
else
{
$sClassAlias = $oFilter->GetClassAlias();
$sAttCode = $sField;
// Disambiguate the class alias
$aFields[$index] = $sClassAlias.'.'.$sAttCode;
}
$aAliasToFields[$sClassAlias][] = $sAttCode;
$sClass = $oFilter->GetClassName($sClassAlias);
if (!MetaModel::IsValidAttCode($sClass, $sAttCode))
{
throw new CoreException("Invalid field specification $sField: $sAttCode is not a valid attribute for $sClass");
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef instanceof AttributeSubItem)
{
$aAliasToFields[$sClassAlias][] = $oAttDef->GetParentAttCode();
}
else if($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName())
{
$sKeyAttCode = $oAttDef->GetKeyAttCode();
$aAliasToFields[$sClassAlias][] = $sKeyAttCode;
}
}
// Read query parameters
//
$aArgs = array();
foreach($oFilter->GetQueryParams() as $sParam => $foo)
{
$value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data');
if (!is_null($value))
{
$aArgs[$sParam] = $value;
}
}
$oFilter->SetInternalParams($aArgs);
foreach ($oFilter->GetSelectedClasses() as $sAlias => $sClass)
{
if ((UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_READ) && UR_ALLOWED_YES) == 0)
{
throw new Exception("The current user does not have permission for exporting data of class $sClass");
}
}
if ($oFilter)
{
$oSet = new CMDBObjectSet($oFilter, array(), $aArgs);
$oSet->OptimizeColumnLoad($aAliasToFields);
switch($sFormat)
{
case 'html':
$oP = new NiceWebPage("iTop - Export");
$oP->add_style('body { overflow: auto; }'); // Show scroll bars if needed
$oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css');
$oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/v4-shims.min.css');
// Integration within MS-Excel web queries + HTTPS + IIS:
// MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS
// Then the fix is to force the reset of header values Pragma and Cache-control
header("Pragma:", true);
header("Cache-control:", true);
// The HTML output is made for pages located in the /pages/ folder
// since this page is in a different folder, let's adjust the HTML 'base' attribute
// to make the relative hyperlinks in the page work
$sUrl = utils::GetAbsoluteUrlAppRoot();
$oP->set_base($sUrl.'pages/');
if(count($aFields) > 0)
{
$iSearch = array_search('id', $aFields);
if ($iSearch !== false)
{
$bViewLink = true;
unset($aFields[$iSearch]);
}
else
{
$bViewLink = false;
}
$sFields = implode(',', $aFields);
$aExtraParams = array('menu' => false, 'toolkit_menu' => false, 'display_limit' => false, 'localize_values' => $bLocalize, 'zlist' => false, 'extra_fields' => $sFields, 'view_link' => $bViewLink);
}
else
{
$aExtraParams = array('menu' => false, 'toolkit_menu' => false, 'display_limit' => false, 'localize_values' => $bLocalize, 'zlist' => 'details');
}
$oResultBlock = new DisplayBlock($oFilter, 'list', false, $aExtraParams);
$oResultBlock->Display($oP, 'expresult');
break;
case 'csv':
$oP = new CSVPage("iTop - Export");
$sFields = implode(',', $aFields);
$sCharset = utils::ReadParam('charset', MetaModel::GetConfig()->Get('csv_file_default_charset'), true /* Allow CLI */, 'raw_data');
$sCSVData = cmdbAbstractObject::GetSetAsCSV($oSet, array('fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize), $sCharset);
if ($sCharset == 'UTF-8')
{
$sOutputData = UTF8_BOM.$sCSVData;
}
else
{
$sOutputData = $sCSVData;
}
if ($sFileName == '')
{
// Plain text => Firefox will NOT propose to download the file
$oP->add_header("Content-type: text/plain; charset=$sCharset");
}
else
{
$oP->add_header("Content-type: text/csv; charset=$sCharset");
}
$oP->add($sOutputData);
break;
case 'spreadsheet':
$oP = new WebPage("iTop - Export for spreadsheet");
// Integration within MS-Excel web queries + HTTPS + IIS:
// MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS
// Then the fix is to force the reset of header values Pragma and Cache-control
header("Pragma:", true);
header("Cache-control:", true);
$sFields = implode(',', $aFields);
$oP->add_style('table br {mso-data-placement:same-cell;}'); // Trick for Excel: keep line breaks inside the same cell !
cmdbAbstractObject::DisplaySetAsHTMLSpreadsheet($oP, $oSet, array('fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize));
break;
case 'xml':
$oP = new XMLPage("iTop - Export", true /* passthrough */);
cmdbAbstractObject::DisplaySetAsXML($oP, $oSet, array('localize_values' => $bLocalize));
break;
case 'xlsx':
$oP = new ajax_page('');
$oExporter = new ExcelExporter();
$oExporter->SetObjectList($oFilter);
// Run the export by chunk of 1000 objects to limit memory usage
$oExporter->SetChunkSize(1000);
do
{
$aStatus = $oExporter->Run(); // process one chunk
}
while( ($aStatus['code'] != 'done') && ($aStatus['code'] != 'error'));
if ($aStatus['code'] == 'done')
{
$oP->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
$oP->SetContentDisposition('attachment', $oFilter->GetClass().'.xlsx');
$oP->add(file_get_contents($oExporter->GetExcelFilePath()));
$oExporter->Cleanup();
}
else
{
$oP->add('Error, xlsx export failed: '.$aStatus['message']);
}
break;
default:
$oP = new WebPage("iTop - Export");
$oP->add("Unsupported format '$sFormat'. Possible values are: html, csv, spreadsheet or xml.");
}
}
}
catch(Exception $e)
{
$oP = new WebPage("iTop - Export");
$oP->p("Error the query can not be executed.");
if ($e instanceof CoreException)
{
$oP->p($e->GetHtmlDesc());
}
else
{
$oP->p($e->getMessage());
}
}
}
if (!$oP)
{
// Display a short message about how to use this page
$bModeCLI = utils::IsModeCLI();
if ($bModeCLI)
{
$oP = new CLIPage("iTop - Export");
}
else
{
$oP = new WebPage("iTop - Export");
}
$oP->p("General purpose export page.");
$oP->p("Parameters:");
$oP->p(" * expression: an OQL expression (URL encoded if needed)");
$oP->p(" * query: (alternative to 'expression') the id of an entry from the query phrasebook");
if (Utils::IsModeCLI())
{
$oP->p(" * with_archive: (optional, defaults to 0) if set to 1 then the result set will include archived objects");
}
else
{
$oP->p(" * with_archive: (optional, defaults to the current mode) if set to 1 then the result set will include archived objects");
}
$oP->p(" * arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'");
$oP->p(" * format: (optional, default is html) the desired output format. Can be one of 'html', 'spreadsheet', 'csv', 'xlsx' or 'xml'");
$oP->p(" * fields: (optional, no effect on XML format) list of fields (attribute codes, or alias.attcode) separated by a coma");
$oP->p(" * fields_advanced: (optional, no effect on XML/HTML formats ; ignored is fields is specified) If set to 1, the default list of fields will include the external keys and their reconciliation keys");
$oP->p(" * filename: (optional, no effect in CLI mode) if set then the results will be downloaded as a file");
}
if ($sFileName != '')
{
$oP->add_header('Content-Disposition: attachment; filename="'.$sFileName.'"');
}
$oP->TrashUnexpectedOutput();
$oP->output();
?>