/**
  * Constructs a DisplayBlock object from a DBObjectSet already in memory
  * @param $oSet DBObjectSet
  * @return DisplayBlock The DisplayBlock object, or null if the creation failed
  */
 public static function FromObjectSet(DBObjectSet $oSet, $sStyle, $aParams = array())
 {
     $oDummyFilter = new DBObjectSearch($oSet->GetClass());
     $aKeys = array();
     $oSet->OptimizeColumnLoad(array('id'));
     // No need to load all the columns just to get the id
     while ($oObject = $oSet->Fetch()) {
         $aKeys[] = $oObject->GetKey();
     }
     $oSet->Rewind();
     if (count($aKeys) > 0) {
         $oDummyFilter->AddCondition('id', $aKeys, 'IN');
     } else {
         $oDummyFilter->AddCondition('id', 0, '=');
     }
     $oBlock = new DisplayBlock($oDummyFilter, $sStyle, false, $aParams);
     // DisplayBlocks built this way are synchronous
     return $oBlock;
 }
Example #2
0
                    $oFilter = new DBObjectSearch($sClassName);
                    $aParams = array();
                    foreach ($aFullTextNeedles as $sSearchText) {
                        $oFilter->AddCondition_FullText($sSearchText);
                    }
                }
                // Skip abstract classes
                if (MetaModel::IsAbstract($sClassName)) {
                    continue;
                }
                if ($iTune > 0) {
                    $fStartedClass = microtime(true);
                }
                $oSet = new DBObjectSet($oFilter, array(), $aParams);
                if (array_key_exists($sClassName, $aAccelerators) && array_key_exists('attributes', $aAccelerators[$sClassName])) {
                    $oSet->OptimizeColumnLoad(array($oFilter->GetClassAlias() => $aAccelerators[$sClassName]['attributes']));
                }
                $sFullTextJS = addslashes($sFullText);
                $bEnableEnlarge = array_key_exists($sClassName, $aAccelerators) && array_key_exists('query', $aAccelerators[$sClassName]);
                if (array_key_exists($sClassName, $aAccelerators) && array_key_exists('enable_enlarge', $aAccelerators[$sClassName])) {
                    $bEnableEnlarge &= $aAccelerators[$sClassName]['enable_enlarge'];
                }
                $sEnlargeTheSearch = <<<EOF
\t\t\t\$('.search-class-{$sClassName} button').attr('disabled', 'disabled');

