/**
  * Spreadsheet output: designed for end users doing some reporting
  * Then the ids are excluded and replaced by the corresponding friendlyname
  */
 static function GetSetAsHTMLSpreadsheet(DBObjectSet $oSet, $aParams = array())
 {
     $aFields = null;
     if (isset($aParams['fields']) && strlen($aParams['fields']) > 0) {
         $aFields = explode(',', $aParams['fields']);
     }
     $bFieldsAdvanced = false;
     if (isset($aParams['fields_advanced'])) {
         $bFieldsAdvanced = (bool) $aParams['fields_advanced'];
     }
     $bLocalize = true;
     if (isset($aParams['localize_values'])) {
         $bLocalize = (bool) $aParams['localize_values'];
     }
     $aList = array();
     $oAppContext = new ApplicationContext();
     $aClasses = $oSet->GetFilter()->GetSelectedClasses();
     $aAuthorizedClasses = array();
     foreach ($aClasses as $sAlias => $sClassName) {
         if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS)) {
             $aAuthorizedClasses[$sAlias] = $sClassName;
         }
     }
     $aAttribs = array();
     $aHeader = array();
     foreach ($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;
                     $aList[$sAlias][$sAttCodeEx] = $oAttDef;
                     if ($bFieldsAdvanced && $oAttDef->IsExternalKey(EXTKEY_RELATIVE)) {
                         $sRemoteClass = $oAttDef->GetTargetClass();
                         foreach (MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) {
                             $aList[$sAlias][$sAttCode . '->' . $sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, $sRemoteAttCode);
                         }
                     }
                 }
             } else {
                 // User defined list of attributes
                 if (in_array($sAttCode, $aFields) || in_array($sAlias . '.' . $sAttCode, $aFields)) {
                     $aList[$sAlias][$sAttCode] = $oAttDef;
                 }
             }
         }
         // Replace external key by the corresponding friendly name (if not already in the list)
         foreach ($aList[$sAlias] as $sAttCode => $oAttDef) {
             if ($oAttDef->IsExternalKey()) {
                 unset($aList[$sAlias][$sAttCode]);
                 $sFriendlyNameAttCode = $sAttCode . '_friendlyname';
                 if (!array_key_exists($sFriendlyNameAttCode, $aList[$sAlias]) && MetaModel::IsValidAttCode($sClassName, $sFriendlyNameAttCode)) {
                     $oFriendlyNameAtt = MetaModel::GetAttributeDef($sClassName, $sFriendlyNameAttCode);
                     $aList[$sAlias][$sFriendlyNameAttCode] = $oFriendlyNameAtt;
                 }
             }
         }
         foreach ($aList[$sAlias] as $sAttCodeEx => $oAttDef) {
             $sColLabel = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx) : $sAttCodeEx;
             $oFinalAttDef = $oAttDef->GetFinalAttDef();
             if (get_class($oFinalAttDef) == 'AttributeDateTime') {
                 $aHeader[] = $sColLabel . ' (' . Dict::S('UI:SplitDateTime-Date') . ')';
                 $aHeader[] = $sColLabel . ' (' . Dict::S('UI:SplitDateTime-Time') . ')';
             } else {
                 $aHeader[] = $sColLabel;
             }
         }
     }
     $sHtml = "<table border=\"1\">\n";
     $sHtml .= "<tr>\n";
     $sHtml .= "<td>" . implode("</td><td>", $aHeader) . "</td>\n";
     $sHtml .= "</tr>\n";
     $oSet->Seek(0);
     while ($aObjects = $oSet->FetchAssoc()) {
         $aRow = array();
         foreach ($aAuthorizedClasses as $sAlias => $sClassName) {
             $oObj = $aObjects[$sAlias];
             foreach ($aList[$sAlias] as $sAttCodeEx => $oAttDef) {
                 if (is_null($oObj)) {
                     $aRow[] = '<td></td>';
                 } else {
                     $oFinalAttDef = $oAttDef->GetFinalAttDef();
                     if (get_class($oFinalAttDef) == 'AttributeDateTime') {
                         $sDate = $oObj->Get($sAttCodeEx);
                         if ($sDate === null) {
                             $aRow[] = '<td></td>';
                             $aRow[] = '<td></td>';
                         } else {
                             $iDate = AttributeDateTime::GetAsUnixSeconds($sDate);
                             $aRow[] = '<td>' . date('Y-m-d', $iDate) . '</td>';
                             $aRow[] = '<td>' . date('H:i:s', $iDate) . '</td>';
                         }
                     } else {
                         if ($oAttDef instanceof AttributeCaseLog) {
                             $rawValue = $oObj->Get($sAttCodeEx);
                             $outputValue = 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
                             $aRow[] = '<td x:str>' . $outputValue . '</td>';
                         } else {
                             $rawValue = $oObj->Get($sAttCodeEx);
                             // 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) {
                                         $rawValue = '';
                                     }
                                 }
                             }
                             if ($bLocalize) {
                                 $outputValue = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8');
                             } else {
                                 $outputValue = htmlentities($rawValue, ENT_QUOTES, 'UTF-8');
                             }
                             $aRow[] = '<td>' . $outputValue . '</td>';
                         }
                     }
                 }
             }
         }
         $sHtml .= implode("\n", $aRow);
         $sHtml .= "</tr>\n";
     }
     $sHtml .= "</table>\n";
     return $sHtml;
 }