mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
Integration of the Excel (XLSX) export feature. (Limitation: export.php takes into account neither the "fields" parameter nor the list of fields defined in the QueryPhrasebook when exporting in XLSX format)
SVN:trunk[3398]
This commit is contained in:
536
application/excelexporter.class.inc.php
Normal file
536
application/excelexporter.class.inc.php
Normal file
@@ -0,0 +1,536 @@
|
|||||||
|
<?php
|
||||||
|
require_once('xlsxwriter.class.php');
|
||||||
|
|
||||||
|
class ExcelExporter
|
||||||
|
{
|
||||||
|
protected $sToken;
|
||||||
|
protected $aStatistics;
|
||||||
|
protected $sState;
|
||||||
|
protected $fStartTime;
|
||||||
|
protected $oSearch;
|
||||||
|
protected $aObjectsIDs;
|
||||||
|
protected $aTableHeaders;
|
||||||
|
protected $aAuthorizedClasses;
|
||||||
|
protected $iChunkSize = 1000;
|
||||||
|
protected $iPosition;
|
||||||
|
protected $sOutputFilePath;
|
||||||
|
protected $bAdvancedMode;
|
||||||
|
|
||||||
|
public function __construct($sToken = null)
|
||||||
|
{
|
||||||
|
$this->aStatistics = array(
|
||||||
|
'objects_count' => 0,
|
||||||
|
'total_duration' => 0,
|
||||||
|
'data_retrieval_duration' => 0,
|
||||||
|
'excel_build_duration' => 0,
|
||||||
|
'excel_write_duration' => 0,
|
||||||
|
'peak_memory_usage' => 0,
|
||||||
|
);
|
||||||
|
$this->fStartTime = microtime(true);
|
||||||
|
$this->oSearch = null;
|
||||||
|
|
||||||
|
$this->sState = 'new';
|
||||||
|
$this->aObjectsIDs = array();
|
||||||
|
$this->iPosition = 0;
|
||||||
|
$this->aAuthorizedClasses = null;
|
||||||
|
$this->aTableHeaders = null;
|
||||||
|
$this->sOutputFilePath = null;
|
||||||
|
$this->bAdvancedMode = false;
|
||||||
|
$this->CheckDataDir();
|
||||||
|
if ($sToken == null)
|
||||||
|
{
|
||||||
|
$this->sToken = $this->GetNewToken();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->sToken = $sToken;
|
||||||
|
$this->ReloadState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if (($this->sState != 'done') && ($this->sState != 'error') && ($this->sToken != null))
|
||||||
|
{
|
||||||
|
// Operation in progress, save the state
|
||||||
|
$this->SaveState();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Operation completed, cleanup the temp files
|
||||||
|
@unlink($this->GetStateFile());
|
||||||
|
@unlink($this->GetDataFile());
|
||||||
|
}
|
||||||
|
self::CleanupOldFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SetChunkSize($iChunkSize)
|
||||||
|
{
|
||||||
|
$this->iChunkSize = $iChunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SetOutputFilePath($sDestFilePath)
|
||||||
|
{
|
||||||
|
$this->sOutputFilePath = $sDestFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SetAdvancedMode($bAdvanced)
|
||||||
|
{
|
||||||
|
$this->bAdvancedMode = $bAdvanced;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SaveState()
|
||||||
|
{
|
||||||
|
$aState = array(
|
||||||
|
'state' => $this->sState,
|
||||||
|
'statistics' => $this->aStatistics,
|
||||||
|
'filter' => $this->oSearch->serialize(),
|
||||||
|
'position' => $this->iPosition,
|
||||||
|
'chunk_size' => $this->iChunkSize,
|
||||||
|
'object_ids' => $this->aObjectsIDs,
|
||||||
|
'output_file_path' => $this->sOutputFilePath,
|
||||||
|
'advanced_mode' => $this->bAdvancedMode,
|
||||||
|
);
|
||||||
|
|
||||||
|
file_put_contents($this->GetStateFile(), json_encode($aState));
|
||||||
|
|
||||||
|
return $this->sToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ReloadState()
|
||||||
|
{
|
||||||
|
if ($this->sToken == null)
|
||||||
|
{
|
||||||
|
throw new Exception('ExcelExporter not initialized with a token, cannot reload state');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists($this->GetStateFile()))
|
||||||
|
{
|
||||||
|
throw new Exception("ExcelExporter: missing status file '".$this->GetStateFile()."', cannot reload state.");
|
||||||
|
}
|
||||||
|
$sJson = file_get_contents($this->GetStateFile());
|
||||||
|
$aState = json_decode($sJson, true);
|
||||||
|
if ($aState === null)
|
||||||
|
{
|
||||||
|
throw new Exception("ExcelExporter:corrupted status file '".$this->GetStateFile()."', not a JSON, cannot reload state.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sState = $aState['state'];
|
||||||
|
$this->aStatistics = $aState['statistics'];
|
||||||
|
$this->oSearch = DBObjectSearch::unserialize($aState['filter']);
|
||||||
|
$this->iPosition = $aState['position'];
|
||||||
|
$this->iChunkSize = $aState['chunk_size'];
|
||||||
|
$this->aObjectsIDs = $aState['object_ids'];
|
||||||
|
$this->sOutputFilePath = $aState['output_file_path'];
|
||||||
|
$this->bAdvancedMode = $aState['advanced_mode'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SetObjectList($oSearch)
|
||||||
|
{
|
||||||
|
$this->oSearch = $oSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Run()
|
||||||
|
{
|
||||||
|
$sCode = 'error';
|
||||||
|
$iPercentage = 100;
|
||||||
|
$sMessage = Dict::Format('ExcelExporter:ErrorUnexpected_State', $this->sState);
|
||||||
|
$fTime = microtime(true);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch($this->sState)
|
||||||
|
{
|
||||||
|
case 'new':
|
||||||
|
$oIDSet = new DBObjectSet($this->oSearch);
|
||||||
|
$oIDSet->OptimizeColumnLoad(array('id'));
|
||||||
|
$this->aObjectsIDs = array();
|
||||||
|
while($oObj = $oIDSet->Fetch())
|
||||||
|
{
|
||||||
|
$this->aObjectsIDs[] = $oObj->GetKey();
|
||||||
|
}
|
||||||
|
$sCode = 'retrieving-data';
|
||||||
|
$iPercentage = 5;
|
||||||
|
$sMessage = Dict::S('ExcelExporter:RetrievingData');
|
||||||
|
$this->iPosition = 0;
|
||||||
|
$this->aStatistics['objects_count'] = count($this->aObjectsIDs);
|
||||||
|
$this->aStatistics['data_retrieval_duration'] += microtime(true) - $fTime;
|
||||||
|
|
||||||
|
// The first line of the file is the "headers" specifying the label and the type of each column
|
||||||
|
$this->GetFieldsList($oIDSet, $this->bAdvancedMode);
|
||||||
|
$sRow = json_encode($this->aTableHeaders);
|
||||||
|
$hFile = @fopen($this->GetDataFile(), 'ab');
|
||||||
|
if ($hFile === false)
|
||||||
|
{
|
||||||
|
throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for writing.');
|
||||||
|
}
|
||||||
|
fwrite($hFile, $sRow."\n");
|
||||||
|
fclose($hFile);
|
||||||
|
|
||||||
|
// Next state
|
||||||
|
$this->sState = 'retrieving-data';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'retrieving-data':
|
||||||
|
$oCurrentSearch = clone $this->oSearch;
|
||||||
|
$aIDs = array_slice($this->aObjectsIDs, $this->iPosition, $this->iChunkSize);
|
||||||
|
|
||||||
|
$oCurrentSearch->AddCondition('id', $aIDs, 'IN');
|
||||||
|
$hFile = @fopen($this->GetDataFile(), 'ab');
|
||||||
|
if ($hFile === false)
|
||||||
|
{
|
||||||
|
throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for writing.');
|
||||||
|
}
|
||||||
|
$oSet = new DBObjectSet($oCurrentSearch);
|
||||||
|
$this->GetFieldsList($oSet, $this->bAdvancedMode);
|
||||||
|
while($aObjects = $oSet->FetchAssoc())
|
||||||
|
{
|
||||||
|
$aRow = array();
|
||||||
|
foreach($this->aAuthorizedClasses as $sAlias => $sClassName)
|
||||||
|
{
|
||||||
|
$oObj = $aObjects[$sAlias];
|
||||||
|
if ($this->bAdvancedMode)
|
||||||
|
{
|
||||||
|
$aRow[] = $oObj->GetKey();
|
||||||
|
}
|
||||||
|
foreach($this->aFieldsList[$sAlias] as $sAttCodeEx => $oAttDef)
|
||||||
|
{
|
||||||
|
$value = $oObj->Get($sAttCodeEx);
|
||||||
|
if ($value instanceOf ormCaseLog)
|
||||||
|
{
|
||||||
|
// Extract the case log as text and remove the "===" which make Excel think that the cell contains a formula the next time you edit it!
|
||||||
|
$sExcelVal = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $value->GetText()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sExcelVal = $oAttDef->GetEditValue($value, $oObj);
|
||||||
|
}
|
||||||
|
$aRow[] = $sExcelVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$sRow = json_encode($aRow);
|
||||||
|
fwrite($hFile, $sRow."\n");
|
||||||
|
}
|
||||||
|
fclose($hFile);
|
||||||
|
|
||||||
|
if (($this->iPosition + $this->iChunkSize) > count($this->aObjectsIDs))
|
||||||
|
{
|
||||||
|
// Next state
|
||||||
|
$this->sState = 'building-excel';
|
||||||
|
$sCode = 'building-excel';
|
||||||
|
$iPercentage = 80;
|
||||||
|
$sMessage = Dict::S('ExcelExporter:BuildingExcelFile');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sCode = 'retrieving-data';
|
||||||
|
$this->iPosition += $this->iChunkSize;
|
||||||
|
$iPercentage = 5 + round(75 * ($this->iPosition / count($this->aObjectsIDs)));
|
||||||
|
$sMessage = Dict::S('ExcelExporter:RetrievingData');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'building-excel':
|
||||||
|
$hFile = @fopen($this->GetDataFile(), 'rb');
|
||||||
|
if ($hFile === false)
|
||||||
|
{
|
||||||
|
throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for reading.');
|
||||||
|
}
|
||||||
|
$sHeaders = fgets($hFile);
|
||||||
|
$aHeaders = json_decode($sHeaders, true);
|
||||||
|
|
||||||
|
$aData = array();
|
||||||
|
while($sLine = fgets($hFile))
|
||||||
|
{
|
||||||
|
$aRow = json_decode($sLine);
|
||||||
|
$aData[] = $aRow;
|
||||||
|
}
|
||||||
|
fclose($hFile);
|
||||||
|
@unlink($this->GetDataFile());
|
||||||
|
|
||||||
|
$fStartExcel = microtime(true);
|
||||||
|
$writer = new XLSXWriter();
|
||||||
|
$writer->setAuthor(UserRights::GetUserFriendlyName());
|
||||||
|
$writer->writeSheet($aData,'Sheet1', $aHeaders);
|
||||||
|
$fExcelTime = microtime(true) - $fStartExcel;
|
||||||
|
$this->aStatistics['excel_build_duration'] = $fExcelTime;
|
||||||
|
|
||||||
|
$fTime = microtime(true);
|
||||||
|
$writer->writeToFile($this->GetExcelFilePath());
|
||||||
|
$fExcelSaveTime = microtime(true) - $fTime;
|
||||||
|
$this->aStatistics['excel_write_duration'] = $fExcelSaveTime;
|
||||||
|
|
||||||
|
// Next state
|
||||||
|
$this->sState = 'done';
|
||||||
|
$sCode = 'done';
|
||||||
|
$iPercentage = 100;
|
||||||
|
$sMessage = Dict::S('ExcelExporter:Done');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'done':
|
||||||
|
$this->sState = 'done';
|
||||||
|
$sCode = 'done';
|
||||||
|
$iPercentage = 100;
|
||||||
|
$sMessage = Dict::S('ExcelExporter:Done');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception $e)
|
||||||
|
{
|
||||||
|
$sCode = 'error';
|
||||||
|
$sMessage = $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->aStatistics['total_duration'] += microtime(true) - $fTime;
|
||||||
|
$peak_memory = memory_get_peak_usage(true);
|
||||||
|
if ($peak_memory > $this->aStatistics['peak_memory_usage'])
|
||||||
|
{
|
||||||
|
$this->aStatistics['peak_memory_usage'] = $peak_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'code' => $sCode,
|
||||||
|
'message' => $sMessage,
|
||||||
|
'percentage' => $iPercentage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetExcelFilePath()
|
||||||
|
{
|
||||||
|
if ($this->sOutputFilePath == null)
|
||||||
|
{
|
||||||
|
return APPROOT.'data/bulk_export/'.$this->sToken.'.xlsx';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->sOutputFilePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function GetExcelFileFromToken($sToken)
|
||||||
|
{
|
||||||
|
return @file_get_contents(APPROOT.'data/bulk_export/'.$sToken.'.xlsx');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function CleanupFromToken($sToken)
|
||||||
|
{
|
||||||
|
@unlink(APPROOT.'data/bulk_export/'.$sToken.'.status');
|
||||||
|
@unlink(APPROOT.'data/bulk_export/'.$sToken.'.data');
|
||||||
|
@unlink(APPROOT.'data/bulk_export/'.$sToken.'.xlsx');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Cleanup()
|
||||||
|
{
|
||||||
|
self::CleanupFromToken($this->sToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all files in the data/bulk_export directory which are older than 1 day
|
||||||
|
* unless a different delay is configured.
|
||||||
|
*/
|
||||||
|
public static function CleanupOldFiles()
|
||||||
|
{
|
||||||
|
$aFiles = glob(APPROOT.'data/bulk_export/*.*');
|
||||||
|
$iDelay = MetaModel::GetConfig()->Get('xlsx_exporter_cleanup_old_files_delay');
|
||||||
|
|
||||||
|
if($iDelay > 0)
|
||||||
|
{
|
||||||
|
foreach($aFiles as $sFile)
|
||||||
|
{
|
||||||
|
$iModificationTime = filemtime($sFile);
|
||||||
|
|
||||||
|
if($iModificationTime < (time() - $iDelay))
|
||||||
|
{
|
||||||
|
// Temporary files older than one day are deleted
|
||||||
|
//echo "Supposed to delete: '".$sFile." (Unix Modification Time: $iModificationTime)'\n";
|
||||||
|
@unlink($sFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function DisplayStatistics(Page $oPage)
|
||||||
|
{
|
||||||
|
$aStats = array(
|
||||||
|
'Number of objects exported' => $this->aStatistics['objects_count'],
|
||||||
|
'Total export duration' => sprintf('%.3f s', $this->aStatistics['total_duration']),
|
||||||
|
'Data retrieval duration' => sprintf('%.3f s', $this->aStatistics['data_retrieval_duration']),
|
||||||
|
'Excel build duration' => sprintf('%.3f s', $this->aStatistics['excel_build_duration']),
|
||||||
|
'Excel write duration' => sprintf('%.3f s', $this->aStatistics['excel_write_duration']),
|
||||||
|
'Peak memory usage' => self::HumanDisplay($this->aStatistics['peak_memory_usage']),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($oPage instanceof CLIPage)
|
||||||
|
{
|
||||||
|
$oPage->add($this->GetStatistics('text'));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$oPage->add($this->GetStatistics('html'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetStatistics($sFormat = 'html')
|
||||||
|
{
|
||||||
|
$sStats = '';
|
||||||
|
$aStats = array(
|
||||||
|
'Number of objects exported' => $this->aStatistics['objects_count'],
|
||||||
|
'Total export duration' => sprintf('%.3f s', $this->aStatistics['total_duration']),
|
||||||
|
'Data retrieval duration' => sprintf('%.3f s', $this->aStatistics['data_retrieval_duration']),
|
||||||
|
'Excel build duration' => sprintf('%.3f s', $this->aStatistics['excel_build_duration']),
|
||||||
|
'Excel write duration' => sprintf('%.3f s', $this->aStatistics['excel_write_duration']),
|
||||||
|
'Peak memory usage' => self::HumanDisplay($this->aStatistics['peak_memory_usage']),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($sFormat == 'text')
|
||||||
|
{
|
||||||
|
foreach($aStats as $sLabel => $sValue)
|
||||||
|
{
|
||||||
|
$sStats .= "+------------------------------+----------+\n";
|
||||||
|
$sStats .= sprintf("|%-30s|%10s|\n", $sLabel, $sValue);
|
||||||
|
}
|
||||||
|
$sStats .= "+------------------------------+----------+";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sStats .= '<table><tbody>';
|
||||||
|
foreach($aStats as $sLabel => $sValue)
|
||||||
|
{
|
||||||
|
$sStats .= "<tr><td>$sLabel</td><td>$sValue</td></tr>";
|
||||||
|
}
|
||||||
|
$sStats .= '</tbody></table>';
|
||||||
|
|
||||||
|
}
|
||||||
|
return $sStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function HumanDisplay($iSize)
|
||||||
|
{
|
||||||
|
$aUnits = array('B','KB','MB','GB','TB','PB');
|
||||||
|
return @round($iSize/pow(1024,($i=floor(log($iSize,1024)))),2).' '.$aUnits[$i];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function CheckDataDir()
|
||||||
|
{
|
||||||
|
if(!is_dir(APPROOT."data/bulk_export"))
|
||||||
|
{
|
||||||
|
@mkdir(APPROOT."data/bulk_export", 0777, true /* recursive */);
|
||||||
|
clearstatcache();
|
||||||
|
}
|
||||||
|
if (!is_writable(APPROOT."data/bulk_export"))
|
||||||
|
{
|
||||||
|
throw new Exception('Data directory "'.APPROOT.'data/bulk_export" could not be written.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function GetStateFile($sToken = null)
|
||||||
|
{
|
||||||
|
if ($sToken == null)
|
||||||
|
{
|
||||||
|
$sToken = $this->sToken;
|
||||||
|
}
|
||||||
|
return APPROOT."data/bulk_export/$sToken.status";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function GetDataFile()
|
||||||
|
{
|
||||||
|
return APPROOT.'data/bulk_export/'.$this->sToken.'.data';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function GetNewToken()
|
||||||
|
{
|
||||||
|
$iNum = rand();
|
||||||
|
do
|
||||||
|
{
|
||||||
|
$iNum++;
|
||||||
|
$sToken = sprintf("%08x", $iNum);
|
||||||
|
$sFileName = $this->GetStateFile($sToken);
|
||||||
|
$hFile = @fopen($sFileName, 'x');
|
||||||
|
}
|
||||||
|
while($hFile === false);
|
||||||
|
|
||||||
|
fclose($hFile);
|
||||||
|
return $sToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function GetFieldsList($oSet, $bFieldsAdvanced = false, $bLocalize = true, $aFields = null)
|
||||||
|
{
|
||||||
|
$this->aFieldsList = array();
|
||||||
|
|
||||||
|
$oAppContext = new ApplicationContext();
|
||||||
|
$aClasses = $oSet->GetFilter()->GetSelectedClasses();
|
||||||
|
$this->aAuthorizedClasses = array();
|
||||||
|
foreach($aClasses as $sAlias => $sClassName)
|
||||||
|
{
|
||||||
|
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
|
||||||
|
{
|
||||||
|
$this->aAuthorizedClasses[$sAlias] = $sClassName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$aAttribs = array();
|
||||||
|
$this->aTableHeaders = array();
|
||||||
|
foreach($this->aAuthorizedClasses as $sAlias => $sClassName)
|
||||||
|
{
|
||||||
|
$aList[$sAlias] = array();
|
||||||
|
|
||||||
|
foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef)
|
||||||
|
{
|
||||||
|
if (is_null($aFields) || (count($aFields) == 0))
|
||||||
|
{
|
||||||
|
// Standard list of attributes (no link sets)
|
||||||
|
if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField()))
|
||||||
|
{
|
||||||
|
$sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode;
|
||||||
|
|
||||||
|
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
|
||||||
|
{
|
||||||
|
if ($bFieldsAdvanced)
|
||||||
|
{
|
||||||
|
$aList[$sAlias][$sAttCodeEx] = $oAttDef;
|
||||||
|
|
||||||
|
if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE))
|
||||||
|
{
|
||||||
|
$sRemoteClass = $oAttDef->GetTargetClass();
|
||||||
|
foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode)
|
||||||
|
{
|
||||||
|
$this->aFieldsList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, $sRemoteAttCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Any other attribute
|
||||||
|
$this->aFieldsList[$sAlias][$sAttCodeEx] = $oAttDef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User defined list of attributes
|
||||||
|
if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields))
|
||||||
|
{
|
||||||
|
$this->aFieldsList[$sAlias][$sAttCode] = $oAttDef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($bFieldsAdvanced)
|
||||||
|
{
|
||||||
|
$this->aTableHeaders['id'] = '0';
|
||||||
|
}
|
||||||
|
foreach($this->aFieldsList[$sAlias] as $sAttCodeEx => $oAttDef)
|
||||||
|
{
|
||||||
|
$sLabel = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx, isset($aParams['showMandatoryFields'])) : $sAttCodeEx;
|
||||||
|
if($oAttDef instanceof AttributeDateTime)
|
||||||
|
{
|
||||||
|
$this->aTableHeaders[$sLabel] = 'datetime';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->aTableHeaders[$sLabel] = 'string';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -783,11 +783,16 @@ class utils
|
|||||||
$sOQL = addslashes($param->GetFilter()->ToOQL(true));
|
$sOQL = addslashes($param->GetFilter()->ToOQL(true));
|
||||||
$sFilter = urlencode($param->GetFilter()->serialize());
|
$sFilter = urlencode($param->GetFilter()->serialize());
|
||||||
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}";
|
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}";
|
||||||
|
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/xlsx-export.js');
|
||||||
|
$sXlsxFilter = $param->GetFilter()->serialize();
|
||||||
|
$sXlsxJSFilter = addslashes($sXlsxFilter);
|
||||||
|
|
||||||
$aResult = array(
|
$aResult = array(
|
||||||
new SeparatorPopupMenuItem(),
|
new SeparatorPopupMenuItem(),
|
||||||
// Static menus: Email this page, CSV Export & Add to Dashboard
|
// Static menus: Email this page, CSV Export & Add to Dashboard
|
||||||
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
|
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
|
||||||
new URLPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), $sUrl."&format=csv"),
|
new URLPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), $sUrl."&format=csv"),
|
||||||
|
new JSPopupMenuItem('xlsx-export', Dict::S('ExcelExporter:ExportMenu'), "XlsxExportDialog('$sXlsxJSFilter');", array()),
|
||||||
new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL')"),
|
new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL')"),
|
||||||
new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')"),
|
new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')"),
|
||||||
);
|
);
|
||||||
@@ -802,11 +807,15 @@ class utils
|
|||||||
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage(get_class($oObj));
|
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage(get_class($oObj));
|
||||||
$oAppContext = new ApplicationContext();
|
$oAppContext = new ApplicationContext();
|
||||||
$sContext = $oAppContext->GetForLink();
|
$sContext = $oAppContext->GetForLink();
|
||||||
|
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/xlsx-export.js');
|
||||||
|
$sXlsxFilter = $param->GetFilter()->serialize();
|
||||||
|
$sXlsxJSFilter = addslashes($sXlsxFilter);
|
||||||
$aResult = array(
|
$aResult = array(
|
||||||
new SeparatorPopupMenuItem(),
|
new SeparatorPopupMenuItem(),
|
||||||
// Static menus: Email this page & CSV Export
|
// Static menus: Email this page & CSV Export
|
||||||
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
|
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
|
||||||
new URLPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".urlencode($sFilter)."&format=csv&{$sContext}"),
|
new URLPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".urlencode($sFilter)."&format=csv&{$sContext}"),
|
||||||
|
new JSPopupMenuItem('xlsx-export', Dict::S('ExcelExporter:ExportMenu'), "XlsxExportDialog('$sXlsxJSFilter');", array()),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
456
application/xlsxwriter.class.php
Normal file
456
application/xlsxwriter.class.php
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
<?php
|
||||||
|
/* @author Mark Jones
|
||||||
|
* @license MIT License
|
||||||
|
* */
|
||||||
|
|
||||||
|
if (!class_exists('ZipArchive')) { throw new Exception('ZipArchive not found'); }
|
||||||
|
|
||||||
|
Class XLSXWriter
|
||||||
|
{
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
protected $author ='Doc Author';
|
||||||
|
protected $sheets_meta = array();
|
||||||
|
protected $shared_strings = array();//unique set
|
||||||
|
protected $shared_string_count = 0;//count of non-unique references to the unique set
|
||||||
|
protected $temp_files = array();
|
||||||
|
|
||||||
|
public function __construct(){}
|
||||||
|
public function setAuthor($author='') { $this->author=$author; }
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if (!empty($this->temp_files)) {
|
||||||
|
foreach($this->temp_files as $temp_file) {
|
||||||
|
@unlink($temp_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tempFilename()
|
||||||
|
{
|
||||||
|
$filename = tempnam("/tmp", "xlsx_writer_");
|
||||||
|
$this->temp_files[] = $filename;
|
||||||
|
return $filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writeToStdOut()
|
||||||
|
{
|
||||||
|
$temp_file = $this->tempFilename();
|
||||||
|
self::writeToFile($temp_file);
|
||||||
|
readfile($temp_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writeToString()
|
||||||
|
{
|
||||||
|
$temp_file = $this->tempFilename();
|
||||||
|
self::writeToFile($temp_file);
|
||||||
|
$string = file_get_contents($temp_file);
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writeToFile($filename)
|
||||||
|
{
|
||||||
|
@unlink($filename);//if the zip already exists, overwrite it
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
if (empty($this->sheets_meta)) { self::log("Error in ".__CLASS__."::".__FUNCTION__.", no worksheets defined."); return; }
|
||||||
|
if (!$zip->open($filename, ZipArchive::CREATE)) { self::log("Error in ".__CLASS__."::".__FUNCTION__.", unable to create zip."); return; }
|
||||||
|
|
||||||
|
$zip->addEmptyDir("docProps/");
|
||||||
|
$zip->addFromString("docProps/app.xml" , self::buildAppXML() );
|
||||||
|
$zip->addFromString("docProps/core.xml", self::buildCoreXML());
|
||||||
|
|
||||||
|
$zip->addEmptyDir("_rels/");
|
||||||
|
$zip->addFromString("_rels/.rels", self::buildRelationshipsXML());
|
||||||
|
|
||||||
|
$zip->addEmptyDir("xl/worksheets/");
|
||||||
|
foreach($this->sheets_meta as $sheet_meta) {
|
||||||
|
$zip->addFile($sheet_meta['filename'], "xl/worksheets/".$sheet_meta['xmlname'] );
|
||||||
|
}
|
||||||
|
if (!empty($this->shared_strings)) {
|
||||||
|
$zip->addFile($this->writeSharedStringsXML(), "xl/sharedStrings.xml" ); //$zip->addFromString("xl/sharedStrings.xml", self::buildSharedStringsXML() );
|
||||||
|
}
|
||||||
|
$zip->addFromString("xl/workbook.xml" , self::buildWorkbookXML() );
|
||||||
|
$zip->addFile($this->writeStylesXML(), "xl/styles.xml" ); //$zip->addFromString("xl/styles.xml" , self::buildStylesXML() );
|
||||||
|
$zip->addFromString("[Content_Types].xml" , self::buildContentTypesXML() );
|
||||||
|
|
||||||
|
$zip->addEmptyDir("xl/_rels/");
|
||||||
|
$zip->addFromString("xl/_rels/workbook.xml.rels", self::buildWorkbookRelsXML() );
|
||||||
|
$zip->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function writeSheet(array $data, $sheet_name='', array $header_types=array() )
|
||||||
|
{
|
||||||
|
$data = empty($data) ? array( array('') ) : $data;
|
||||||
|
|
||||||
|
$sheet_filename = $this->tempFilename();
|
||||||
|
$sheet_default = 'Sheet'.(count($this->sheets_meta)+1);
|
||||||
|
$sheet_name = !empty($sheet_name) ? $sheet_name : $sheet_default;
|
||||||
|
$this->sheets_meta[] = array('filename'=>$sheet_filename, 'sheetname'=>$sheet_name ,'xmlname'=>strtolower($sheet_default).".xml" );
|
||||||
|
|
||||||
|
$header_offset = empty($header_types) ? 0 : 1;
|
||||||
|
$row_count = count($data) + $header_offset;
|
||||||
|
$column_count = count($data[self::array_first_key($data)]);
|
||||||
|
$max_cell = self::xlsCell( $row_count-1, $column_count-1 );
|
||||||
|
|
||||||
|
$tabselected = count($this->sheets_meta)==1 ? 'true' : 'false';//only first sheet is selected
|
||||||
|
$cell_formats_arr = empty($header_types) ? array_fill(0, $column_count, 'string') : array_values($header_types);
|
||||||
|
$header_row = empty($header_types) ? array() : array_keys($header_types);
|
||||||
|
|
||||||
|
$fd = fopen($sheet_filename, "w+");
|
||||||
|
if ($fd===false) { self::log("write failed in ".__CLASS__."::".__FUNCTION__."."); return; }
|
||||||
|
|
||||||
|
fwrite($fd,'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
|
||||||
|
fwrite($fd,'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
|
||||||
|
fwrite($fd, '<sheetPr filterMode="false">');
|
||||||
|
fwrite($fd, '<pageSetUpPr fitToPage="false"/>');
|
||||||
|
fwrite($fd, '</sheetPr>');
|
||||||
|
fwrite($fd, '<dimension ref="A1:'.$max_cell.'"/>');
|
||||||
|
fwrite($fd, '<sheetViews>');
|
||||||
|
fwrite($fd, '<sheetView colorId="64" defaultGridColor="true" rightToLeft="false" showFormulas="false" showGridLines="true" showOutlineSymbols="true" showRowColHeaders="true" showZeros="true" tabSelected="'.$tabselected.'" topLeftCell="A1" view="normal" windowProtection="false" workbookViewId="0" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100">');
|
||||||
|
fwrite($fd, '<selection activeCell="A1" activeCellId="0" pane="topLeft" sqref="A1"/>');
|
||||||
|
fwrite($fd, '</sheetView>');
|
||||||
|
fwrite($fd, '</sheetViews>');
|
||||||
|
fwrite($fd, '<cols>');
|
||||||
|
fwrite($fd, '<col collapsed="false" hidden="false" max="1025" min="1" style="0" width="19"/>');
|
||||||
|
fwrite($fd, '</cols>');
|
||||||
|
fwrite($fd, '<sheetData>');
|
||||||
|
if (!empty($header_row))
|
||||||
|
{
|
||||||
|
fwrite($fd, '<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="'.(1).'">');
|
||||||
|
foreach($header_row as $k=>$v)
|
||||||
|
{
|
||||||
|
$this->writeCell($fd, 0, $k, $v, $cell_format='string');
|
||||||
|
}
|
||||||
|
fwrite($fd, '</row>');
|
||||||
|
}
|
||||||
|
foreach($data as $i=>$row)
|
||||||
|
{
|
||||||
|
fwrite($fd, '<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="'.($i+$header_offset+1).'">');
|
||||||
|
foreach($row as $k=>$v)
|
||||||
|
{
|
||||||
|
$this->writeCell($fd, $i+$header_offset, $k, $v, $cell_formats_arr[$k]);
|
||||||
|
}
|
||||||
|
fwrite($fd, '</row>');
|
||||||
|
}
|
||||||
|
fwrite($fd, '</sheetData>');
|
||||||
|
fwrite($fd, '<printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"/>');
|
||||||
|
fwrite($fd, '<pageMargins left="0.5" right="0.5" top="1.0" bottom="1.0" header="0.5" footer="0.5"/>');
|
||||||
|
fwrite($fd, '<pageSetup blackAndWhite="false" cellComments="none" copies="1" draft="false" firstPageNumber="1" fitToHeight="1" fitToWidth="1" horizontalDpi="300" orientation="portrait" pageOrder="downThenOver" paperSize="1" scale="100" useFirstPageNumber="true" usePrinterDefaults="false" verticalDpi="300"/>');
|
||||||
|
fwrite($fd, '<headerFooter differentFirst="false" differentOddEven="false">');
|
||||||
|
fwrite($fd, '<oddHeader>&C&"Times New Roman,Regular"&12&A</oddHeader>');
|
||||||
|
fwrite($fd, '<oddFooter>&C&"Times New Roman,Regular"&12Page &P</oddFooter>');
|
||||||
|
fwrite($fd, '</headerFooter>');
|
||||||
|
fwrite($fd,'</worksheet>');
|
||||||
|
fclose($fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function writeCell($fd, $row_number, $column_number, $value, $cell_format)
|
||||||
|
{
|
||||||
|
static $styles = array('money'=>1,'dollar'=>1,'datetime'=>2,'date'=>3,'string'=>0);
|
||||||
|
$cell = self::xlsCell($row_number, $column_number);
|
||||||
|
$s = isset($styles[$cell_format]) ? $styles[$cell_format] : '0';
|
||||||
|
|
||||||
|
if (is_numeric($value)) {
|
||||||
|
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.($value*1).'</v></c>');//int,float, etc
|
||||||
|
} else if ($cell_format=='date') {
|
||||||
|
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.intval(self::convert_date_time($value)).'</v></c>');
|
||||||
|
} else if ($cell_format=='datetime') {
|
||||||
|
if ($value === '') {
|
||||||
|
fwrite($fd,'<c r="'.$cell.'" s="0"/>');
|
||||||
|
} else {
|
||||||
|
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.self::convert_date_time($value).'</v></c>');
|
||||||
|
}
|
||||||
|
} else if ($value==''){
|
||||||
|
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'"/>');
|
||||||
|
} else if ($value{0}=='='){
|
||||||
|
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="s"><f>'.self::xmlspecialchars($value).'</f></c>');
|
||||||
|
} else if ($value!==''){
|
||||||
|
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="s"><v>'.self::xmlspecialchars($this->setSharedString($value)).'</v></c>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function writeStylesXML()
|
||||||
|
{
|
||||||
|
$tempfile = $this->tempFilename();
|
||||||
|
$fd = fopen($tempfile, "w+");
|
||||||
|
if ($fd===false) { self::log("write failed in ".__CLASS__."::".__FUNCTION__."."); return; }
|
||||||
|
fwrite($fd, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
|
||||||
|
fwrite($fd, '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">');
|
||||||
|
fwrite($fd, '<numFmts count="4">');
|
||||||
|
fwrite($fd, '<numFmt formatCode="GENERAL" numFmtId="164"/>');
|
||||||
|
fwrite($fd, '<numFmt formatCode="[$$-1009]#,##0.00;[RED]\-[$$-1009]#,##0.00" numFmtId="165"/>');
|
||||||
|
fwrite($fd, '<numFmt formatCode="YYYY-MM-DD\ HH:MM:SS" numFmtId="166"/>');
|
||||||
|
fwrite($fd, '<numFmt formatCode="YYYY-MM-DD" numFmtId="167"/>');
|
||||||
|
fwrite($fd, '</numFmts>');
|
||||||
|
fwrite($fd, '<fonts count="4">');
|
||||||
|
fwrite($fd, '<font><name val="Arial"/><charset val="1"/><family val="2"/><sz val="10"/></font>');
|
||||||
|
fwrite($fd, '<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
|
||||||
|
fwrite($fd, '<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
|
||||||
|
fwrite($fd, '<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
|
||||||
|
fwrite($fd, '</fonts>');
|
||||||
|
fwrite($fd, '<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>');
|
||||||
|
fwrite($fd, '<borders count="1"><border diagonalDown="false" diagonalUp="false"><left/><right/><top/><bottom/><diagonal/></border></borders>');
|
||||||
|
fwrite($fd, '<cellStyleXfs count="15">');
|
||||||
|
fwrite($fd, '<xf applyAlignment="true" applyBorder="true" applyFont="true" applyProtection="true" borderId="0" fillId="0" fontId="0" numFmtId="164">');
|
||||||
|
fwrite($fd, '<alignment horizontal="general" indent="0" shrinkToFit="false" textRotation="0" vertical="bottom" wrapText="false"/>');
|
||||||
|
fwrite($fd, '<protection hidden="false" locked="true"/>');
|
||||||
|
fwrite($fd, '</xf>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
|
||||||
|
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="43"/>');
|
||||||
|
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="41"/>');
|
||||||
|
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="44"/>');
|
||||||
|
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="42"/>');
|
||||||
|
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="9"/>');
|
||||||
|
fwrite($fd, '</cellStyleXfs>');
|
||||||
|
fwrite($fd, '<cellXfs count="4">');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="164" xfId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="165" xfId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="166" xfId="0"/>');
|
||||||
|
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="167" xfId="0"/>');
|
||||||
|
fwrite($fd, '</cellXfs>');
|
||||||
|
fwrite($fd, '<cellStyles count="1">');
|
||||||
|
fwrite($fd, '<cellStyle builtinId="0" customBuiltin="false" name="Normal" xfId="0"/>');
|
||||||
|
//fwrite($fd, '<cellStyle builtinId="3" customBuiltin="false" name="Comma" xfId="15"/>');
|
||||||
|
//fwrite($fd, '<cellStyle builtinId="6" customBuiltin="false" name="Comma [0]" xfId="16"/>');
|
||||||
|
//fwrite($fd, '<cellStyle builtinId="4" customBuiltin="false" name="Currency" xfId="17"/>');
|
||||||
|
//fwrite($fd, '<cellStyle builtinId="7" customBuiltin="false" name="Currency [0]" xfId="18"/>');
|
||||||
|
//fwrite($fd, '<cellStyle builtinId="5" customBuiltin="false" name="Percent" xfId="19"/>');
|
||||||
|
fwrite($fd, '</cellStyles>');
|
||||||
|
fwrite($fd, '</styleSheet>');
|
||||||
|
fclose($fd);
|
||||||
|
return $tempfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setSharedString($v)
|
||||||
|
{
|
||||||
|
// Strip control characters which Excel does not seem to like...
|
||||||
|
$v = preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F]/u', '', $v);
|
||||||
|
if (isset($this->shared_strings[$v]))
|
||||||
|
{
|
||||||
|
$string_value = $this->shared_strings[$v];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$string_value = count($this->shared_strings);
|
||||||
|
$this->shared_strings[$v] = $string_value;
|
||||||
|
}
|
||||||
|
$this->shared_string_count++;//non-unique count
|
||||||
|
return $string_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function writeSharedStringsXML()
|
||||||
|
{
|
||||||
|
$tempfile = $this->tempFilename();
|
||||||
|
$fd = fopen($tempfile, "w+");
|
||||||
|
if ($fd===false) { self::log("write failed in ".__CLASS__."::".__FUNCTION__."."); return; }
|
||||||
|
|
||||||
|
fwrite($fd,'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
|
||||||
|
fwrite($fd,'<sst count="'.($this->shared_string_count).'" uniqueCount="'.count($this->shared_strings).'" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">');
|
||||||
|
foreach($this->shared_strings as $s=>$c)
|
||||||
|
{
|
||||||
|
fwrite($fd,'<si><t>'.self::xmlspecialchars($s).'</t></si>');
|
||||||
|
}
|
||||||
|
fwrite($fd, '</sst>');
|
||||||
|
fclose($fd);
|
||||||
|
return $tempfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildAppXML()
|
||||||
|
{
|
||||||
|
$app_xml="";
|
||||||
|
$app_xml.='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
|
||||||
|
$app_xml.='<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"><TotalTime>0</TotalTime></Properties>';
|
||||||
|
return $app_xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildCoreXML()
|
||||||
|
{
|
||||||
|
$core_xml="";
|
||||||
|
$core_xml.='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
|
||||||
|
$core_xml.='<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">';
|
||||||
|
$core_xml.='<dcterms:created xsi:type="dcterms:W3CDTF">'.date("Y-m-d\TH:i:s.00\Z").'</dcterms:created>';//$date_time = '2013-07-25T15:54:37.00Z';
|
||||||
|
$core_xml.='<dc:creator>'.self::xmlspecialchars($this->author).'</dc:creator>';
|
||||||
|
$core_xml.='<cp:revision>0</cp:revision>';
|
||||||
|
$core_xml.='</cp:coreProperties>';
|
||||||
|
return $core_xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildRelationshipsXML()
|
||||||
|
{
|
||||||
|
$rels_xml="";
|
||||||
|
$rels_xml.='<?xml version="1.0" encoding="UTF-8"?>'."\n";
|
||||||
|
$rels_xml.='<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
|
||||||
|
$rels_xml.='<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>';
|
||||||
|
$rels_xml.='<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>';
|
||||||
|
$rels_xml.='<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>';
|
||||||
|
$rels_xml.="\n";
|
||||||
|
$rels_xml.='</Relationships>';
|
||||||
|
return $rels_xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildWorkbookXML()
|
||||||
|
{
|
||||||
|
$workbook_xml="";
|
||||||
|
$workbook_xml.='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
|
||||||
|
$workbook_xml.='<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">';
|
||||||
|
$workbook_xml.='<fileVersion appName="Calc"/><workbookPr backupFile="false" showObjects="all" date1904="false"/><workbookProtection/>';
|
||||||
|
$workbook_xml.='<bookViews><workbookView activeTab="0" firstSheet="0" showHorizontalScroll="true" showSheetTabs="true" showVerticalScroll="true" tabRatio="212" windowHeight="8192" windowWidth="16384" xWindow="0" yWindow="0"/></bookViews>';
|
||||||
|
$workbook_xml.='<sheets>';
|
||||||
|
foreach($this->sheets_meta as $i=>$sheet_meta) {
|
||||||
|
$workbook_xml.='<sheet name="'.self::xmlspecialchars($sheet_meta['sheetname']).'" sheetId="'.($i+1).'" state="visible" r:id="rId'.($i+2).'"/>';
|
||||||
|
}
|
||||||
|
$workbook_xml.='</sheets>';
|
||||||
|
$workbook_xml.='<calcPr iterateCount="100" refMode="A1" iterate="false" iterateDelta="0.001"/></workbook>';
|
||||||
|
return $workbook_xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildWorkbookRelsXML()
|
||||||
|
{
|
||||||
|
$wkbkrels_xml="";
|
||||||
|
$wkbkrels_xml.='<?xml version="1.0" encoding="UTF-8"?>'."\n";
|
||||||
|
$wkbkrels_xml.='<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
|
||||||
|
$wkbkrels_xml.='<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>';
|
||||||
|
foreach($this->sheets_meta as $i=>$sheet_meta) {
|
||||||
|
$wkbkrels_xml.='<Relationship Id="rId'.($i+2).'" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/'.($sheet_meta['xmlname']).'"/>';
|
||||||
|
}
|
||||||
|
if (!empty($this->shared_strings)) {
|
||||||
|
$wkbkrels_xml.='<Relationship Id="rId'.(count($this->sheets_meta)+2).'" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>';
|
||||||
|
}
|
||||||
|
$wkbkrels_xml.="\n";
|
||||||
|
$wkbkrels_xml.='</Relationships>';
|
||||||
|
return $wkbkrels_xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildContentTypesXML()
|
||||||
|
{
|
||||||
|
$content_types_xml="";
|
||||||
|
$content_types_xml.='<?xml version="1.0" encoding="UTF-8"?>'."\n";
|
||||||
|
$content_types_xml.='<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">';
|
||||||
|
$content_types_xml.='<Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
|
||||||
|
$content_types_xml.='<Override PartName="/xl/_rels/workbook.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
|
||||||
|
foreach($this->sheets_meta as $i=>$sheet_meta) {
|
||||||
|
$content_types_xml.='<Override PartName="/xl/worksheets/'.($sheet_meta['xmlname']).'" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>';
|
||||||
|
}
|
||||||
|
if (!empty($this->shared_strings)) {
|
||||||
|
$content_types_xml.='<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>';
|
||||||
|
}
|
||||||
|
$content_types_xml.='<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>';
|
||||||
|
$content_types_xml.='<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>';
|
||||||
|
$content_types_xml.='<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>';
|
||||||
|
$content_types_xml.='<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>';
|
||||||
|
$content_types_xml.="\n";
|
||||||
|
$content_types_xml.='</Types>';
|
||||||
|
return $content_types_xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
* @param $row_number int, zero based
|
||||||
|
* @param $column_number int, zero based
|
||||||
|
* @return Cell label/coordinates, ex: A1, C3, AA42
|
||||||
|
* */
|
||||||
|
public static function xlsCell($row_number, $column_number)
|
||||||
|
{
|
||||||
|
$n = $column_number;
|
||||||
|
for($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
|
||||||
|
$r = chr($n%26 + 0x41) . $r;
|
||||||
|
}
|
||||||
|
return $r . ($row_number+1);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
public static function log($string)
|
||||||
|
{
|
||||||
|
file_put_contents("php://stderr", date("Y-m-d H:i:s:").rtrim(is_array($string) ? json_encode($string) : $string)."\n");
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
public static function xmlspecialchars($val)
|
||||||
|
{
|
||||||
|
return str_replace("'", "'", htmlspecialchars($val));
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
public static function array_first_key(array $arr)
|
||||||
|
{
|
||||||
|
reset($arr);
|
||||||
|
$first_key = key($arr);
|
||||||
|
return $first_key;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
public static function convert_date_time($date_input) //thanks to Excel::Writer::XLSX::Worksheet.pm (perl)
|
||||||
|
{
|
||||||
|
$days = 0; # Number of days since epoch
|
||||||
|
$seconds = 0; # Time expressed as fraction of 24h hours in seconds
|
||||||
|
$year=$month=$day=0;
|
||||||
|
$hour=$min =$sec=0;
|
||||||
|
|
||||||
|
$date_time = $date_input;
|
||||||
|
if (preg_match("/(\d{4})\-(\d{2})\-(\d{2})/", $date_time, $matches))
|
||||||
|
{
|
||||||
|
list($junk,$year,$month,$day) = $matches;
|
||||||
|
}
|
||||||
|
if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $date_time, $matches))
|
||||||
|
{
|
||||||
|
list($junk,$hour,$min,$sec) = $matches;
|
||||||
|
$seconds = ( $hour * 60 * 60 + $min * 60 + $sec ) / ( 24 * 60 * 60 );
|
||||||
|
}
|
||||||
|
|
||||||
|
//using 1900 as epoch, not 1904, ignoring 1904 special case
|
||||||
|
|
||||||
|
# Special cases for Excel.
|
||||||
|
if ("$year-$month-$day"=='1899-12-31') return $seconds ; # Excel 1900 epoch
|
||||||
|
if ("$year-$month-$day"=='1900-01-00') return $seconds ; # Excel 1900 epoch
|
||||||
|
if ("$year-$month-$day"=='1900-02-29') return 60 + $seconds ; # Excel false leapday
|
||||||
|
|
||||||
|
# We calculate the date by calculating the number of days since the epoch
|
||||||
|
# and adjust for the number of leap days. We calculate the number of leap
|
||||||
|
# days by normalising the year in relation to the epoch. Thus the year 2000
|
||||||
|
# becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
|
||||||
|
$epoch = 1900;
|
||||||
|
$offset = 0;
|
||||||
|
$norm = 300;
|
||||||
|
$range = $year - $epoch;
|
||||||
|
|
||||||
|
# Set month days and check for leap year.
|
||||||
|
$leap = (($year % 400 == 0) || (($year % 4 == 0) && ($year % 100)) ) ? 1 : 0;
|
||||||
|
$mdays = array( 31, ($leap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
|
||||||
|
|
||||||
|
# Some boundary checks
|
||||||
|
if($year < $epoch || $year > 9999) return 0;
|
||||||
|
if($month < 1 || $month > 12) return 0;
|
||||||
|
if($day < 1 || $day > $mdays[ $month - 1 ]) return 0;
|
||||||
|
|
||||||
|
# Accumulate the number of days since the epoch.
|
||||||
|
$days = $day; # Add days for current month
|
||||||
|
$days += array_sum( array_slice($mdays, 0, $month-1 ) ); # Add days for past months
|
||||||
|
$days += $range * 365; # Add days for past years
|
||||||
|
$days += intval( ( $range ) / 4 ); # Add leapdays
|
||||||
|
$days -= intval( ( $range + $offset ) / 100 ); # Subtract 100 year leapdays
|
||||||
|
$days += intval( ( $range + $offset + $norm ) / 400 ); # Add 400 year leapdays
|
||||||
|
$days -= $leap; # Already counted above
|
||||||
|
|
||||||
|
# Adjust for Excel erroneously treating 1900 as a leap year.
|
||||||
|
if ($days > 59) { $days++;}
|
||||||
|
|
||||||
|
return $days + $seconds;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -777,6 +777,22 @@ class Config
|
|||||||
'source_of_value' => '',
|
'source_of_value' => '',
|
||||||
'show_in_conf_sample' => false,
|
'show_in_conf_sample' => false,
|
||||||
),
|
),
|
||||||
|
'xlsx_exporter_cleanup_old_files_delay' => array(
|
||||||
|
'type' => 'int',
|
||||||
|
'description' => 'Delay (in seconds) for which to let the exported XLSX files on the server so that the user who initiated the export can download the result',
|
||||||
|
'default' => 86400,
|
||||||
|
'value' => '',
|
||||||
|
'source_of_value' => '',
|
||||||
|
'show_in_conf_sample' => false,
|
||||||
|
),
|
||||||
|
'xlsx_exporter_memory_limit' => array(
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => 'Memory limit to use when (interactively) exporting data to Excel',
|
||||||
|
'default' => '2048M', // Huuuuuuge 2GB!
|
||||||
|
'value' => '',
|
||||||
|
'source_of_value' => '',
|
||||||
|
'show_in_conf_sample' => false,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
public function IsProperty($sPropCode)
|
public function IsProperty($sPropCode)
|
||||||
|
|||||||
@@ -1217,5 +1217,16 @@ When associated with a trigger, each action is given an "order" number, specifyi
|
|||||||
'UI:About:Support' => 'Support information',
|
'UI:About:Support' => 'Support information',
|
||||||
'UI:About:Licenses' => 'Licenses',
|
'UI:About:Licenses' => 'Licenses',
|
||||||
'UI:About:Modules' => 'Installed modules',
|
'UI:About:Modules' => 'Installed modules',
|
||||||
|
|
||||||
|
'ExcelExporter:ExportMenu' => 'Excel Export...',
|
||||||
|
'ExcelExporter:ExportDialogTitle' => 'Excel Export',
|
||||||
|
'ExcelExporter:ExportButton' => 'Export',
|
||||||
|
'ExcelExporter:DownloadButton' => 'Download %1$s',
|
||||||
|
'ExcelExporter:RetrievingData' => 'Retrieving data...',
|
||||||
|
'ExcelExporter:BuildingExcelFile' => 'Building the Excel file...',
|
||||||
|
'ExcelExporter:Done' => 'Done.',
|
||||||
|
'ExcelExport:AutoDownload' => 'Start the download automatically when the export is ready',
|
||||||
|
'ExcelExport:PreparingExport' => 'Preparing the export...',
|
||||||
|
'ExcelExport:Statistics' => 'Statistics',
|
||||||
));
|
));
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -1057,5 +1057,16 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
|
|||||||
'UI:About:Support' => 'Informations pour le support',
|
'UI:About:Support' => 'Informations pour le support',
|
||||||
'UI:About:Licenses' => 'Licences',
|
'UI:About:Licenses' => 'Licences',
|
||||||
'UI:About:Modules' => 'Modules installés',
|
'UI:About:Modules' => 'Modules installés',
|
||||||
|
|
||||||
|
'ExcelExporter:ExportMenu' => 'Exporter pour Excel...',
|
||||||
|
'ExcelExporter:ExportDialogTitle' => 'Export au format Excel',
|
||||||
|
'ExcelExporter:ExportButton' => 'Exporter',
|
||||||
|
'ExcelExporter:DownloadButton' => 'Télécharger %1$s',
|
||||||
|
'ExcelExporter:RetrievingData' => 'Récupération des données...',
|
||||||
|
'ExcelExporter:BuildingExcelFile' => 'Construction du fichier Excel...',
|
||||||
|
'ExcelExporter:Done' => 'Terminé.',
|
||||||
|
'ExcelExport:AutoDownload' => 'Téléchargement automatique dès que le fichier est prêt',
|
||||||
|
'ExcelExport:PreparingExport' => 'Préparation de l\'export...',
|
||||||
|
'ExcelExport:Statistics' => 'Statistiques',
|
||||||
));
|
));
|
||||||
?>
|
?>
|
||||||
|
|||||||
BIN
images/xlsx.png
Normal file
BIN
images/xlsx.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
185
js/xlsx-export.js
Normal file
185
js/xlsx-export.js
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
// jQuery UI style "widget" for managing the "xlsx-exporter"
|
||||||
|
$(function()
|
||||||
|
{
|
||||||
|
// the widget definition, where "itop" is the namespace,
|
||||||
|
// "xlsxexporter" the widget name
|
||||||
|
$.widget( "itop.xlsxexporter",
|
||||||
|
{
|
||||||
|
// default options
|
||||||
|
options:
|
||||||
|
{
|
||||||
|
filter: '',
|
||||||
|
ajax_page_url: '',
|
||||||
|
labels: {dialog_title: 'Excel Export', export_button: 'Export', cancel_button: 'Cancel', download_button: 'Download', complete: 'Complete', cancelled: 'Cancelled' }
|
||||||
|
},
|
||||||
|
|
||||||
|
// the constructor
|
||||||
|
_create: function()
|
||||||
|
{
|
||||||
|
this.element
|
||||||
|
.addClass('itop-xlsxexporter');
|
||||||
|
|
||||||
|
this.sToken = null;
|
||||||
|
this.ajaxCall = null;
|
||||||
|
this.oProgressBar = $('.progress-bar', this.element);
|
||||||
|
this.oStatusMessage = $('.status-message', this.element);
|
||||||
|
$('.progress', this.element).hide();
|
||||||
|
$('.statistics', this.element).hide();
|
||||||
|
|
||||||
|
var me = this;
|
||||||
|
|
||||||
|
this.element.dialog({
|
||||||
|
title: this.options.labels.dialog_title,
|
||||||
|
modal: true,
|
||||||
|
width: 500,
|
||||||
|
height: 300,
|
||||||
|
buttons: [
|
||||||
|
{ text: this.options.labels.export_button, 'class': 'export-button', click: function() {
|
||||||
|
me._start();
|
||||||
|
} },
|
||||||
|
{ text: this.options.labels.cancel_button, 'class': 'cancel-button', click: function() {
|
||||||
|
$(this).dialog( "close" );
|
||||||
|
} },
|
||||||
|
],
|
||||||
|
close: function() { me._abort(); $(this).remove(); }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// events bound via _bind are removed automatically
|
||||||
|
// revert other modifications here
|
||||||
|
destroy: function()
|
||||||
|
{
|
||||||
|
this.element
|
||||||
|
.removeClass('itop-xlsxexporter');
|
||||||
|
},
|
||||||
|
// _setOptions is called with a hash of all options that are changing
|
||||||
|
_setOptions: function()
|
||||||
|
{
|
||||||
|
this._superApply(arguments);
|
||||||
|
},
|
||||||
|
// _setOption is called for each individual option that is changing
|
||||||
|
_setOption: function( key, value )
|
||||||
|
{
|
||||||
|
this._superApply(arguments);
|
||||||
|
},
|
||||||
|
_start: function()
|
||||||
|
{
|
||||||
|
var me = this;
|
||||||
|
$('.export-options', this.element).hide();
|
||||||
|
$('.progress', this.element).show();
|
||||||
|
var bAdvanced = $('#export-advanced-mode').prop('checked');
|
||||||
|
this.bAutoDownload = $('#export-auto-download').prop('checked');
|
||||||
|
$('.export-button', this.element.parent()).button('disable');
|
||||||
|
|
||||||
|
this.oProgressBar.progressbar({
|
||||||
|
value: 0,
|
||||||
|
change: function() {
|
||||||
|
var progressLabel = $('.progress-label', me.element);
|
||||||
|
progressLabel.text( $(this).progressbar( "value" ) + "%" );
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
var progressLabel = $('.progress-label', me.element);
|
||||||
|
progressLabel.text( me.options.labels['complete'] );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//TODO disable the "export" button
|
||||||
|
this.ajaxCall = $.post(this.options.ajax_page_url, {filter: this.options.filter, operation: 'xlsx_start', advanced: bAdvanced}, function(data) {
|
||||||
|
this.ajaxCall = null;
|
||||||
|
if (data && data.status == 'ok')
|
||||||
|
{
|
||||||
|
me.sToken = data.token;
|
||||||
|
me._run();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
me.oStatusMessage.html('Unexpected error (operation=xlsx_start).');
|
||||||
|
me.oProgressBar.progressbar({value: 100});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
me.oStatusMessage.html(data.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 'json');
|
||||||
|
|
||||||
|
},
|
||||||
|
_abort: function()
|
||||||
|
{
|
||||||
|
$('.cancel-button', this.element.parent()).button('disable');
|
||||||
|
this.oStatusMessage.html(this.options.labels['cancelled']);
|
||||||
|
this.oProgressBar.progressbar({value: 100});
|
||||||
|
if (this.sToken != null)
|
||||||
|
{
|
||||||
|
// Cancel the operation in progress... or cleanup a completed export
|
||||||
|
// TODO
|
||||||
|
if (this.ajaxCall)
|
||||||
|
{
|
||||||
|
this.ajaxCall.abort();
|
||||||
|
this.ajaxClass = null;
|
||||||
|
}
|
||||||
|
var me = this;
|
||||||
|
$.post(this.options.ajax_page_url, {token: this.sToken, operation: 'xlsx_abort'}, function(data) {
|
||||||
|
me.sToken = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_run: function()
|
||||||
|
{
|
||||||
|
var me = this;
|
||||||
|
this.ajaxCall = $.post(this.options.ajax_page_url, {token: this.sToken, operation: 'xlsx_run'}, function(data) {
|
||||||
|
this.ajaxCall = null;
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
me.oStatusMessage.html('Unexpected error (operation=xlsx_run).');
|
||||||
|
me.oProgressBar.progressbar({value: 100});
|
||||||
|
}
|
||||||
|
else if (data.status == 'error')
|
||||||
|
{
|
||||||
|
me.oStatusMessage.html(data.message);
|
||||||
|
me.oProgressBar.progressbar({value: 100});
|
||||||
|
}
|
||||||
|
else if (data.status == 'done')
|
||||||
|
{
|
||||||
|
me.oStatusMessage.html(data.message);
|
||||||
|
me.oProgressBar.progressbar({value: 100});
|
||||||
|
$('.stats-data', this.element).html(data.statistics);
|
||||||
|
me._on_completion();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// continue running the export in the background
|
||||||
|
me.oStatusMessage.html(data.message);
|
||||||
|
me.oProgressBar.progressbar({value: data.percentage});
|
||||||
|
me._run();
|
||||||
|
}
|
||||||
|
}, 'json');
|
||||||
|
},
|
||||||
|
_on_completion: function()
|
||||||
|
{
|
||||||
|
var me = this;
|
||||||
|
$('.progress', this.element).html('<form class="download-form" method="post" action="'+this.options.ajax_page_url+'"><input type="hidden" name="operation" value="xlsx_download"/><input type="hidden" name="token" value="'+this.sToken+'"/><button type="submit">'+this.options.labels['download_button']+'</button></form>');
|
||||||
|
$('.download-form button', this.element).button().click(function() { me.sToken = null; window.setTimeout(function() { me.element.dialog('close'); }, 100); return true;});
|
||||||
|
if (this.bAutoDownload)
|
||||||
|
{
|
||||||
|
me.sToken = null;
|
||||||
|
$('.download-form').submit();
|
||||||
|
this.element.dialog('close');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('.statistics', this.element).show();
|
||||||
|
$('.statistics .stats-toggle', this.element).click(function() { $(this).toggleClass('closed'); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function XlsxExportDialog(sFilter)
|
||||||
|
{
|
||||||
|
var sUrl = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php';
|
||||||
|
$.post(sUrl, {operation: 'xlsx_export_dialog', filter: sFilter}, function(data) {
|
||||||
|
$('body').append(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -418,6 +418,7 @@ EOF
|
|||||||
|
|
||||||
case 'get_csv_template':
|
case 'get_csv_template':
|
||||||
$sClassName = utils::ReadParam('class_name');
|
$sClassName = utils::ReadParam('class_name');
|
||||||
|
$sFormat = utils::ReadParam('format', 'csv');
|
||||||
if (MetaModel::IsValidClass($sClassName))
|
if (MetaModel::IsValidClass($sClassName))
|
||||||
{
|
{
|
||||||
$oSearch = new DBObjectSearch($sClassName);
|
$oSearch = new DBObjectSearch($sClassName);
|
||||||
@@ -429,17 +430,37 @@ EOF
|
|||||||
$sDisposition = utils::ReadParam('disposition', 'inline');
|
$sDisposition = utils::ReadParam('disposition', 'inline');
|
||||||
if ($sDisposition == 'attachment')
|
if ($sDisposition == 'attachment')
|
||||||
{
|
{
|
||||||
|
switch($sFormat)
|
||||||
|
{
|
||||||
|
case 'xlsx':
|
||||||
|
$oPage = new ajax_page("");
|
||||||
|
$oPage->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||||
|
$oPage->SetContentDisposition('attachment', $sClassDisplayName.'.xlsx');
|
||||||
|
require_once(APPROOT.'/application/excelexporter.class.inc.php');
|
||||||
|
$writer = new XLSXWriter();
|
||||||
|
$writer->setAuthor(UserRights::GetUserFriendlyName());
|
||||||
|
$aHeaders = array( 0 => explode(',', $sResult)); // comma is the default separator
|
||||||
|
$writer->writeSheet($aHeaders, $sClassDisplayName, array());
|
||||||
|
$oPage->add($writer->writeToString());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'csv':
|
||||||
|
default:
|
||||||
$oPage = new CSVPage("");
|
$oPage = new CSVPage("");
|
||||||
$oPage->add_header("Content-type: text/csv; charset=utf-8");
|
$oPage->add_header("Content-type: text/csv; charset=utf-8");
|
||||||
$oPage->add_header("Content-disposition: attachment; filename=\"{$sClassDisplayName}.csv\"");
|
$oPage->add_header("Content-disposition: attachment; filename=\"{$sClassDisplayName}.csv\"");
|
||||||
$oPage->no_cache();
|
$oPage->no_cache();
|
||||||
$oPage->add($sResult);
|
$oPage->add($sResult);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$oPage = new ajax_page("");
|
$oPage = new ajax_page("");
|
||||||
$oPage->no_cache();
|
$oPage->no_cache();
|
||||||
$oPage->add('<p style="text-align:center"><a style="text-decoration:none" href="'.utils::GetAbsoluteUrlAppRoot().'pages/ajax.csvimport.php?operation=get_csv_template&disposition=attachment&class_name='.$sClassName.'"><img border="0" src="../images/csv.png"><br/>'.$sClassDisplayName.'.csv</a></p>');
|
$oPage->add('<p style="text-align:center">');
|
||||||
|
$oPage->add('<div style="display:inline-block;margin:0.5em;"><a style="text-decoration:none" href="'.utils::GetAbsoluteUrlAppRoot().'pages/ajax.csvimport.php?operation=get_csv_template&disposition=attachment&class_name='.$sClassName.'"><img border="0" src="../images/csv.png"><br/>'.$sClassDisplayName.'.csv</a></div>');
|
||||||
|
$oPage->add('<div style="display:inline-block;margin:0.5em;"><a style="text-decoration:none" href="'.utils::GetAbsoluteUrlAppRoot().'pages/ajax.csvimport.php?operation=get_csv_template&disposition=attachment&format=xlsx&class_name='.$sClassName.'"><img border="0" src="../images/xlsx.png"><br/>'.$sClassDisplayName.'.xlsx</a></div>');
|
||||||
|
$oPage->add('</p>');
|
||||||
$oPage->add('<p><textarea rows="5" cols="100">'.$sResult.'</textarea></p>');
|
$oPage->add('<p><textarea rows="5" cols="100">'.$sResult.'</textarea></p>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ require_once(APPROOT.'/application/wizardhelper.class.inc.php');
|
|||||||
require_once(APPROOT.'/application/ui.linkswidget.class.inc.php');
|
require_once(APPROOT.'/application/ui.linkswidget.class.inc.php');
|
||||||
require_once(APPROOT.'/application/ui.extkeywidget.class.inc.php');
|
require_once(APPROOT.'/application/ui.extkeywidget.class.inc.php');
|
||||||
require_once(APPROOT.'/application/datatable.class.inc.php');
|
require_once(APPROOT.'/application/datatable.class.inc.php');
|
||||||
|
require_once(APPROOT.'/application/excelexporter.class.inc.php');
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1608,6 +1609,119 @@ EOF
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'xlsx_export_dialog':
|
||||||
|
$sFilter = utils::ReadParam('filter', '', false, 'raw_data');
|
||||||
|
$oPage->SetContentType('text/html');
|
||||||
|
$oPage->add(
|
||||||
|
<<<EOF
|
||||||
|
<style>
|
||||||
|
.ui-progressbar {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.progress-label {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 1px;
|
||||||
|
font-size: 11pt;
|
||||||
|
}
|
||||||
|
.download-form button {
|
||||||
|
display:block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
.ui-progressbar-value {
|
||||||
|
background: url(../setup/orange-progress.gif);
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
.statistics > div {
|
||||||
|
padding-left: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 10pt;
|
||||||
|
background: url(../images/minus.gif) 0 2px no-repeat;
|
||||||
|
}
|
||||||
|
.statistics > div.closed {
|
||||||
|
padding-left: 16px;
|
||||||
|
background: url(../images/plus.gif) 0 2px no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statistics .closed .stats-data {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.stats-data td {
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
EOF
|
||||||
|
);
|
||||||
|
$oPage->add('<div id="XlsxExportDlg">');
|
||||||
|
$oPage->add('<div class="export-options">');
|
||||||
|
$oPage->add('<p><input type="checkbox" id="export-advanced-mode"/> <label for="export-advanced-mode">'.Dict::S('UI:CSVImport:AdvancedMode').'</label></p>');
|
||||||
|
$oPage->add('<p style="font-size:10pt;margin-left:2em;margin-top:-0.5em;padding-bottom:1em;">'.Dict::S('UI:CSVImport:AdvancedMode+').'</p>');
|
||||||
|
$oPage->add('<p><input type="checkbox" id="export-auto-download" checked="checked"/> <label for="export-auto-download">'.Dict::S('ExcelExport:AutoDownload').'</label></p>');
|
||||||
|
$oPage->add('</div>');
|
||||||
|
$oPage->add('<div class="progress"><p class="status-message">'.Dict::S('ExcelExport:PreparingExport').'</p><div class="progress-bar"><div class="progress-label"></div></div></div>');
|
||||||
|
$oPage->add('<div class="statistics"><div class="stats-toggle closed">'.Dict::S('ExcelExport:Statistics').'<div class="stats-data"></div></div></div>');
|
||||||
|
$oPage->add('</div>');
|
||||||
|
$aLabels = array(
|
||||||
|
'dialog_title' => Dict::S('ExcelExporter:ExportDialogTitle'),
|
||||||
|
'cancel_button' => Dict::S('UI:Button:Cancel'),
|
||||||
|
'export_button' => Dict::S('ExcelExporter:ExportButton'),
|
||||||
|
'download_button' => Dict::Format('ExcelExporter:DownloadButton', 'export.xlsx'), //TODO: better name for the file (based on the class of the filter??)
|
||||||
|
);
|
||||||
|
$sJSLabels = json_encode($aLabels);
|
||||||
|
$sFilter = addslashes($sFilter);
|
||||||
|
$sJSPageUrl = addslashes(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php');
|
||||||
|
$oPage->add_ready_script("$('#XlsxExportDlg').xlsxexporter({filter: '$sFilter', labels: $sJSLabels, ajax_page_url: '$sJSPageUrl'});");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'xlsx_start':
|
||||||
|
$sFilter = utils::ReadParam('filter', '', false, 'raw_data');
|
||||||
|
$bAdvanced = (utils::ReadParam('advanced', 'false') == 'true');
|
||||||
|
$oSearch = DBObjectSearch::unserialize($sFilter);
|
||||||
|
|
||||||
|
$oExcelExporter = new ExcelExporter();
|
||||||
|
$oExcelExporter->SetObjectList($oSearch);
|
||||||
|
//$oExcelExporter->SetChunkSize(10); //Only for testing
|
||||||
|
$oExcelExporter->SetAdvancedMode($bAdvanced);
|
||||||
|
$sToken = $oExcelExporter->SaveState();
|
||||||
|
$oPage->add(json_encode(array('status' => 'ok', 'token' => $sToken)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'xlsx_run':
|
||||||
|
$sMemoryLimit = MetaModel::GetConfig()->Get('xlsx_exporter_memory_limit');
|
||||||
|
ini_set('memory_limit', $sMemoryLimit);
|
||||||
|
ini_set('max_execution_time', max(300, ini_get('max_execution_time'))); // At least 5 minutes
|
||||||
|
|
||||||
|
$sToken = utils::ReadParam('token', '', false, 'raw_data');
|
||||||
|
$oExcelExporter = new ExcelExporter($sToken);
|
||||||
|
$aStatus = $oExcelExporter->Run();
|
||||||
|
$aResults = array('status' => $aStatus['code'], 'percentage' => $aStatus['percentage'], 'message' => $aStatus['message']);
|
||||||
|
if ($aStatus['code'] == 'done')
|
||||||
|
{
|
||||||
|
$aResults['statistics'] = $oExcelExporter->GetStatistics('html');
|
||||||
|
}
|
||||||
|
$oPage->add(json_encode($aResults));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'xlsx_download':
|
||||||
|
$sToken = utils::ReadParam('token', '', false, 'raw_data');
|
||||||
|
$oPage->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||||
|
$oPage->SetContentDisposition('attachment', 'export.xlsx');
|
||||||
|
$sFileContent = ExcelExporter::GetExcelFileFromToken($sToken);
|
||||||
|
$oPage->add($sFileContent);
|
||||||
|
ExcelExporter::CleanupFromToken($sToken);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'xlsx_abort':
|
||||||
|
// Stop & cleanup an export...
|
||||||
|
$sToken = utils::ReadParam('token', '', false, 'raw_data');
|
||||||
|
ExcelExporter::CleanupFromToken($sToken);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
$oPage->p("Invalid query.");
|
$oPage->p("Invalid query.");
|
||||||
}
|
}
|
||||||
|
|||||||
414
portal/index.php
414
portal/index.php
@@ -60,8 +60,7 @@ function ValidateObject($oObject)
|
|||||||
if (IsPowerUser())
|
if (IsPowerUser())
|
||||||
{
|
{
|
||||||
$sValidationDefine = 'PORTAL_'.strtoupper(get_class($oObject)).'_DISPLAY_POWERUSER_QUERY';
|
$sValidationDefine = 'PORTAL_'.strtoupper(get_class($oObject)).'_DISPLAY_POWERUSER_QUERY';
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
{
|
{
|
||||||
$sValidationDefine = 'PORTAL_'.strtoupper(get_class($oObject)).'_DISPLAY_QUERY';
|
$sValidationDefine = 'PORTAL_'.strtoupper(get_class($oObject)).'_DISPLAY_QUERY';
|
||||||
}
|
}
|
||||||
@@ -162,10 +161,17 @@ function DisplayMainMenu(WebPage $oP)
|
|||||||
$oP->AddMenuButton('showongoing', 'Portal:ShowOngoing', '../portal/index.php?operation=show_ongoing');
|
$oP->AddMenuButton('showongoing', 'Portal:ShowOngoing', '../portal/index.php?operation=show_ongoing');
|
||||||
$oP->AddMenuButton('newrequest', 'Portal:CreateNewRequest', '../portal/index.php?operation=create_request');
|
$oP->AddMenuButton('newrequest', 'Portal:CreateNewRequest', '../portal/index.php?operation=create_request');
|
||||||
$oP->AddMenuButton('showclosed', 'Portal:ShowClosed', '../portal/index.php?operation=show_closed');
|
$oP->AddMenuButton('showclosed', 'Portal:ShowClosed', '../portal/index.php?operation=show_closed');
|
||||||
|
$oP->AddMenuButton('showtoapprove', 'Portal:ShowToApprove', '../portal/index.php?operation=show_toapprove');
|
||||||
|
|
||||||
|
if (isKeyUser()) {
|
||||||
|
$oP->AddMenuButton('showtoresolve', 'Portal:ShowToResolve', '../portal/index.php?operation=show_toresolve');
|
||||||
|
}
|
||||||
|
/* THEBEN
|
||||||
if (UserRights::CanChangePassword())
|
if (UserRights::CanChangePassword())
|
||||||
{
|
{
|
||||||
$oP->AddMenuButton('change_pwd', 'Portal:ChangeMyPassword', '../portal/index.php?loginop=change_pwd');
|
$oP->AddMenuButton('change_pwd', 'Portal:ChangeMyPassword', '../portal/index.php?loginop=change_pwd');
|
||||||
}
|
} */
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -184,6 +190,48 @@ function ShowOngoingTickets(WebPage $oP)
|
|||||||
$oP->add("<h1 id=\"#resolved_requests\">".Dict::S('Portal:ResolvedRequests')."</h1>\n");
|
$oP->add("<h1 id=\"#resolved_requests\">".Dict::S('Portal:ResolvedRequests')."</h1>\n");
|
||||||
ListResolvedRequests($oP);
|
ListResolvedRequests($oP);
|
||||||
$oP->add("</div>\n");
|
$oP->add("</div>\n");
|
||||||
|
|
||||||
|
$oP->add("<div id=\"#requests_to_approve\">\n");
|
||||||
|
$oP->add("<h1 id=\"#title_requests_to_approve\">".Dict::S('Portal:RequestsToApprove')."</h1>\n");
|
||||||
|
ListRequestsToApprove($oP);
|
||||||
|
$oP->add("</div>\n");
|
||||||
|
|
||||||
|
if (isKeyUser()) {
|
||||||
|
$oP->add("<div id=\"open_requests\">\n");
|
||||||
|
$oP->add("<h1 id=\"title_requests_to_resolve\">".Dict::S('Portal:RequestsToResolve')."</h1>\n");
|
||||||
|
ListRequestsToResolve($oP);
|
||||||
|
$oP->add("</div>\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the tickets which need approval by mysel
|
||||||
|
* @param WebPage $oP The current web page
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
// =========================== THEBEN ==================================
|
||||||
|
function ShowTicketsToApprove(WebPage $oP)
|
||||||
|
{
|
||||||
|
$oP->add("<div id=\"open_requests\">\n");
|
||||||
|
$oP->add("<h1 id=\"title_open_requests\">".Dict::S('Portal:RequestsToApprove')."</h1>\n");
|
||||||
|
ListRequestsToApprove($oP);
|
||||||
|
$oP->add("</div>\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the tickets which need approval by mysel
|
||||||
|
* @param WebPage $oP The current web page
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
// =========================== THEBEN ==================================
|
||||||
|
function ShowTicketsToResolve(WebPage $oP)
|
||||||
|
{
|
||||||
|
$oP->add("<div id=\"open_requests\">\n");
|
||||||
|
$oP->add("<h1 id=\"title_requests_to_resolve\">".Dict::S('Portal:RequestsToResolve')."</h1>\n");
|
||||||
|
ListRequestsToResolve($oP);
|
||||||
|
$oP->add("</div>\n");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -199,134 +247,6 @@ function ShowClosedTickets(WebPage $oP)
|
|||||||
$oP->add("</div>\n");
|
$oP->add("</div>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the form to select a Service Category Id (among the valid ones for the specified user Organization)
|
|
||||||
* @param WebPage $oP Web page for the form output
|
|
||||||
* @param Organization $oUserOrg The organization of the current user
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function SelectServiceCategory($oP, $oUserOrg)
|
|
||||||
{
|
|
||||||
$aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS.',template_id');
|
|
||||||
|
|
||||||
$oSearch = DBObjectSearch::FromOQL(PORTAL_SERVICECATEGORY_QUERY);
|
|
||||||
$oSearch->AllowAllData(); // In case the user has the rights on his org only
|
|
||||||
$oSet = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey()));
|
|
||||||
if ($oSet->Count() == 1)
|
|
||||||
{
|
|
||||||
$oService = $oSet->Fetch();
|
|
||||||
$iSvcCategory = $oService->GetKey();
|
|
||||||
// Only one Category, skip this step in the wizard
|
|
||||||
SelectServiceSubCategory($oP, $oUserOrg, $iSvcCategory);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$oP->add("<div class=\"wizContainer\" id=\"form_select_service\">\n");
|
|
||||||
$oP->WizardFormStart('request_wizard', 1);
|
|
||||||
|
|
||||||
$oP->add("<h1 id=\"select_category\">".Dict::S('Portal:SelectService')."</h1>\n");
|
|
||||||
$oP->add("<table>\n");
|
|
||||||
while($oService = $oSet->Fetch())
|
|
||||||
{
|
|
||||||
$id = $oService->GetKey();
|
|
||||||
$sChecked = "";
|
|
||||||
if (isset($aParameters['service_id']) && ($id == $aParameters['service_id']))
|
|
||||||
{
|
|
||||||
$sChecked = "checked";
|
|
||||||
}
|
|
||||||
$oP->p("<tr><td style=\"vertical-align:top\"><p><input name=\"attr_service_id\" $sChecked type=\"radio\" id=\"service_$id\" value=\"$id\"></p></td><td style=\"vertical-align:top\"><p><b><label for=\"service_$id\">".$oService->GetName()."</label></b></p>");
|
|
||||||
$oP->p("<p>".$oService->GetAsHTML('description')."</p></td></tr>");
|
|
||||||
}
|
|
||||||
$oP->add("</table>\n");
|
|
||||||
|
|
||||||
$oP->DumpHiddenParams($aParameters, array('service_id'));
|
|
||||||
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"create_request\">");
|
|
||||||
$oP->WizardFormButtons(BUTTON_NEXT | BUTTON_CANCEL); // NO back button since it's the first step of the Wizard
|
|
||||||
$oP->WizardFormEnd();
|
|
||||||
$oP->WizardCheckSelectionOnSubmit(Dict::S('Portal:PleaseSelectOneService'));
|
|
||||||
$oP->add("</div>\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the form to select a Service Subcategory Id (among the valid ones for the specified user Organization)
|
|
||||||
* and based on the page's parameter 'service_id'
|
|
||||||
* @param WebPage $oP Web page for the form output
|
|
||||||
* @param Organization $oUserOrg The organization of the current user
|
|
||||||
* @param $iSvcId Id of the selected service in case of pass-through (when there is only one service)
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function SelectServiceSubCategory($oP, $oUserOrg, $iSvcId = null)
|
|
||||||
{
|
|
||||||
$aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS.',template_id');
|
|
||||||
if ($iSvcId == null)
|
|
||||||
{
|
|
||||||
$iSvcId = $aParameters['service_id'];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$aParameters['service_id'] = $iSvcId;
|
|
||||||
}
|
|
||||||
$iDefaultSubSvcId = isset($aParameters['servicesubcategory_id']) ? $aParameters['servicesubcategory_id'] : 0;
|
|
||||||
|
|
||||||
$iDefaultWizNext = 2;
|
|
||||||
|
|
||||||
$oSearch = DBObjectSearch::FromOQL(PORTAL_SERVICE_SUBCATEGORY_QUERY);
|
|
||||||
RestrictSubcategories($oSearch);
|
|
||||||
$oSearch->AllowAllData(); // In case the user has the rights on his org only
|
|
||||||
$oSet = new CMDBObjectSet($oSearch, array(), array('svc_id' => $iSvcId, 'org_id' => $oUserOrg->GetKey()));
|
|
||||||
if ($oSet->Count() == 1)
|
|
||||||
{
|
|
||||||
// Only one sub service, skip this step of the wizard
|
|
||||||
$oSubService = $oSet->Fetch();
|
|
||||||
$iSubSvdId = $oSubService->GetKey();
|
|
||||||
SelectRequestTemplate($oP, $oUserOrg, $iSvcId, $iSubSvdId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$oServiceCategory = MetaModel::GetObject('Service', $iSvcId, false, true /* allow all data*/);
|
|
||||||
if (is_object($oServiceCategory))
|
|
||||||
{
|
|
||||||
$oP->add("<div class=\"wizContainer\" id=\"form_select_servicesubcategory\">\n");
|
|
||||||
$oP->add("<h1 id=\"select_subcategory\">".Dict::Format('Portal:SelectSubcategoryFrom_Service', $oServiceCategory->GetName())."</h1>\n");
|
|
||||||
$oP->WizardFormStart('request_wizard', $iDefaultWizNext);
|
|
||||||
$oP->add("<table>\n");
|
|
||||||
while($oSubService = $oSet->Fetch())
|
|
||||||
{
|
|
||||||
$id = $oSubService->GetKey();
|
|
||||||
$sChecked = "";
|
|
||||||
if ($id == $iDefaultSubSvcId)
|
|
||||||
{
|
|
||||||
$sChecked = "checked";
|
|
||||||
}
|
|
||||||
|
|
||||||
$oP->add("<tr>");
|
|
||||||
|
|
||||||
$oP->add("<td style=\"vertical-align:top\">");
|
|
||||||
$oP->add("<p><input name=\"attr_servicesubcategory_id\" $sChecked type=\"radio\" id=\"servicesubcategory_$id\" value=\"$id\"></p>");
|
|
||||||
$oP->add("</td>");
|
|
||||||
|
|
||||||
$oP->add("<td style=\"vertical-align:top\">");
|
|
||||||
$oP->add("<p><b><label for=\"servicesubcategory_$id\">".$oSubService->GetName()."</label></b></p>");
|
|
||||||
$oP->add("<p>".$oSubService->GetAsHTML('description')."</p>");
|
|
||||||
$oP->add("</td>");
|
|
||||||
$oP->add("</tr>");
|
|
||||||
}
|
|
||||||
$oP->add("</table>\n");
|
|
||||||
$oP->DumpHiddenParams($aParameters, array('servicesubcategory_id'));
|
|
||||||
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"create_request\">");
|
|
||||||
$oP->WizardFormButtons(BUTTON_BACK | BUTTON_NEXT | BUTTON_CANCEL); //Back button automatically discarded if on the first page
|
|
||||||
$oP->WizardFormEnd();
|
|
||||||
$oP->WizardCheckSelectionOnSubmit(Dict::S('Portal:PleaseSelectAServiceSubCategory'));
|
|
||||||
$oP->add("</div>\n");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$oP->p("Error: Invalid Service: id = $iSvcId");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the form to select a Template
|
* Displays the form to select a Template
|
||||||
* @param WebPage $oP Web page for the form output
|
* @param WebPage $oP Web page for the form output
|
||||||
@@ -437,21 +357,9 @@ function SelectRequestTemplate($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null
|
|||||||
* @param integer $iTemplateId The identifier of the template (fall through when there is only one template)
|
* @param integer $iTemplateId The identifier of the template (fall through when there is only one template)
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function RequestCreationForm($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null, $iTemplateId = null)
|
function RequestCreationForm($oP, $oUserOrg)
|
||||||
{
|
{
|
||||||
$aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS.',template_id');
|
$aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS.',template_id');
|
||||||
if (!is_null($iSvcId))
|
|
||||||
{
|
|
||||||
$aParameters['service_id'] = $iSvcId;
|
|
||||||
}
|
|
||||||
if (!is_null($iSubSvcId))
|
|
||||||
{
|
|
||||||
$aParameters['servicesubcategory_id'] = $iSubSvcId;
|
|
||||||
}
|
|
||||||
if (!is_null($iTemplateId))
|
|
||||||
{
|
|
||||||
$aParameters['template_id'] = $iTemplateId;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sDescription = '';
|
$sDescription = '';
|
||||||
if (isset($aParameters['template_id']) && ($aParameters['template_id'] != 0))
|
if (isset($aParameters['template_id']) && ($aParameters['template_id'] != 0))
|
||||||
@@ -475,25 +383,33 @@ function RequestCreationForm($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$oServiceCategory = MetaModel::GetObject('Service', $aParameters['service_id'], false, true /* allow all data*/);
|
//$oServiceCategory = MetaModel::GetObject('Service', $aParameters['service_id'], false, true /* allow all data*/);
|
||||||
$oServiceSubCategory = MetaModel::GetObject('ServiceSubcategory', $aParameters['servicesubcategory_id'], false, true /* allow all data*/);
|
//$oServiceSubCategory = MetaModel::GetObject('ServiceSubcategory', $aParameters['servicesubcategory_id'], false, true /* allow all data*/);
|
||||||
if (is_object($oServiceCategory) && is_object($oServiceSubCategory))
|
//if (is_object($oServiceCategory) && is_object($oServiceSubCategory))
|
||||||
{
|
{
|
||||||
$sClass = ComputeClass($oServiceSubCategory->GetKey());
|
$sClass = "UserRequest"; // ComputeClass($oServiceSubCategory->GetKey());
|
||||||
$oRequest = MetaModel::NewObject($sClass);
|
$oRequest = MetaModel::NewObject($sClass);
|
||||||
$oRequest->Set('org_id', $oUserOrg->GetKey());
|
$oRequest->Set('org_id', $oUserOrg->GetKey());
|
||||||
$oRequest->Set('caller_id', UserRights::GetContactId());
|
$oRequest->Set('caller_id', UserRights::GetContactId());
|
||||||
$oRequest->Set('service_id', $aParameters['service_id']);
|
// $oRequest->Set('service_id', $aParameters['service_id']);
|
||||||
$oRequest->Set('servicesubcategory_id', $aParameters['servicesubcategory_id']);
|
// $oRequest->Set('servicesubcategory_id', $aParameters['servicesubcategory_id']);
|
||||||
|
|
||||||
$oAttDef = MetaModel::GetAttributeDef($sClass, 'service_id');
|
/* $oAttDef = MetaModel::GetAttributeDef($sClass, 'service_id');
|
||||||
$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $oServiceCategory->GetName());
|
$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $oServiceCategory->GetName());
|
||||||
|
|
||||||
$oAttDef = MetaModel::GetAttributeDef($sClass, 'servicesubcategory_id');
|
$oAttDef = MetaModel::GetAttributeDef($sClass, 'servicesubcategory_id');
|
||||||
$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $oServiceSubCategory->GetName());
|
$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $oServiceSubCategory->GetName());
|
||||||
|
|
||||||
|
*/
|
||||||
$aList = explode(',', GetConstant($sClass, 'FORM_ATTRIBUTES'));
|
$aList = explode(',', GetConstant($sClass, 'FORM_ATTRIBUTES'));
|
||||||
|
|
||||||
|
IssueLog::info("aList, FORM_ATTRIBUTES=".print_r($aList,true));
|
||||||
|
// TODO
|
||||||
|
/* echo "<select name='userCharacters' id='userCharacter'>";
|
||||||
|
echo "<option value='1'>Kid Wonder</option>";
|
||||||
|
echo "<option value='3'>Oriel</option>";
|
||||||
|
echo "</select>"; */
|
||||||
|
|
||||||
$iFlags = 0;
|
$iFlags = 0;
|
||||||
foreach($aList as $sAttCode)
|
foreach($aList as $sAttCode)
|
||||||
{
|
{
|
||||||
@@ -507,8 +423,10 @@ function RequestCreationForm($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null,
|
|||||||
$aFieldsMap = array();
|
$aFieldsMap = array();
|
||||||
foreach($aList as $sAttCode)
|
foreach($aList as $sAttCode)
|
||||||
{
|
{
|
||||||
|
IssueLog::Info("sAttCode=".$sAttCode);
|
||||||
$value = '';
|
$value = '';
|
||||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||||
|
IssueLog::Info("oAttDef=".print_r($oAttDef,true));
|
||||||
$iFlags = $oRequest->GetAttributeFlags($sAttCode);
|
$iFlags = $oRequest->GetAttributeFlags($sAttCode);
|
||||||
if (isset($aParameters[$sAttCode]))
|
if (isset($aParameters[$sAttCode]))
|
||||||
{
|
{
|
||||||
@@ -517,8 +435,10 @@ function RequestCreationForm($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null,
|
|||||||
$aArgs = array('this' => $oRequest);
|
$aArgs = array('this' => $oRequest);
|
||||||
|
|
||||||
$sInputId = 'attr_'.$sAttCode;
|
$sInputId = 'attr_'.$sAttCode;
|
||||||
|
IssueLog::Info("sInputId=".$sInputId);
|
||||||
$aFieldsMap[$sAttCode] = $sInputId;
|
$aFieldsMap[$sAttCode] = $sInputId;
|
||||||
$sValue = "<span id=\"field_{$sInputId}\">".$oRequest->GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $value, '', 'attr_'.$sAttCode, '', $iFlags, $aArgs).'</span>';
|
$sValue = "<span id=\"field_{$sInputId}\">".$oRequest->GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $value, '', 'attr_'.$sAttCode, '', $iFlags, $aArgs).'</span>';
|
||||||
|
IssueLog::Info("sValue=".$sValue);
|
||||||
$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sValue);
|
$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sValue);
|
||||||
}
|
}
|
||||||
$aHidden = array();
|
$aHidden = array();
|
||||||
@@ -527,6 +447,7 @@ function RequestCreationForm($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null,
|
|||||||
foreach ($aTemplateFields as $sAttCode => $oField)
|
foreach ($aTemplateFields as $sAttCode => $oField)
|
||||||
{
|
{
|
||||||
$sValue = $oField->GetFormElement($oP, $sClass);
|
$sValue = $oField->GetFormElement($oP, $sClass);
|
||||||
|
|
||||||
if ($oField->Get('input_type') == 'hidden')
|
if ($oField->Get('input_type') == 'hidden')
|
||||||
{
|
{
|
||||||
$aHidden[] = $sValue;
|
$aHidden[] = $sValue;
|
||||||
@@ -554,7 +475,7 @@ EOF
|
|||||||
$oP->add_linked_script("../js/jquery.blockUI.js");
|
$oP->add_linked_script("../js/jquery.blockUI.js");
|
||||||
$oP->add("<div class=\"wizContainer\" id=\"form_request_description\">\n");
|
$oP->add("<div class=\"wizContainer\" id=\"form_request_description\">\n");
|
||||||
$oP->add("<h1 id=\"title_request_form\">".Dict::S('Portal:DescriptionOfTheRequest')."</h1>\n");
|
$oP->add("<h1 id=\"title_request_form\">".Dict::S('Portal:DescriptionOfTheRequest')."</h1>\n");
|
||||||
$oP->WizardFormStart('request_form', 4);
|
$oP->WizardFormStart('request_form', 1);
|
||||||
|
|
||||||
$oP->details($aDetails);
|
$oP->details($aDetails);
|
||||||
|
|
||||||
@@ -587,7 +508,7 @@ EOF
|
|||||||
$oP->add("</div>\n");
|
$oP->add("</div>\n");
|
||||||
$iFieldsCount = count($aFieldsMap);
|
$iFieldsCount = count($aFieldsMap);
|
||||||
$sJsonFieldsMap = json_encode($aFieldsMap);
|
$sJsonFieldsMap = json_encode($aFieldsMap);
|
||||||
|
// IssueLog::Info("sJsonFieldsMap=".$sJsonFieldsMap);
|
||||||
$oP->add_ready_script(
|
$oP->add_ready_script(
|
||||||
<<<EOF
|
<<<EOF
|
||||||
oWizardHelper.SetFieldsMap($sJsonFieldsMap);
|
oWizardHelper.SetFieldsMap($sJsonFieldsMap);
|
||||||
@@ -601,11 +522,11 @@ EOF
|
|||||||
EOF
|
EOF
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
// else
|
||||||
{
|
// {
|
||||||
// User not authorized to use this service ?
|
// User not authorized to use this service ?
|
||||||
//ShowOngoingTickets($oP);
|
//ShowOngoingTickets($oP);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -615,7 +536,7 @@ EOF
|
|||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function DoCreateRequest($oP, $oUserOrg)
|
function DoCreateRequest($oP, $oUserOrg)
|
||||||
{
|
{ IssueLog::Info("DoCreateRequest");
|
||||||
$aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS.',template_id');
|
$aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS.',template_id');
|
||||||
$sTransactionId = utils::ReadPostedParam('transaction_id', '');
|
$sTransactionId = utils::ReadPostedParam('transaction_id', '');
|
||||||
if (!utils::IsTransactionValid($sTransactionId))
|
if (!utils::IsTransactionValid($sTransactionId))
|
||||||
@@ -713,8 +634,8 @@ function CreateRequest(WebPage $oP, Organization $oUserOrg)
|
|||||||
{
|
{
|
||||||
switch($oP->GetWizardStep())
|
switch($oP->GetWizardStep())
|
||||||
{
|
{
|
||||||
case 0:
|
// case 0:
|
||||||
default:
|
/* default:
|
||||||
SelectServiceCategory($oP, $oUserOrg);
|
SelectServiceCategory($oP, $oUserOrg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -724,13 +645,13 @@ function CreateRequest(WebPage $oP, Organization $oUserOrg)
|
|||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
SelectRequestTemplate($oP, $oUserOrg);
|
SelectRequestTemplate($oP, $oUserOrg);
|
||||||
break;
|
break; */
|
||||||
|
|
||||||
case 3:
|
case 0:
|
||||||
RequestCreationForm($oP, $oUserOrg);
|
RequestCreationForm($oP, $oUserOrg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 4:
|
case 1:
|
||||||
DoCreateRequest($oP, $oUserOrg);
|
DoCreateRequest($oP, $oUserOrg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -825,6 +746,54 @@ function ListResolvedRequests(WebPage $oP)
|
|||||||
DisplayRequestLists($oP, $aClassToSet);
|
DisplayRequestLists($oP, $aClassToSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all the currently resolved (not yet closed) User Requests for the current user
|
||||||
|
* @param WebPage $oP The current web page
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
// ========================= THEBEN ===================================
|
||||||
|
function ListRequestsToApprove(WebPage $oP)
|
||||||
|
{
|
||||||
|
$oUserOrg = GetUserOrg();
|
||||||
|
|
||||||
|
$aClassToSet = array();
|
||||||
|
foreach (GetTicketClasses() as $sClass)
|
||||||
|
{
|
||||||
|
$sOQL = "SELECT $sClass WHERE org_id = :org_id AND status = 'waiting_for_approval'";
|
||||||
|
$oSearch = DBObjectSearch::FromOQL($sOQL);
|
||||||
|
$iUser = UserRights::GetContactId();
|
||||||
|
|
||||||
|
$oSearch->AddCondition('approver_id', $iUser);
|
||||||
|
|
||||||
|
$aClassToSet[$sClass] = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey()));
|
||||||
|
}
|
||||||
|
DisplayRequestLists($oP, $aClassToSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all the currently resolved (not yet closed) User Requests for the current user
|
||||||
|
* @param WebPage $oP The current web page
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
// ========================= THEBEN ===================================
|
||||||
|
function ListRequestsToResolve(WebPage $oP)
|
||||||
|
{
|
||||||
|
$oUserOrg = GetUserOrg();
|
||||||
|
|
||||||
|
$aClassToSet = array();
|
||||||
|
foreach (GetTicketClasses() as $sClass)
|
||||||
|
{
|
||||||
|
$sOQL = "SELECT $sClass WHERE org_id = :org_id AND status = 'assigned'";
|
||||||
|
$oSearch = DBObjectSearch::FromOQL($sOQL);
|
||||||
|
$iUser = UserRights::GetContactId();
|
||||||
|
|
||||||
|
$oSearch->AddCondition('agent_id', $iUser);
|
||||||
|
|
||||||
|
$aClassToSet[$sClass] = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey()));
|
||||||
|
}
|
||||||
|
DisplayRequestLists($oP, $aClassToSet);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists all the currently closed tickets
|
* Lists all the currently closed tickets
|
||||||
* @param WebPage $oP The current web page
|
* @param WebPage $oP The current web page
|
||||||
@@ -896,6 +865,8 @@ function DisplayObject($oP, $oObj, $oUserOrg)
|
|||||||
* @param Object $oObj The target object
|
* @param Object $oObj The target object
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
|
//============= THEBEN ============= ApproveButton === ResolveButton ===============
|
||||||
|
|
||||||
function ShowDetailsRequest(WebPage $oP, $oObj)
|
function ShowDetailsRequest(WebPage $oP, $oObj)
|
||||||
{
|
{
|
||||||
$sClass = get_class($oObj);
|
$sClass = get_class($oObj);
|
||||||
@@ -905,6 +876,10 @@ function ShowDetailsRequest(WebPage $oP, $oObj)
|
|||||||
$bIsReopenButton = false;
|
$bIsReopenButton = false;
|
||||||
$bIsCloseButton = false;
|
$bIsCloseButton = false;
|
||||||
$bIsEscalateButton = false;
|
$bIsEscalateButton = false;
|
||||||
|
$bIsApproveButton = false;
|
||||||
|
$bIsRejectButton = false;
|
||||||
|
$bIsResolveButton = false;
|
||||||
|
|
||||||
$bEditAttachments = false;
|
$bEditAttachments = false;
|
||||||
$aEditAtt = array(); // List of attributes editable in the main form
|
$aEditAtt = array(); // List of attributes editable in the main form
|
||||||
if (!MetaModel::DBIsReadOnly())
|
if (!MetaModel::DBIsReadOnly())
|
||||||
@@ -923,16 +898,66 @@ function ShowDetailsRequest(WebPage $oP, $oObj)
|
|||||||
}
|
}
|
||||||
// Add the "Close" button if this is valid action
|
// Add the "Close" button if this is valid action
|
||||||
if (array_key_exists('ev_close', $aTransitions) && UserRights::IsStimulusAllowed($sClass, 'ev_close', $oSet))
|
if (array_key_exists('ev_close', $aTransitions) && UserRights::IsStimulusAllowed($sClass, 'ev_close', $oSet))
|
||||||
{
|
{ if (($oObj->Get('caller_id') == UserRights::GetContactId())
|
||||||
|
|| IsPowerUSer()) {
|
||||||
|
// I can only close a ticket if I'm the caller or a power user
|
||||||
$bIsCloseButton = true;
|
$bIsCloseButton = true;
|
||||||
MakeStimulusForm($oP, $oObj, 'ev_close', array('user_satisfaction', $sUserCommentAttCode));
|
MakeStimulusForm($oP, $oObj, 'ev_close', array('user_satisfaction', $sUserCommentAttCode));
|
||||||
}
|
}
|
||||||
|
IssueLog::Info('caller_id='.$oObj->Get('caller_id')." user-id=".UserRights::GetContactId());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'waiting_for_approval':
|
||||||
|
$aEditAtt = array();
|
||||||
|
$aTransitions = $oObj->EnumTransitions();
|
||||||
|
$oSet = DBObjectSet::FromObject($oObj);
|
||||||
|
// Add the "Approve" button if this is valid action
|
||||||
|
if (array_key_exists('ev_approve', $aTransitions)) //TODO check if current user is approver && UserRights::IsStimulusAllowed($sClass, 'ev_reopen', $oSet))
|
||||||
|
{
|
||||||
|
$bIsApproveButton = true;
|
||||||
|
MakeStimulusForm($oP, $oObj, 'ev_approve', array($sLogAttCode));
|
||||||
|
}
|
||||||
|
// Add the "Reject" button if this is valid action
|
||||||
|
if (array_key_exists('ev_reject', $aTransitions)) // TODO check if current user is approve && UserRights::IsStimulusAllowed($sClass, 'ev_close', $oSet))
|
||||||
|
{
|
||||||
|
$bIsRejectButton = true;
|
||||||
|
MakeStimulusForm($oP, $oObj, 'ev_reject', array($sLogAttCode) ); //array('user_satisfaction', $sUserCommentAttCode));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'closed':
|
case 'closed':
|
||||||
// By convention 'closed' is the final state of a ticket and nothing can be done in such a state
|
// By convention 'closed' is the final state of a ticket and nothing can be done in such a state
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'assigned':
|
||||||
|
|
||||||
|
$iFlags = $oObj->GetAttributeFlags($sLogAttCode);
|
||||||
|
$bReadOnly = (($iFlags & (OPT_ATT_READONLY | OPT_ATT_HIDDEN)) != 0);
|
||||||
|
if ($bReadOnly)
|
||||||
|
{
|
||||||
|
$aEditAtt = array();
|
||||||
|
$bEditAttachments = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$aEditAtt = array(
|
||||||
|
$sLogAttCode => '????'
|
||||||
|
);
|
||||||
|
$bEditAttachments = true;
|
||||||
|
}
|
||||||
|
if (isKeyUser()) {
|
||||||
|
$aTransitions = $oObj->EnumTransitions();
|
||||||
|
$oSet = DBObjectSet::FromObject($oObj);
|
||||||
|
// Add the "Resolver" button if this is valid action
|
||||||
|
if (array_key_exists('ev_resolve', $aTransitions)) //TODO check if current user is approver && UserRights::IsStimulusAllowed($sClass, 'ev_reopen', $oSet))
|
||||||
|
{
|
||||||
|
$bIsResolveButton = true;
|
||||||
|
MakeStimulusForm($oP, $oObj, 'ev_resolve', array($sLogAttCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// In all other states, the only possible action is to update the ticket (both the case log and the attachments)
|
// In all other states, the only possible action is to update the ticket (both the case log and the attachments)
|
||||||
// This update is possible only if the case log field is not read-only or hidden in the current state
|
// This update is possible only if the case log field is not read-only or hidden in the current state
|
||||||
@@ -1073,6 +1098,32 @@ EOF
|
|||||||
$sOk = addslashes(Dict::S('UI:Button:Ok'));
|
$sOk = addslashes(Dict::S('UI:Button:Ok'));
|
||||||
$oP->p('<input type="button" onClick="RunStimulusDialog(\''.$sStimulusCode.'\', \''.$sTitle.'\', \''.$sOk.'\');" value="'.$sTitle.'...">');
|
$oP->p('<input type="button" onClick="RunStimulusDialog(\''.$sStimulusCode.'\', \''.$sTitle.'\', \''.$sOk.'\');" value="'.$sTitle.'...">');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($bIsApproveButton)
|
||||||
|
{
|
||||||
|
$sStimulusCode = 'ev_approve';
|
||||||
|
$sTitle = addslashes(Dict::S('Portal:Button:ApproveTicket'));
|
||||||
|
$sOk = addslashes(Dict::S('UI:Button:Ok'));
|
||||||
|
$oP->p('<input type="button" onClick="RunStimulusDialog(\''.$sStimulusCode.'\', \''.$sTitle.'\', \''.$sOk.'\');" value="'.$sTitle.'...">');
|
||||||
|
}
|
||||||
|
|
||||||
|
if($bIsRejectButton)
|
||||||
|
{
|
||||||
|
$sStimulusCode = 'ev_reject';
|
||||||
|
$sTitle = addslashes(Dict::S('Portal:Button:RejectTicket'));
|
||||||
|
$sOk = addslashes(Dict::S('UI:Button:Ok'));
|
||||||
|
$oP->p('<input type="button" onClick="RunStimulusDialog(\''.$sStimulusCode.'\', \''.$sTitle.'\', \''.$sOk.'\');" value="'.$sTitle.'...">');
|
||||||
|
}
|
||||||
|
|
||||||
|
if($bIsResolveButton)
|
||||||
|
{
|
||||||
|
$sStimulusCode = 'ev_resolve';
|
||||||
|
$sTitle = addslashes(Dict::S('Portal:Button:ResolveTicket'));
|
||||||
|
$sOk = addslashes(Dict::S('UI:Button:Ok'));
|
||||||
|
$oP->p('<input type="button" onClick="RunStimulusDialog(\''.$sStimulusCode.'\', \''.$sTitle.'\', \''.$sOk.'\');" value="'.$sTitle.'...">');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if($bIsCloseButton)
|
if($bIsCloseButton)
|
||||||
{
|
{
|
||||||
$sStimulusCode = 'ev_close';
|
$sStimulusCode = 'ev_close';
|
||||||
@@ -1257,6 +1308,26 @@ function IsPowerUSer()
|
|||||||
return $bRes;
|
return $bRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the current user can be considered as being a portal key user
|
||||||
|
* (can update tickets where he is agent and can resolve them)
|
||||||
|
*/
|
||||||
|
function IsKeyUSer()
|
||||||
|
{
|
||||||
|
$iUserID = UserRights::GetUserId();
|
||||||
|
$sOQLprofile = "SELECT URP_Profiles AS p JOIN URP_UserProfile AS up ON up.profileid=p.id WHERE up.userid = :user AND p.name = :profile";
|
||||||
|
$oProfileSet = new DBObjectSet(
|
||||||
|
DBObjectSearch::FromOQL($sOQLprofile),
|
||||||
|
array(),
|
||||||
|
array(
|
||||||
|
'user' => $iUserID,
|
||||||
|
'profile' => 'PORTAL_KEY_USER_PROFILE',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$bRes = ($oProfileSet->count() > 0);
|
||||||
|
return $bRes;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Main program
|
// Main program
|
||||||
@@ -1288,9 +1359,11 @@ try
|
|||||||
|
|
||||||
$sCode = $oUserOrg->Get('code');
|
$sCode = $oUserOrg->Get('code');
|
||||||
$sAlternateStylesheet = '';
|
$sAlternateStylesheet = '';
|
||||||
|
//IssueLog::Info("org code of user=".$sCode);
|
||||||
if (@file_exists("./$sCode/portal.css"))
|
if (@file_exists("./$sCode/portal.css"))
|
||||||
{
|
{
|
||||||
$sAlternateStylesheet = "$sCode";
|
$sAlternateStylesheet = "$sCode";
|
||||||
|
IssueLog::Info("using Alt Stylesheet: ".$sAlternateStylesheet);
|
||||||
}
|
}
|
||||||
|
|
||||||
$oP = new PortalWebPage(Dict::S('Portal:Title'), $sAlternateStylesheet);
|
$oP = new PortalWebPage(Dict::S('Portal:Title'), $sAlternateStylesheet);
|
||||||
@@ -1350,11 +1423,26 @@ try
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// =================== THEBEN ===========================
|
||||||
|
case 'show_toapprove':
|
||||||
|
$oP->set_title(Dict::S('Portal:ShowToApprove'));
|
||||||
|
DisplayMainMenu($oP);
|
||||||
|
ShowTicketsToApprove($oP);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// =================== THEBEN ===========================
|
||||||
|
case 'show_toresolve':
|
||||||
|
$oP->set_title(Dict::S('Portal:ShowToResolve'));
|
||||||
|
DisplayMainMenu($oP);
|
||||||
|
ShowTicketsToResolve($oP);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'show_ongoing':
|
case 'show_ongoing':
|
||||||
default:
|
default:
|
||||||
$oP->set_title(Dict::S('Portal:ShowOngoing'));
|
$oP->set_title(Dict::S('Portal:ShowOngoing'));
|
||||||
DisplayMainMenu($oP);
|
DisplayMainMenu($oP);
|
||||||
ShowOngoingTickets($oP);
|
ShowOngoingTickets($oP);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -803,5 +803,30 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</pre>
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</pre>
|
||||||
]]></text>
|
]]></text>
|
||||||
</license>
|
</license>
|
||||||
|
<license>
|
||||||
|
<product>PHP XLSXWriter</product>
|
||||||
|
<author>Mark Jones</author>
|
||||||
|
<license_type>MIT</license_type>
|
||||||
|
<text><![CDATA[
|
||||||
|
<pre>Copyright (c) 2013 Mark Jones
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</pre>
|
||||||
|
]]></text>
|
||||||
|
</license>
|
||||||
</licenses>
|
</licenses>
|
||||||
|
|
||||||
|
|||||||
@@ -28,9 +28,11 @@ if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
|
|||||||
require_once(__DIR__.'/../approot.inc.php');
|
require_once(__DIR__.'/../approot.inc.php');
|
||||||
require_once(APPROOT.'/application/application.inc.php');
|
require_once(APPROOT.'/application/application.inc.php');
|
||||||
require_once(APPROOT.'/application/nicewebpage.class.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/csvpage.class.inc.php');
|
||||||
require_once(APPROOT.'/application/xmlpage.class.inc.php');
|
require_once(APPROOT.'/application/xmlpage.class.inc.php');
|
||||||
require_once(APPROOT.'/application/clipage.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');
|
require_once(APPROOT.'/application/startup.inc.php');
|
||||||
|
|
||||||
@@ -264,6 +266,32 @@ if (!empty($sExpression))
|
|||||||
cmdbAbstractObject::DisplaySetAsXML($oP, $oSet, array('localize_values' => $bLocalize));
|
cmdbAbstractObject::DisplaySetAsXML($oP, $oSet, array('localize_values' => $bLocalize));
|
||||||
break;
|
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:
|
default:
|
||||||
$oP = new WebPage("iTop - Export");
|
$oP = new WebPage("iTop - Export");
|
||||||
$oP->add("Unsupported format '$sFormat'. Possible values are: html, csv, spreadsheet or xml.");
|
$oP->add("Unsupported format '$sFormat'. Possible values are: html, csv, spreadsheet or xml.");
|
||||||
@@ -301,7 +329,7 @@ if (!$oP)
|
|||||||
$oP->p(" * expression: an OQL expression (URL encoded if needed)");
|
$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");
|
$oP->p(" * query: (alternative to 'expression') the id of an entry from the query phrasebook");
|
||||||
$oP->p(" * arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'");
|
$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' or 'xml'");
|
$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: (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(" * 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");
|
$oP->p(" * filename: (optional, no effect in CLI mode) if set then the results will be downloaded as a file");
|
||||||
|
|||||||
Reference in New Issue
Block a user