\t\t\t\$('.search-class-{$sClassName} h2').append('&nbsp;<img id="indicator" src="../images/indicator.gif">');
\t\t\tvar oParams = {operation: 'full_text_search_enlarge', class: '{$sClassName}', text: '{$sFullTextJS}'};
\t\t\t\$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function(data) {
\t\t\t\t\$('.search-class-{$sClassName}').html(data);
\t\t\t});
EOF;
 /**
  * Prepare the given object set with the list of fields as read into $this->aStatusInfo['fields']
  */
 protected function OptimizeColumnLoad(DBObjectSet $oSet)
 {
     $aColumnsToLoad = array();
     foreach ($this->aStatusInfo['fields'] as $iCol => $aFieldSpec) {
         $sClass = $aFieldSpec['sClass'];
         $sAlias = $aFieldSpec['sAlias'];
         $sAttCode = $aFieldSpec['sAttCode'];
         if (!array_key_exists($sAlias, $aColumnsToLoad)) {
             $aColumnsToLoad[$sAlias] = array();
         }
         // id is not a real attribute code and, moreover, is always loaded
         if ($sAttCode != 'id') {
             // Extended attributes are not recognized by DBObjectSet::OptimizeColumnLoad
             if (($iPos = strpos($sAttCode, '->')) === false) {
                 $aColumnsToLoad[$sAlias][] = $sAttCode;
                 $sClass = '???';
             } else {
                 $sExtKeyAttCode = substr($sAttCode, 0, $iPos);
                 $sRemoteAttCode = substr($sAttCode, $iPos + 2);
                 // Load the external key to avoid an object reload!
                 $aColumnsToLoad[$sAlias][] = $sExtKeyAttCode;
                 // Load the external field (if any) to avoid getting the remote object (see DBObject::Get that does the same)
                 $oExtFieldAtt = MetaModel::FindExternalField($sClass, $sExtKeyAttCode, $sRemoteAttCode);
                 if (!is_null($oExtFieldAtt)) {
                     $aColumnsToLoad[$sAlias][] = $oExtFieldAtt->GetCode();
                 }
             }
         }
     }
     // Add "always loaded attributes"
     //
     $aSelectedClasses = $this->oSearch->GetSelectedClasses();
     foreach ($aSelectedClasses as $sAlias => $sClass) {
         foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) {
             if ($oAttDef->AlwaysLoadInTables()) {
                 $aColumnsToLoad[$sAlias][] = $sAttCode;
             }
         }
     }
     $oSet->OptimizeColumnLoad($aColumnsToLoad);
 }
 public function GetNextChunk(&$aStatus)
 {
     $sRetCode = 'run';
     $iPercentage = 0;
     $oSet = new DBObjectSet($this->oSearch);
     $aSelectedClasses = $this->oSearch->GetSelectedClasses();
     $aAliases = array_keys($aSelectedClasses);
     $oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
     $aAliasByField = array();
     $aColumnsToLoad = array();
     // Prepare the list of aliases / columns to load
     foreach ($this->aStatusInfo['fields'] as $sExtendedAttCode) {
         if (preg_match('/^([^\\.]+)\\.(.+)$/', $sExtendedAttCode, $aMatches)) {
             $sAlias = $aMatches[1];
             $sAttCode = $aMatches[2];
         } else {
             $sAlias = reset($aAliases);
             $sAttCode = $sExtendedAttCode;
         }
         if (!in_array($sAlias, $aAliases)) {
             throw new Exception("Invalid alias '{$sAlias}' for the column '{$sExtendedAttCode}'. Availables aliases: '" . implode("', '", $aAliases) . "'");
         }
         if (!array_key_exists($sAlias, $aColumnsToLoad)) {
             $aColumnsToLoad[$sAlias] = array();
         }
         if ($sAttCode != 'id') {
             // id is not a real attribute code and, moreover, is always loaded
             $aColumnsToLoad[$sAlias][] = $sAttCode;
         }
         $aAliasByField[$sExtendedAttCode] = array('alias' => $sAlias, 'attcode' => $sAttCode);
     }
     $iCount = 0;
     $sData = '';
     $oSet->OptimizeColumnLoad($aColumnsToLoad);
     $iPreviousTimeLimit = ini_get('max_execution_time');
     $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
     while ($aRow = $oSet->FetchAssoc()) {
         set_time_limit($iLoopTimeLimit);
         $sFirstAlias = reset($aAliases);
         $sHilightClass = $aRow[$sFirstAlias]->GetHilightClass();
         if ($sHilightClass != '') {
             $sData .= "<tr class=\"{$sHilightClass}\">";
         } else {
             $sData .= "<tr>";
         }
         foreach ($aAliasByField as $aAttCode) {
             $sField = '';
             switch ($aAttCode['attcode']) {
                 case 'id':
                     $sField = $aRow[$aAttCode['alias']]->GetHyperlink();
                     break;
                 default:
                     $sField = $aRow[$aAttCode['alias']]->GetAsHtml($aAttCode['attcode']);
             }
             $sValue = $sField === '' ? '&nbsp;' : $sField;
             $sData .= "<td>{$sValue}</td>";
         }
         $sData .= "</tr>";
         $iCount++;
     }
     set_time_limit($iPreviousTimeLimit);
     $this->aStatusInfo['position'] += $this->iChunkSize;
     if ($this->aStatusInfo['total'] == 0) {
         $iPercentage = 100;
     } else {
         $iPercentage = floor(min(100.0, 100.0 * $this->aStatusInfo['position'] / $this->aStatusInfo['total']));
     }
     if ($iCount < $this->iChunkSize) {
         $sRetCode = 'done';
     }
     $aStatus = array('code' => $sRetCode, 'message' => Dict::S('Core:BulkExport:RetrievingData'), 'percentage' => $iPercentage);
     return $sData;
 }
Example #5
0
 /**
  * Helper to remove selected objects without calling any handler
  * Surpasses BulkDelete as it can handle abstract classes, but has the other limitation as it bypasses standard objects handlers 
  * 	 
  * @param string $oFilter Scope of objects to wipe out
  * @return The count of deleted objects
  */
 public static function PurgeData($oFilter)
 {
     $sTargetClass = $oFilter->GetClass();
     $oSet = new DBObjectSet($oFilter);
     $oSet->OptimizeColumnLoad(array($sTargetClass => array('finalclass')));
     $aIdToClass = $oSet->GetColumnAsArray('finalclass', true);
     $aIds = array_keys($aIdToClass);
     if (count($aIds) > 0) {
         $aQuotedIds = CMDBSource::Quote($aIds);
         $sIdList = implode(',', $aQuotedIds);
         $aTargetClasses = array_merge(self::EnumChildClasses($sTargetClass, ENUM_CHILD_CLASSES_ALL), self::EnumParentClasses($sTargetClass, ENUM_PARENT_CLASSES_EXCLUDELEAF));
         foreach ($aTargetClasses as $sSomeClass) {
             $sTable = MetaModel::DBGetTable($sSomeClass);
             $sPKField = MetaModel::DBGetKey($sSomeClass);
             $sDeleteSQL = "DELETE FROM `{$sTable}` WHERE `{$sPKField}` IN ({$sIdList})";
             CMDBSource::DeleteFrom($sDeleteSQL);
         }
     }
     return count($aIds);
 }
 public function GetNextChunk(&$aStatus)
 {
     $sRetCode = 'run';
     $iPercentage = 0;
     $oSet = new DBObjectSet($this->oSearch);
     $aSelectedClasses = $this->oSearch->GetSelectedClasses();
     $oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
     $aAliasByField = array();
     $aColumnsToLoad = array();
     // Prepare the list of aliases / columns to load
     foreach ($this->aStatusInfo['fields'] as $sExtendedAttCode) {
         if (preg_match('/^([^\\.]+)\\.(.+)$/', $sExtendedAttCode, $aMatches)) {
             $sAlias = $aMatches[1];
             $sAttCode = $aMatches[2];
         } else {
             $sAlias = reset($aSelectedClasses);
             $sAttCode = $sExtendedAttCode;
         }
         if (!array_key_exists($sAlias, $aSelectedClasses)) {
             throw new Exception("Invalid alias '{$sAlias}' for the column '{$sExtendedAttCode}'. Availables aliases: '" . implode("', '", array_keys($aSelectedClasses)) . "'");
         }
         if (!array_key_exists($sAlias, $aColumnsToLoad)) {
             $aColumnsToLoad[$sAlias] = array();
         }
         if ($sAttCode != 'id') {
             // id is not a real attribute code and, moreover, is always loaded
             $aColumnsToLoad[$sAlias][] = $sAttCode;
         }
         $aAliasByField[$sExtendedAttCode] = array('alias' => $sAlias, 'attcode' => $sAttCode);
     }
     $iCount = 0;
     $sData = '';
     $oSet->OptimizeColumnLoad($aColumnsToLoad);
     $iPreviousTimeLimit = ini_get('max_execution_time');
     $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
     while ($aRow = $oSet->FetchAssoc()) {
         set_time_limit($iLoopTimeLimit);
         $sData .= "<tr>";
         foreach ($aAliasByField as $aAttCode) {
             $sField = '';
             $oObj = $aRow[$aAttCode['alias']];
             if ($oObj == null) {
                 $sData .= "<td x:str>{$sField}</td>";
                 continue;
             }
             switch ($aAttCode['attcode']) {
                 case 'id':
                     $sField = $oObj->GetName();
                     $sData .= "<td>{$sField}</td>";
                     break;
                 default:
                     $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $aAttCode['attcode']);
                     $oFinalAttDef = $oAttDef->GetFinalAttDef();
                     if (get_class($oFinalAttDef) == 'AttributeDateTime') {
                         $iDate = AttributeDateTime::GetAsUnixSeconds($oObj->Get($aAttCode['attcode']));
                         $sData .= '<td>' . date('Y-m-d', $iDate) . '</td>';
                         // Add the first column directly
                         $sField = date('H:i:s', $iDate);
                         // Will add the second column below
                         $sData .= "<td>{$sField}</td>";
                     } else {
                         if ($oAttDef instanceof AttributeCaseLog) {
                             $rawValue = $oObj->Get($aAttCode['attcode']);
                             $sField = str_replace("\n", "<br/>", htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8'));
                             // Trick for Excel: treat the content as text even if it begins with an equal sign
                             $sData .= "<td x:str>{$sField}</td>";
                         } else {
                             $rawValue = $oObj->Get($aAttCode['attcode']);
                             // Due to custom formatting rules, empty friendlynames may be rendered as non-empty strings
                             // let's fix this and make sure we render an empty string if the key == 0
                             if ($oAttDef instanceof AttributeFriendlyName) {
                                 $sKeyAttCode = $oAttDef->GetKeyAttCode();
                                 if ($sKeyAttCode != 'id') {
                                     if ($oObj->Get($sKeyAttCode) == 0) {
                                         $sValue = '';
                                     }
                                 }
                             }
                             if ($this->aStatusInfo['localize']) {
                                 $sField = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8');
                             } else {
                                 $sField = htmlentities($rawValue, ENT_QUOTES, 'UTF-8');
                             }
                             $sData .= "<td>{$sField}</td>";
                         }
                     }
             }
         }
         $sData .= "</tr>";
         $iCount++;
     }
     set_time_limit($iPreviousTimeLimit);
     $this->aStatusInfo['position'] += $this->iChunkSize;
     if ($this->aStatusInfo['total'] == 0) {
         $iPercentage = 100;
     } else {
         $iPercentage = floor(min(100.0, 100.0 * $this->aStatusInfo['position'] / $this->aStatusInfo['total']));
     }
     if ($iCount < $this->iChunkSize) {
         $sRetCode = 'done';
     }
     $aStatus = array('code' => $sRetCode, 'message' => Dict::S('Core:BulkExport:RetrievingData'), 'percentage' => $iPercentage);
     return $sData;
 }
Example #7
0
 /**
  * Interprets the results posted by a normal or paginated list (in multiple selection mode)
  * @param $oFullSetFilter DBSearch The criteria defining the whole sets of objects being selected
  * @return Array An arry of object IDs corresponding to the objects selected in the set
  */
 public static function ReadMultipleSelection($oFullSetFilter)
 {
     $aSelectedObj = utils::ReadParam('selectObject', array());
     $sSelectionMode = utils::ReadParam('selectionMode', '');
     if ($sSelectionMode != '') {
         // Paginated selection
         $aExceptions = utils::ReadParam('storedSelection', array());
         if ($sSelectionMode == 'positive') {
             // Only the explicitely listed items are selected
             $aSelectedObj = $aExceptions;
         } else {
             // All items of the set are selected, except the one explicitely listed
             $aSelectedObj = array();
             $oFullSet = new DBObjectSet($oFullSetFilter);
             $sClassAlias = $oFullSetFilter->GetClassAlias();
             $oFullSet->OptimizeColumnLoad(array($sClassAlias => array('friendlyname')));
             // We really need only the IDs but it does not work since id is not a real field
             while ($oObj = $oFullSet->Fetch()) {
                 if (!in_array($oObj->GetKey(), $aExceptions)) {
                     $aSelectedObj[] = $oObj->GetKey();
                 }
             }
         }
     }
     return $aSelectedObj;
 }
 public function GetNextChunk(&$aStatus)
 {
     $sRetCode = 'run';
     $iPercentage = 0;
     $hFile = fopen($this->aStatusInfo['tmp_file'], 'ab');
     $oSet = new DBObjectSet($this->oSearch);
     $aSelectedClasses = $this->oSearch->GetSelectedClasses();
     $aAliases = array_keys($aSelectedClasses);
     $oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
     $aAliasByField = array();
     $aColumnsToLoad = array();
     // Prepare the list of aliases / columns to load
     foreach ($this->aStatusInfo['fields'] as $sExtendedAttCode) {
         if (preg_match('/^([^\\.]+)\\.(.+)$/', $sExtendedAttCode, $aMatches)) {
             $sAlias = $aMatches[1];
             $sAttCode = $aMatches[2];
         } else {
             $sAlias = reset($aAliases);
             $sAttCode = $sExtendedAttCode;
         }
         if (!in_array($sAlias, $aAliases)) {
             throw new Exception("Invalid alias '{$sAlias}' for the column '{$sExtendedAttCode}'. Availables aliases: '" . implode("', '", $aAliases) . "'");
         }
         if (!array_key_exists($sAlias, $aColumnsToLoad)) {
             $aColumnsToLoad[$sAlias] = array();
         }
         if ($sAttCode != 'id') {
             // id is not a real attribute code and, moreover, is always loaded
             $aColumnsToLoad[$sAlias][] = $sAttCode;
         }
         $aAliasByField[$sExtendedAttCode] = array('alias' => $sAlias, 'attcode' => $sAttCode);
     }
     $iCount = 0;
     $oSet->OptimizeColumnLoad($aColumnsToLoad);
     $iPreviousTimeLimit = ini_get('max_execution_time');
     $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
     while ($aRow = $oSet->FetchAssoc()) {
         set_time_limit($iLoopTimeLimit);
         $aData = array();
         foreach ($aAliasByField as $aAttCode) {
             $sField = '';
             switch ($aAttCode['attcode']) {
                 case 'id':
                     $sField = $aRow[$aAttCode['alias']]->GetKey();
                     break;
                 default:
                     $value = $aRow[$aAttCode['alias']]->Get($aAttCode['attcode']);
                     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!
                         $sField = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $value->GetText()));
                     } else {
                         if ($value instanceof DBObjectSet) {
                             $oAttDef = MetaModel::GetAttributeDef(get_class($aRow[$aAttCode['alias']]), $aAttCode['attcode']);
                             $sField = $oAttDef->GetAsCSV($value, '', '', $aRow[$aAttCode['alias']]);
                         } else {
                             $oAttDef = MetaModel::GetAttributeDef(get_class($aRow[$aAttCode['alias']]), $aAttCode['attcode']);
                             $sField = $oAttDef->GetEditValue($value, $aRow[$aAttCode['alias']]);
                         }
                     }
             }
             $aData[] = $sField;
         }
         fwrite($hFile, json_encode($aData) . "\n");
         $iCount++;
     }
     set_time_limit($iPreviousTimeLimit);
     $this->aStatusInfo['position'] += $this->iChunkSize;
     if ($this->aStatusInfo['total'] == 0) {
         $iPercentage = 100;
         $sRetCode = 'done';
         // Next phase (GetFooter) will be to build the xlsx file
     } else {
         $iPercentage = floor(min(100.0, 100.0 * $this->aStatusInfo['position'] / $this->aStatusInfo['total']));
     }
     if ($iCount < $this->iChunkSize) {
         $sRetCode = 'done';
     }
     $aStatus = array('code' => $sRetCode, 'message' => Dict::S('Core:BulkExport:RetrievingData'), 'percentage' => $iPercentage);
     return '';
     // The actual XLSX file is built in GetFooter();
 }
 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);
 }