/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <sal/config.h>
#include <string_view>
#include <editsh.hxx>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/container/XContentEnumerationAccess.hpp>
#include <com/sun/star/document/XActionLockable.hpp>
#include <com/sun/star/document/XDocumentProperties.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/drawing/HomogenMatrix3.hpp>
#include <com/sun/star/drawing/LineStyle.hpp>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
#include <com/sun/star/text/RelOrientation.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <com/sun/star/text/VertOrientation.hpp>
#include <com/sun/star/text/WrapTextMode.hpp>
#include <com/sun/star/text/XTextContent.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
#include <com/sun/star/text/XTextField.hpp>
#include <com/sun/star/text/XTextRange.hpp>
#include <com/sun/star/text/XParagraphAppend.hpp>
#include <com/sun/star/text/XParagraphCursor.hpp>
#include <com/sun/star/awt/FontWeight.hpp>
#include <com/sun/star/rdf/XMetadatable.hpp>
#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
#include <com/sun/star/security/XCertificate.hpp>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <comphelper/propertysequence.hxx>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/scopeguard.hxx>
#include <comphelper/string.hxx>
#include <editeng/unoprnms.hxx>
#include <sfx2/classificationhelper.hxx>
#include <svx/ClassificationCommon.hxx>
#include <svx/ClassificationField.hxx>
#include <svl/cryptosign.hxx>
#include <svl/sigstruct.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <vcl/virdev.hxx>
#include <redline.hxx>
#include <poolfmt.hxx>
#include <hintids.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <ndtxt.hxx>
#include <paratr.hxx>
#include <viewopt.hxx>
#include <SwRewriter.hxx>
#include <numrule.hxx>
#include <swundo.hxx>
#include <docary.hxx>
#include <docsh.hxx>
#include <unoprnms.hxx>
#include <rootfrm.hxx>
#include <pagefrm.hxx>
#include <txtfrm.hxx>
#include <rdfhelper.hxx>
#include <sfx2/watermarkitem.hxx>
#include <unoparagraph.hxx>
#include <strings.hrc>
#include <undobj.hxx>
#include <UndoParagraphSignature.hxx>
#include <txtatr.hxx>
#include <fmtmeta.hxx>
#include <unotxdoc.hxx>
#include <unotextbodyhf.hxx>
#include <unoport.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <IDocumentRedlineAccess.hxx>
constexpr OUString WATERMARK_NAME = u"PowerPlusWaterMarkObject"_ustr;
#define WATERMARK_AUTO_SIZE sal_uInt32(1)
namespace
{
constexpr OUString MetaFilename(u"tscp/bails.rdf"_ustr);
constexpr OUString MetaNS(u"urn:bails"_ustr);
constexpr OUString ParagraphSignatureRDFNamespace = u"urn:bails:loext:paragraph:signature:"_ustr;
constexpr OUString ParagraphSignatureIdRDFName = u"urn:bails:loext:paragraph:signature:id"_ustr;
constexpr OUString ParagraphSignatureDigestRDFName = u":digest"_ustr;
constexpr OUString ParagraphSignatureDateRDFName = u":date"_ustr;
constexpr OUString ParagraphSignatureUsageRDFName = u":usage"_ustr;
constexpr OUString ParagraphSignatureLastIdRDFName = u"urn:bails:loext:paragraph:signature:lastid"_ustr;
constexpr OUString ParagraphClassificationNameRDFName = u"urn:bails:loext:paragraph:classification:name"_ustr;
constexpr OUString ParagraphClassificationValueRDFName = u"urn:bails:loext:paragraph:classification:value"_ustr;
constexpr OUString ParagraphClassificationAbbrRDFName = u"urn:bails:loext:paragraph:classification:abbreviation"_ustr;
constexpr OUString ParagraphClassificationFieldNamesRDFName = u"urn:bails:loext:paragraph:classification:fields"_ustr;
constexpr OUString MetadataFieldServiceName = u"com.sun.star.text.textfield.MetadataField"_ustr;
constexpr OUString DocInfoServiceName = u"com.sun.star.text.TextField.DocInfo.Custom"_ustr;
/// Find all page styles which are currently used in the document.
std::vector<OUString> lcl_getUsedPageStyles(SwViewShell const * pShell)
{
std::vector<OUString> aReturn;
SwRootFrame* pLayout = pShell->GetLayout();
for (SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext())
{
SwPageFrame* pPage = static_cast<SwPageFrame*>(pFrame);
if (const SwPageDesc *pDesc = pPage->FindPageDesc())
aReturn.push_back(pDesc->GetName());
}
return aReturn;
}
/// Search for a field named rFieldName of type rServiceName in xText and return it.
uno::Reference<text::XTextField> lcl_findField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, std::u16string_view rFieldName)
{
uno::Reference<text::XTextField> xField;
uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY);
uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
while (xParagraphs->hasMoreElements())
{
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
while (xTextPortions->hasMoreElements())
{
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
OUString aTextPortionType;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
continue;
uno::Reference<lang::XServiceInfo> xTextField;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
if (!xTextField->supportsService(rServiceName))
continue;
OUString aName;
uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
if (aName == rFieldName)
{
xField = uno::Reference<text::XTextField>(xTextField, uno::UNO_QUERY);
break;
}
}
}
return xField;
}
/// Search for a field named rFieldName of type rServiceName in xText and return true iff found.
bool lcl_hasField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, std::u16string_view rFieldName)
{
return lcl_findField(xText, rServiceName, rFieldName).is();
}
/// Search for a frame with WATERMARK_NAME in name of type rServiceName in xText. Returns found name in rShapeName.
uno::Reference<drawing::XShape> lcl_getWatermark(const uno::Reference<text::XText>& xText,
const OUString& rServiceName, OUString& rShapeName, bool& bSuccess)
{
bSuccess = false;
uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY);
uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
while (xParagraphs->hasMoreElements())
{
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
if (!xTextPortionEnumerationAccess.is())
continue;
bSuccess = true;
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
while (xTextPortions->hasMoreElements())
{
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
OUString aTextPortionType;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
if (aTextPortionType != "Frame")
continue;
uno::Reference<container::XContentEnumerationAccess> xContentEnumerationAccess(xTextPortion, uno::UNO_QUERY);
if (!xContentEnumerationAccess.is())
continue;
uno::Reference<container::XEnumeration> xEnumeration = xContentEnumerationAccess->createContentEnumeration(u"com.sun.star.text.TextContent"_ustr);
if (!xEnumeration->hasMoreElements())
continue;
uno::Reference<lang::XServiceInfo> xWatermark(xEnumeration->nextElement(), uno::UNO_QUERY);
if (!xWatermark->supportsService(rServiceName))
continue;
uno::Reference<container::XNamed> xNamed(xWatermark, uno::UNO_QUERY);
if (!xNamed->getName().match(WATERMARK_NAME))
continue;
rShapeName = xNamed->getName();
uno::Reference<drawing::XShape> xShape(xWatermark, uno::UNO_QUERY);
return xShape;
}
}
return uno::Reference<drawing::XShape>();
}
/// Extract the text of the paragraph without any of the fields.
/// TODO: Consider moving to SwTextNode, or extend ModelToViewHelper.
OString lcl_getParagraphBodyText(const uno::Reference<text::XTextContent>& xText)
{
OUStringBuffer strBuf;
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xText, uno::UNO_QUERY);
if (!xTextPortionEnumerationAccess.is())
return OString();
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
while (xTextPortions->hasMoreElements())
{
uno::Any elem = xTextPortions->nextElement();
//TODO: Consider including hidden and conditional texts/portions.
OUString aTextPortionType;
uno::Reference<beans::XPropertySet> xPropertySet(elem, uno::UNO_QUERY);
xPropertySet->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
if (aTextPortionType == "Text")
{
uno::Reference<text::XTextRange> xTextRange(elem, uno::UNO_QUERY);
if (xTextRange.is())
strBuf.append(xTextRange->getString());
}
}
// Cleanup the dummy characters added by fields (which we exclude).
comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDSTART);
comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDEND);
comphelper::string::remove(strBuf, CH_TXTATR_BREAKWORD);
return strBuf.makeStringAndClear().trim().toUtf8();
}
template <typename T>
std::map<OUString, OUString> lcl_getRDFStatements(const rtl::Reference<SwXTextDocument>& xModel,
const T& xRef)
{
try
{
const css::uno::Reference<css::rdf::XResource> xSubject(xRef, uno::UNO_QUERY);
return SwRDFHelper::getStatements(xModel, MetaNS, xSubject);
}
catch (const ::css::uno::Exception&)
{
}
return std::map<OUString, OUString>();
}
/// Returns RDF (key, value) pair associated with the field, if any.
std::pair<OUString, OUString> lcl_getFieldRDFByPrefix(const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<css::text::XTextField>& xField,
std::u16string_view sPrefix)
{
for (const auto& pair : lcl_getRDFStatements(xModel, xField))
{
if (pair.first.startsWith(sPrefix))
return pair;
}
return std::make_pair(OUString(), OUString());
}
/// Returns RDF (key, value) pair associated with the field, if any.
template <typename T>
std::pair<OUString, OUString> lcl_getRDF(const rtl::Reference<SwXTextDocument>& xModel,
const T& xRef,
const OUString& sRDFName)
{
const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xRef);
const auto it = aStatements.find(sRDFName);
return (it != aStatements.end()) ? std::make_pair(it->first, it->second) : std::make_pair(OUString(), OUString());
}
/// Returns true iff the field in question is paragraph signature.
/// Note: must have associated RDF, since signatures are otherwise just metadata fields.
bool lcl_IsParagraphSignatureField(const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<css::text::XTextField>& xField)
{
return (lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).first == ParagraphSignatureIdRDFName);
}
uno::Reference<text::XTextField> lcl_findFieldByRDF(const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<text::XTextContent>& xParagraph,
const OUString& sRDFName,
std::u16string_view sRDFValue)
{
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
if (!xTextPortionEnumerationAccess.is())
return uno::Reference<text::XTextField>();
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
if (!xTextPortions.is())
return uno::Reference<text::XTextField>();
while (xTextPortions->hasMoreElements())
{
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
OUString aTextPortionType;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
continue;
uno::Reference<lang::XServiceInfo> xTextField;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
if (!xTextField->supportsService(MetadataFieldServiceName))
continue;
uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xField, sRDFName);
if (pair.first == sRDFName && (sRDFValue.empty() || sRDFValue == pair.second))
return xField;
}
return uno::Reference<text::XTextField>();
}
struct SignatureDescr
{
OUString msSignature;
OUString msUsage;
OUString msDate;
bool isValid() const { return !msSignature.isEmpty(); }
};
SignatureDescr lcl_getSignatureDescr(const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<css::text::XTextContent>& xParagraph,
std::u16string_view sFieldId)
{
SignatureDescr aDescr;
const OUString prefix = ParagraphSignatureRDFNamespace + sFieldId;
const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xParagraph);
const auto itSig = aStatements.find(prefix + ParagraphSignatureDigestRDFName);
aDescr.msSignature = (itSig != aStatements.end() ? itSig->second : OUString());
const auto itDate = aStatements.find(prefix + ParagraphSignatureDateRDFName);
aDescr.msDate = (itDate != aStatements.end() ? itDate->second : OUString());
const auto itUsage = aStatements.find(prefix + ParagraphSignatureUsageRDFName);
aDescr.msUsage = (itUsage != aStatements.end() ? itUsage->second : OUString());
return aDescr;
}
SignatureDescr lcl_getSignatureDescr(const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<css::text::XTextContent>& xParagraph,
const uno::Reference<css::text::XTextField>& xField)
{
const OUString sFieldId = lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).second;
if (!sFieldId.isEmpty())
return lcl_getSignatureDescr(xModel, xParagraph, sFieldId);
return SignatureDescr();
}
/// Validate and create the signature field display text from the fields.
std::pair<bool, OUString> lcl_MakeParagraphSignatureFieldText(const SignatureDescr& aDescr,
const OString& utf8Text)
{
OUString msg = SwResId(STR_INVALID_SIGNATURE);
bool valid = false;
if (aDescr.isValid())
{
const char* pData = utf8Text.getStr();
const std::vector<unsigned char> data(pData, pData + utf8Text.getLength());
OString encSignature;
if (aDescr.msSignature.convertToString(&encSignature, RTL_TEXTENCODING_UTF8, 0))
{
const std::vector<unsigned char> sig(svl::crypto::DecodeHexString(encSignature));
SignatureInformation aInfo(0);
valid = svl::crypto::Signing::Verify(data, false, sig, aInfo);
valid = valid
&& aInfo.nStatus == xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;
assert(aInfo.GetSigningCertificate()); // it was valid
msg = SwResId(STR_SIGNED_BY) + ": " + aInfo.GetSigningCertificate()->X509Subject + ", " +
aDescr.msDate;
msg += (!aDescr.msUsage.isEmpty() ? (" (" + aDescr.msUsage + "): ") : u": "_ustr);
msg += (valid ? SwResId(STR_VALID) : SwResId(STR_INVALID));
}
}
return std::make_pair(valid, msg);
}
/// Validate and return validation result and signature field display text.
std::pair<bool, OUString>
lcl_MakeParagraphSignatureFieldText(const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<css::text::XTextContent>& xParagraph,
const uno::Reference<css::text::XTextField>& xField,
const OString& utf8Text)
{
const SignatureDescr aDescr = lcl_getSignatureDescr(xModel, xParagraph, xField);
return lcl_MakeParagraphSignatureFieldText(aDescr, utf8Text);
}
/// Generate the next valid ID for the new signature on this paragraph.
OUString lcl_getNextSignatureId(const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<text::XTextContent>& xParagraph)
{
const OUString sFieldId = lcl_getRDF(xModel, xParagraph, ParagraphSignatureLastIdRDFName).second;
return OUString::number(!sFieldId.isEmpty() ? sFieldId.toInt32() + 1 : 1);
}
/// Creates and inserts Paragraph Signature Metadata field and creates the RDF entry
uno::Reference<text::XTextField> lcl_InsertParagraphSignature(const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<text::XTextContent>& xParagraph,
const OUString& signature,
const OUString& usage)
{
uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel);
auto xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);
// Add the signature at the end.
xField->attach(xParagraph->getAnchor()->getEnd());
const OUString sId = lcl_getNextSignatureId(xModel, xParagraph);
const css::uno::Reference<css::rdf::XResource> xSubject(xField, uno::UNO_QUERY);
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xSubject, ParagraphSignatureIdRDFName, sId);
// First convert the UTC UNIX timestamp to a tools::DateTime then to local time.
DateTime aDateTime = DateTime::CreateFromUnixTime(time(nullptr));
aDateTime.ConvertToLocalTime();
OUStringBuffer rBuffer;
rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear()));
rBuffer.append('-');
if (aDateTime.GetMonth() < 10)
rBuffer.append('0');
rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth()));
rBuffer.append('-');
if (aDateTime.GetDay() < 10)
rBuffer.append('0');
rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay()));
// Now set the RDF on the paragraph, since that's what is preserved in .doc(x).
const css::uno::Reference<css::rdf::XResource> xParaSubject(xParagraph, uno::UNO_QUERY);
const OUString prefix = ParagraphSignatureRDFNamespace + sId;
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, ParagraphSignatureLastIdRDFName, sId);
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDigestRDFName, signature);
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureUsageRDFName, usage);
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDateRDFName, rBuffer.makeStringAndClear());
return xField;
}
/// Updates the signature field text if changed and returns true only iff updated.
bool lcl_DoUpdateParagraphSignatureField(SwDoc& rDoc,
const uno::Reference<css::text::XTextField>& xField,
const OUString& sDisplayText)
{
// Disable undo to avoid introducing noise when we edit the metadata field.
const bool isUndoEnabled = rDoc.GetIDocumentUndoRedo().DoesUndo();
rDoc.GetIDocumentUndoRedo().DoUndo(false);
comphelper::ScopeGuard const g([&rDoc, isUndoEnabled]() {
rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
});
try
{
uno::Reference<css::text::XTextRange> xText(xField, uno::UNO_QUERY);
const OUString curText = xText->getString();
if (curText != sDisplayText)
{
xText->setString(sDisplayText);
return true;
}
}
catch (const uno::Exception&)
{
// We failed; avoid crashing.
DBG_UNHANDLED_EXCEPTION("sw.uno", "Failed to update paragraph signature");
}
return false;
}
/// Updates the signature field text if changed and returns true only iff updated.
bool lcl_UpdateParagraphSignatureField(SwDoc& rDoc,
const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<css::text::XTextContent>& xParagraph,
const uno::Reference<css::text::XTextField>& xField,
const OString& utf8Text)
{
const OUString sDisplayText
= lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).second;
return lcl_DoUpdateParagraphSignatureField(rDoc, xField, sDisplayText);
}
void lcl_RemoveParagraphMetadataField(const uno::Reference<css::text::XTextField>& xField)
{
uno::Reference<css::text::XTextRange> xParagraph(xField->getAnchor());
xParagraph->getText()->removeTextContent(xField);
}
/// Returns true iff the field in question is paragraph classification.
/// Note: must have associated RDF, since classifications are otherwise just metadata fields.
bool lcl_IsParagraphClassificationField(const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<css::text::XTextField>& xField,
std::u16string_view sKey)
{
const std::pair<OUString, OUString> rdfPair = lcl_getRDF(xModel, xField, ParagraphClassificationNameRDFName);
return rdfPair.first == ParagraphClassificationNameRDFName && (sKey.empty() || rdfPair.second == sKey);
}
uno::Reference<text::XTextField> lcl_FindParagraphClassificationField(const rtl::Reference<SwXTextDocument>& xModel,
const rtl::Reference<SwXParagraph>& xParagraph,
std::u16string_view sKey = u"")
{
uno::Reference<text::XTextField> xTextField;
if (!xParagraph.is())
return xTextField;
// Enumerate text portions to find metadata fields. This is expensive, best to enumerate fields only.
rtl::Reference<SwXTextPortionEnumeration> xTextPortions = xParagraph->createTextFieldsEnumeration();
while (xTextPortions->hasMoreElements())
{
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
OUString aTextPortionType;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
continue;
uno::Reference<lang::XServiceInfo> xServiceInfo;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xServiceInfo;
if (!xServiceInfo->supportsService(MetadataFieldServiceName))
continue;
uno::Reference<text::XTextField> xField(xServiceInfo, uno::UNO_QUERY);
if (lcl_IsParagraphClassificationField(xModel, xField, sKey))
{
xTextField = std::move(xField);
break;
}
}
return xTextField;
}
/// Creates and inserts Paragraph Classification Metadata field and creates the RDF entry
uno::Reference<text::XTextField> lcl_InsertParagraphClassification(const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<text::XTextContent>& xParent)
{
auto xField = uno::Reference<text::XTextField>(xModel->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);
// Add the classification at the start.
xField->attach(xParent->getAnchor()->getStart());
return xField;
}
/// Updates the paragraph classification field text if changed and returns true only iff updated.
bool lcl_UpdateParagraphClassificationField(SwDoc* pDoc,
const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<css::text::XTextContent>& xTextNode,
const OUString& sKey,
const OUString& sValue,
const OUString& sDisplayText)
{
// Disable undo to avoid introducing noise when we edit the metadata field.
const bool isUndoEnabled = pDoc->GetIDocumentUndoRedo().DoesUndo();
pDoc->GetIDocumentUndoRedo().DoUndo(false);
comphelper::ScopeGuard const g([pDoc, isUndoEnabled] () {
pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
});
uno::Reference<text::XTextField> xField = lcl_InsertParagraphClassification(xModel, xTextNode);
css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY);
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, sKey, sValue);
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationNameRDFName, sKey);
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationValueRDFName, sValue);
css::uno::Reference<css::rdf::XResource> xNodeSubject(xTextNode, uno::UNO_QUERY);
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, sKey, sValue);
return lcl_DoUpdateParagraphSignatureField(*pDoc, xField, sDisplayText);
}
void lcl_ValidateParagraphSignatures(SwDoc& rDoc, const uno::Reference<text::XTextContent>& xParagraph, const bool updateDontRemove, const uno::Sequence<uno::Reference<css::rdf::XURI>>& rGraphNames)
{
SwDocShell* pDocShell = rDoc.GetDocShell();
if (!pDocShell)
return;
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
// Check if the paragraph is signed.
try
{
const css::uno::Reference<css::rdf::XResource> xSubject(xParagraph, uno::UNO_QUERY);
std::map<OUString, OUString> aStatements = SwRDFHelper::getStatements(xModel, rGraphNames, xSubject);
const auto it = aStatements.find(ParagraphSignatureLastIdRDFName);
if (it == aStatements.end() || it->second.isEmpty())
return;
}
catch (const ::css::uno::Exception&)
{
return;
}
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
if (!xTextPortionEnumerationAccess.is())
return;
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
if (!xTextPortions.is())
return;
// Get the text (without fields).
const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
if (utf8Text.isEmpty())
return;
while (xTextPortions->hasMoreElements())
{
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
OUString aTextPortionType;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
continue;
uno::Reference<lang::XServiceInfo> xTextField;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
if (!xTextField->supportsService(MetadataFieldServiceName))
continue;
uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
if (!lcl_IsParagraphSignatureField(xModel, xField))
{
continue;
}
if (updateDontRemove)
{
lcl_UpdateParagraphSignatureField(rDoc, xModel, xParagraph, xField, utf8Text);
}
else if (!lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).first)
{
rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
rDoc.GetIDocumentUndoRedo().AppendUndo(
std::make_unique<SwUndoParagraphSigning>(rDoc, xField, xParagraph, false));
lcl_RemoveParagraphMetadataField(xField);
rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
}
}
}
} // anonymous namespace
SwTextFormatColl& SwEditShell::GetDfltTextFormatColl() const
{
return *GetDoc()->GetDfltTextFormatColl();
}
sal_uInt16 SwEditShell::GetTextFormatCollCount() const
{
return GetDoc()->GetTextFormatColls()->size();
}
SwTextFormatColl& SwEditShell::GetTextFormatColl(sal_uInt16 nFormatColl) const
{
return *((*(GetDoc()->GetTextFormatColls()))[nFormatColl]);
}
static void insertFieldToDocument(uno::Reference<lang::XMultiServiceFactory> const & rxMultiServiceFactory,
uno::Reference<text::XText> const & rxText, uno::Reference<text::XParagraphCursor> const & rxParagraphCursor,
OUString const & rsKey)
{
uno::Reference<beans::XPropertySet> xField(rxMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
xField->setPropertyValue(UNO_NAME_NAME, uno::Any(rsKey));
uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
rxText->insertTextContent(rxParagraphCursor, xTextContent, false);
}
static void removeAllClassificationFields(std::u16string_view rPolicy, uno::Reference<text::XText> const & rxText)
{
uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(rxText, uno::UNO_QUERY);
uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
while (xParagraphs->hasMoreElements())
{
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
while (xTextPortions->hasMoreElements())
{
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
OUString aTextPortionType;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
continue;
uno::Reference<lang::XServiceInfo> xTextField;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
if (!xTextField->supportsService(DocInfoServiceName))
continue;
OUString aName;
uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
if (aName.startsWith(rPolicy))
{
uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
rxText->removeTextContent(xField);
}
}
}
}
static sal_Int32 getNumberOfParagraphs(uno::Reference<text::XText> const & xText)
{
uno::Reference<container::XEnumerationAccess> xParagraphEnumAccess(xText, uno::UNO_QUERY);
uno::Reference<container::XEnumeration> xParagraphEnum = xParagraphEnumAccess->createEnumeration();
sal_Int32 nResult = 0;
while (xParagraphEnum->hasMoreElements())
{
xParagraphEnum->nextElement();
nResult++;
}
return nResult;
}
static void equaliseNumberOfParagraph(std::vector<svx::ClassificationResult> const & rResults, uno::Reference<text::XText> const & xText)
{
sal_Int32 nNumberOfParagraphs = 0;
for (svx::ClassificationResult const & rResult : rResults)
{
if (rResult.meType == svx::ClassificationType::PARAGRAPH)
nNumberOfParagraphs++;
}
while (getNumberOfParagraphs(xText) < nNumberOfParagraphs)
{
uno::Reference<text::XParagraphAppend> xParagraphAppend(xText, uno::UNO_QUERY);
xParagraphAppend->finishParagraph(uno::Sequence<beans::PropertyValue>());
}
}
void SwEditShell::ApplyAdvancedClassification(std::vector<svx::ClassificationResult> const & rResults)
{
SwDocShell* pDocShell = GetDoc()->GetDocShell();
if (!pDocShell)
return;
const SfxObjectShell* pObjSh = SfxObjectShell::Current();
if (!pObjSh)
return;
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel);
uno::Reference<document::XDocumentProperties> xDocumentProperties = pObjSh->getDocProperties();
const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType());
const std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this);
for (const OUString& rPageStyleName : aUsedPageStyles)
{
uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
// HEADER
bool bHeaderIsOn = false;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
uno::Reference<text::XText> xHeaderText;
if (bHeaderIsOn)
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
if (xHeaderText.is())
removeAllClassificationFields(sPolicy, xHeaderText);
// FOOTER
bool bFooterIsOn = false;
xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
uno::Reference<text::XText> xFooterText;
if (bFooterIsOn)
xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
if (xFooterText.is())
removeAllClassificationFields(sPolicy, xFooterText);
}
// Clear properties
uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
svx::classification::removeAllProperties(xPropertyContainer);
SfxClassificationHelper aHelper(xDocumentProperties);
// Apply properties from the BA policy
for (svx::ClassificationResult const & rResult : rResults)
{
if (rResult.meType == svx::ClassificationType::CATEGORY)
{
aHelper.SetBACName(rResult.msName, SfxClassificationHelper::getPolicyType());
}
}
sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
// Insert origin document property
svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::MANUAL);
// Insert full text as document property
svx::classification::insertFullTextualRepresentationAsDocumentProperty(xPropertyContainer, aCreator, rResults);
for (const OUString& rPageStyleName : aUsedPageStyles)
{
uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
// HEADER
bool bHeaderIsOn = false;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
if (!bHeaderIsOn)
xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true));
uno::Reference<text::XText> xHeaderText;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
equaliseNumberOfParagraph(rResults, xHeaderText);
// FOOTER
bool bFooterIsOn = false;
xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
if (!bFooterIsOn)
xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::Any(true));
uno::Reference<text::XText> xFooterText;
xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
equaliseNumberOfParagraph(rResults, xFooterText);
// SET/DELETE WATERMARK
SfxWatermarkItem aWatermarkItem;
aWatermarkItem.SetText(aHelper.GetDocumentWatermark());
SetWatermark(aWatermarkItem);
uno::Reference<text::XParagraphCursor> xHeaderParagraphCursor(xHeaderText->createTextCursor(), uno::UNO_QUERY);
uno::Reference<text::XParagraphCursor> xFooterParagraphCursor(xFooterText->createTextCursor(), uno::UNO_QUERY);
sal_Int32 nParagraph = -1;
for (svx::ClassificationResult const & rResult : rResults)
{
switch(rResult.meType)
{
case svx::ClassificationType::TEXT:
{
OUString sKey = aCreator.makeNumberedTextKey();
svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
}
break;
case svx::ClassificationType::CATEGORY:
{
OUString sKey = aCreator.makeCategoryNameKey();
insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
}
break;
case svx::ClassificationType::MARKING:
{
OUString sKey = aCreator.makeNumberedMarkingKey();
svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
}
break;
case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART:
{
OUString sKey = aCreator.makeNumberedIntellectualPropertyPartKey();
svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
}
break;
case svx::ClassificationType::PARAGRAPH:
{
nParagraph++;
if (nParagraph != 0) // only jump to next paragraph, if we aren't at the first paragraph
{
xHeaderParagraphCursor->gotoNextParagraph(false);
xFooterParagraphCursor->gotoNextParagraph(false);
}
xHeaderParagraphCursor->gotoStartOfParagraph(false);
xFooterParagraphCursor->gotoStartOfParagraph(false);
uno::Reference<beans::XPropertySet> xHeaderPropertySet(xHeaderParagraphCursor, uno::UNO_QUERY_THROW);
uno::Reference<beans::XPropertySet> xFooterPropertySet(xFooterParagraphCursor, uno::UNO_QUERY_THROW);
if (rResult.msName == "BOLD")
{
xHeaderPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::BOLD));
xFooterPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::BOLD));
}
else
{
xHeaderPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::NORMAL));
xFooterPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::NORMAL));
}
}
break;
default:
break;
}
}
}
}
std::vector<svx::ClassificationResult> SwEditShell::CollectAdvancedClassification()
{
std::vector<svx::ClassificationResult> aResult;
SwDocShell* pDocShell = GetDoc()->GetDocShell();
if (!pDocShell)
return aResult;
const SfxObjectShell* pObjSh = SfxObjectShell::Current();
if (!pObjSh)
return aResult;
const OUString sBlank;
uno::Reference<document::XDocumentProperties> xDocumentProperties = pObjSh->getDocProperties();
uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
std::vector<OUString> aPageStyles = lcl_getUsedPageStyles(this);
const OUString& aPageStyleString = aPageStyles.back();
uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(aPageStyleString), uno::UNO_QUERY);
bool bHeaderIsOn = false;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
if (!bHeaderIsOn)
{
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey());
if (!aValue.isEmpty())
aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
return aResult;
}
uno::Reference<text::XText> xHeaderText;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xHeaderText, uno::UNO_QUERY);
uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
// set to true if category was found in the header
bool bFoundClassificationCategory = false;
while (xParagraphs->hasMoreElements())
{
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
if (!xTextPortionEnumerationAccess.is())
continue;
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
// Check font weight
uno::Reference<beans::XPropertySet> xParagraphPropertySet(xTextPortionEnumerationAccess, uno::UNO_QUERY_THROW);
uno::Any aAny = xParagraphPropertySet->getPropertyValue(u"CharWeight"_ustr);
OUString sWeight = (aAny.get<float>() >= awt::FontWeight::BOLD) ? u"BOLD"_ustr : u"NORMAL"_ustr;
aResult.emplace_back(svx::ClassificationType::PARAGRAPH, sWeight, sBlank, sBlank);
// Process portions
while (xTextPortions->hasMoreElements())
{
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
OUString aTextPortionType;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
continue;
uno::Reference<lang::XServiceInfo> xTextField;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
if (!xTextField->supportsService(DocInfoServiceName))
continue;
OUString aName;
uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
if (aCreator.isMarkingTextKey(aName))
{
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
if (!aValue.isEmpty())
aResult.emplace_back(svx::ClassificationType::TEXT, aValue, sBlank, sBlank);
}
else if (aCreator.isCategoryNameKey(aName))
{
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
if (!aValue.isEmpty())
aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
bFoundClassificationCategory = true;
}
else if (aCreator.isCategoryIdentifierKey(aName))
{
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
if (!aValue.isEmpty())
aResult.emplace_back(svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue);
bFoundClassificationCategory = true;
}
else if (aCreator.isMarkingKey(aName))
{
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
if (!aValue.isEmpty())
aResult.emplace_back(svx::ClassificationType::MARKING, aValue, sBlank, sBlank);
}
else if (aCreator.isIntellectualPropertyPartKey(aName))
{
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
if (!aValue.isEmpty())
aResult.emplace_back(svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, aValue, sBlank, sBlank);
}
}
}
if (!bFoundClassificationCategory)
{
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey());
if (!aValue.isEmpty())
aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
}
return aResult;
}
void SwEditShell::SetClassification(const OUString& rName, SfxClassificationPolicyType eType)
{
SwDocShell* pDocShell = GetDoc()->GetDocShell();
if (!pDocShell)
return;
SfxClassificationHelper aHelper(pDocShell->getDocProperties());
const bool bHadWatermark = !aHelper.GetDocumentWatermark().isEmpty();
// This updates the infobar as well.
aHelper.SetBACName(rName, eType);
// Insert origin document property
uno::Reference<beans::XPropertyContainer> xPropertyContainer = pDocShell->getDocProperties()->getUserDefinedProperties();
sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::BAF_POLICY);
bool bHeaderIsNeeded = aHelper.HasDocumentHeader();
bool bFooterIsNeeded = aHelper.HasDocumentFooter();
OUString aWatermark = aHelper.GetDocumentWatermark();
bool bWatermarkIsNeeded = !aWatermark.isEmpty();
if (!bHeaderIsNeeded && !bFooterIsNeeded && !bWatermarkIsNeeded && !bHadWatermark)
return;
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();
for (const OUString& rPageStyleName : aStyles)
{
uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel);
if (bHeaderIsNeeded || bWatermarkIsNeeded || bHadWatermark)
{
// If the header is off, turn it on.
bool bHeaderIsOn = false;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
if (!bHeaderIsOn)
xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true));
// If the header already contains a document header field, no need to do anything.
uno::Reference<text::XText> xHeaderText;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
if (bHeaderIsNeeded)
{
if (!lcl_hasField(xHeaderText, DocInfoServiceName, Concat2View(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER())))
{
// Append a field to the end of the header text.
uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
xField->setPropertyValue(UNO_NAME_NAME, uno::Any(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER()));
uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, /*bAbsorb=*/false);
}
}
SfxWatermarkItem aWatermarkItem;
aWatermarkItem.SetText(aWatermark);
SetWatermark(aWatermarkItem);
}
if (bFooterIsNeeded)
{
// If the footer is off, turn it on.
bool bFooterIsOn = false;
xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
if (!bFooterIsOn)
xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::Any(true));
// If the footer already contains a document header field, no need to do anything.
uno::Reference<text::XText> xFooterText;
xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
static OUString sFooter = SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCFOOTER();
if (!lcl_hasField(xFooterText, DocInfoServiceName, sFooter))
{
// Append a field to the end of the footer text.
uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
xField->setPropertyValue(UNO_NAME_NAME, uno::Any(sFooter));
uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
xFooterText->insertTextContent(xFooterText->getEnd(), xTextContent, /*bAbsorb=*/false);
}
}
}
}
// We pass xParent and xNodeSubject even though they point to the same thing because the UNO_QUERY is
// on a performance-sensitive path.
static void lcl_ApplyParagraphClassification(SwDoc* pDoc,
const rtl::Reference<SwXTextDocument>& xModel,
const rtl::Reference<SwXParagraph>& xParent,
const css::uno::Reference<css::rdf::XResource>& xNodeSubject,
std::vector<svx::ClassificationResult> aResults)
{
if (!xNodeSubject.is())
return;
// Remove all paragraph classification fields.
for (;;)
{
uno::Reference<text::XTextField> xTextField = lcl_FindParagraphClassificationField(xModel, xParent);
if (!xTextField.is())
break;
lcl_RemoveParagraphMetadataField(xTextField);
}
if (aResults.empty())
return;
// Since we always insert at the start of the paragraph,
// need to insert in reverse order.
std::reverse(aResults.begin(), aResults.end());
// Ignore "PARAGRAPH" types
std::erase_if(aResults,
[](const svx::ClassificationResult& rResult)-> bool
{ return rResult.meType == svx::ClassificationType::PARAGRAPH; });
sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
std::vector<OUString> aFieldNames;
for (size_t nIndex = 0; nIndex < aResults.size(); ++nIndex)
{
const svx::ClassificationResult& rResult = aResults[nIndex];
const bool isLast = nIndex == 0;
const bool isFirst = (nIndex == aResults.size() - 1);
OUString sKey;
OUString sValue = rResult.msName;
switch (rResult.meType)
{
case svx::ClassificationType::TEXT:
{
sKey = aKeyCreator.makeNumberedTextKey();
}
break;
case svx::ClassificationType::CATEGORY:
{
if (rResult.msIdentifier.isEmpty())
{
sKey = aKeyCreator.makeCategoryNameKey();
}
else
{
sValue = rResult.msIdentifier;
sKey = aKeyCreator.makeCategoryIdentifierKey();
}
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationAbbrRDFName, rResult.msAbbreviatedName);
}
break;
case svx::ClassificationType::MARKING:
{
sKey = aKeyCreator.makeNumberedMarkingKey();
}
break;
case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART:
{
sKey = aKeyCreator.makeNumberedIntellectualPropertyPartKey();
}
break;
default:
break;
}
OUString sDisplayText = (isFirst ? ("(" + rResult.msAbbreviatedName) : rResult.msAbbreviatedName);
if (isLast)
sDisplayText += ")";
lcl_UpdateParagraphClassificationField(pDoc, xModel, xParent, sKey, sValue, sDisplayText);
aFieldNames.emplace_back(sKey);
}
// Correct the order
std::reverse(aFieldNames.begin(), aFieldNames.end());
OUStringBuffer sFieldNames;
bool first = true;
for (const OUString& rFieldName : aFieldNames)
{
if (!first)
sFieldNames.append("/");
sFieldNames.append(rFieldName);
first = false;
}
const OUString sOldFieldNames = lcl_getRDF(xModel, xNodeSubject, ParagraphClassificationFieldNamesRDFName).second;
SwRDFHelper::removeStatement(xModel, MetaNS, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sOldFieldNames);
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sFieldNames.makeStringAndClear());
}
void SwEditShell::ApplyParagraphClassification(std::vector<svx::ClassificationResult> aResults)
{
SwDocShell* pDocShell = GetDoc()->GetDocShell();
if (!pDocShell || !GetCursor() || !GetCursor()->Start())
return;
SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
if (pNode == nullptr)
return;
// Prevent recursive validation since this is triggered on node updates, which we do below.
const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
comphelper::ScopeGuard const g([this, bOldValidationFlag]() {
SetParagraphSignatureValidation(bOldValidationFlag);
});
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
rtl::Reference<SwXParagraph> xParent = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
lcl_ApplyParagraphClassification(GetDoc(), xModel, xParent, css::uno::Reference<css::rdf::XResource>(xParent), std::move(aResults));
}
static std::vector<svx::ClassificationResult> lcl_CollectParagraphClassification(const rtl::Reference<SwXTextDocument>& xModel, const uno::Reference<text::XTextContent>& xParagraph)
{
std::vector<svx::ClassificationResult> aResult;
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
if (!xTextPortionEnumerationAccess.is())
return aResult;
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
while (xTextPortions->hasMoreElements())
{
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
OUString aTextPortionType;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
continue;
uno::Reference<lang::XServiceInfo> xField;
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xField;
if (!xField->supportsService(MetadataFieldServiceName))
continue;
uno::Reference<text::XTextField> xTextField(xField, uno::UNO_QUERY);
const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType());
const std::pair<OUString, OUString> rdfNamePair = lcl_getFieldRDFByPrefix(xModel, xTextField, sPolicy);
uno::Reference<text::XTextRange> xTextRange(xField, uno::UNO_QUERY);
const OUString aName = rdfNamePair.first;
const OUString aValue = rdfNamePair.second;
static constexpr OUString sBlank(u""_ustr);
if (aKeyCreator.isMarkingTextKey(aName))
{
aResult.emplace_back(svx::ClassificationType::TEXT, aValue, sBlank, sBlank);
}
else if (aKeyCreator.isCategoryNameKey(aName))
{
aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
}
else if (aKeyCreator.isCategoryIdentifierKey(aName))
{
aResult.emplace_back(svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue);
}
else if (aKeyCreator.isMarkingKey(aName))
{
aResult.emplace_back(svx::ClassificationType::MARKING, aValue, sBlank, sBlank);
}
else if (aKeyCreator.isIntellectualPropertyPartKey(aName))
{
aResult.emplace_back(svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, xTextRange->getString(), sBlank, sBlank);
}
}
return aResult;
}
std::vector<svx::ClassificationResult> SwEditShell::CollectParagraphClassification()
{
std::vector<svx::ClassificationResult> aResult;
SwDocShell* pDocShell = GetDoc()->GetDocShell();
if (!pDocShell || !GetCursor() || !GetCursor()->Start())
return aResult;
SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
if (pNode == nullptr)
return aResult;
rtl::Reference<SwXParagraph> xParent = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
return lcl_CollectParagraphClassification(xModel, xParent);
}
static sal_Int16 lcl_GetAngle(const drawing::HomogenMatrix3& rMatrix)
{
basegfx::B2DHomMatrix aTransformation;
basegfx::B2DTuple aScale;
basegfx::B2DTuple aTranslate;
double fRotate = 0;
double fShear = 0;
aTransformation.set(0, 0, rMatrix.Line1.Column1);
aTransformation.set(0, 1, rMatrix.Line1.Column2);
aTransformation.set(0, 2, rMatrix.Line1.Column3);
aTransformation.set(1, 0, rMatrix.Line2.Column1);
aTransformation.set(1, 1, rMatrix.Line2.Column2);
aTransformation.set(1, 2, rMatrix.Line2.Column3);
// For this to be a valid 2D transform matrix, the last row must be [0,0,1]
assert( rMatrix.Line3.Column1 == 0 );
assert( rMatrix.Line3.Column2 == 0 );
assert( rMatrix.Line3.Column3 == 1 );
aTransformation.decompose(aScale, aTranslate, fRotate, fShear);
sal_Int16 nDeg = round(basegfx::rad2deg(fRotate));
return nDeg < 0 ? round(nDeg) * -1 : round(360.0 - nDeg);
}
SfxWatermarkItem SwEditShell::GetWatermark() const
{
SwDocShell* pDocShell = GetDoc()->GetDocShell();
if (!pDocShell)
return SfxWatermarkItem();
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this);
for (const OUString& rPageStyleName : aUsedPageStyles)
{
uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
bool bHeaderIsOn = false;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
if (!bHeaderIsOn)
return SfxWatermarkItem();
uno::Reference<text::XText> xHeaderText;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
OUString sWatermark = u""_ustr;
bool bSuccess = false;
uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, u"com.sun.star.drawing.CustomShape"_ustr, sWatermark, bSuccess);
if (xWatermark.is())
{
SfxWatermarkItem aItem;
uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY);
uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY);
Color nColor;
sal_Int16 nTransparency;
OUString aFont;
drawing::HomogenMatrix3 aMatrix;
aItem.SetText(xTextRange->getString());
if (xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont)
aItem.SetFont(aFont);
if (xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor)
aItem.SetColor(nColor);
if (xPropertySet->getPropertyValue(u"Transformation"_ustr) >>= aMatrix)
aItem.SetAngle(lcl_GetAngle(aMatrix));
if (xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency)
aItem.SetTransparency(nTransparency);
return aItem;
}
}
return SfxWatermarkItem();
}
static void lcl_placeWatermarkInHeader(const SfxWatermarkItem& rWatermark,
const rtl::Reference<SwXTextDocument>& xModel,
const uno::Reference<beans::XPropertySet>& xPageStyle,
const uno::Reference<text::XText>& xHeaderText)
{
if (!xHeaderText.is())
return;
OUString aShapeServiceName = u"com.sun.star.drawing.CustomShape"_ustr;
OUString sWatermark = WATERMARK_NAME;
bool bSuccess = false;
uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, aShapeServiceName, sWatermark, bSuccess);
bool bDeleteWatermark = rWatermark.GetText().isEmpty();
if (xWatermark.is())
{
drawing::HomogenMatrix3 aMatrix;
Color nColor = 0xc0c0c0;
sal_Int16 nTransparency = 50;
sal_Int16 nAngle = 45;
OUString aFont = u""_ustr;
uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY);
xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont;
xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor;
xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency;
xPropertySet->getPropertyValue(u"Transformation"_ustr) >>= aMatrix;
nAngle = lcl_GetAngle(aMatrix);
// If the header already contains a watermark, see if it its text is up to date.
uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY);
if (xTextRange->getString() != rWatermark.GetText()
|| aFont != rWatermark.GetFont()
|| nColor != rWatermark.GetColor()
|| nAngle != rWatermark.GetAngle()
|| nTransparency != rWatermark.GetTransparency()
|| bDeleteWatermark)
{
// No: delete it and we'll insert a replacement.
uno::Reference<lang::XComponent> xComponent(xWatermark, uno::UNO_QUERY);
xComponent->dispose();
xWatermark.clear();
}
}
if (!bSuccess || xWatermark.is() || bDeleteWatermark)
return;
const OUString& sFont = rWatermark.GetFont();
sal_Int16 nAngle = rWatermark.GetAngle();
sal_Int16 nTransparency = rWatermark.GetTransparency();
Color nColor = rWatermark.GetColor();
// Calc the ratio.
double fRatio = 0;
ScopedVclPtrInstance<VirtualDevice> pDevice;
vcl::Font aFont = pDevice->GetFont();
aFont.SetFamilyName(sFont);
aFont.SetFontSize(Size(0, 96));
pDevice->SetFont(aFont);
auto nTextWidth = pDevice->GetTextWidth(rWatermark.GetText());
if (nTextWidth)
{
fRatio = pDevice->GetTextHeight();
fRatio /= nTextWidth;
}
// Calc the size.
sal_Int32 nWidth = 0;
awt::Size aSize;
xPageStyle->getPropertyValue(UNO_NAME_SIZE) >>= aSize;
if (aSize.Width < aSize.Height)
{
// Portrait.
sal_Int32 nLeftMargin = 0;
xPageStyle->getPropertyValue(UNO_NAME_LEFT_MARGIN) >>= nLeftMargin;
sal_Int32 nRightMargin = 0;
xPageStyle->getPropertyValue(UNO_NAME_RIGHT_MARGIN) >>= nRightMargin;
nWidth = aSize.Width - nLeftMargin - nRightMargin;
}
else
{
// Landscape.
sal_Int32 nTopMargin = 0;
xPageStyle->getPropertyValue(UNO_NAME_TOP_MARGIN) >>= nTopMargin;
sal_Int32 nBottomMargin = 0;
xPageStyle->getPropertyValue(UNO_NAME_BOTTOM_MARGIN) >>= nBottomMargin;
nWidth = aSize.Height - nTopMargin - nBottomMargin;
}
sal_Int32 nHeight = fRatio * nWidth;
// Create and insert the shape.
uno::Reference<drawing::XShape> xShape(xModel->createInstance(aShapeServiceName), uno::UNO_QUERY);
uno::Reference<container::XNamed> xNamed(xShape, uno::UNO_QUERY);
xNamed->setName(sWatermark);
basegfx::B2DHomMatrix aTransformation;
aTransformation.identity();
aTransformation.scale(nWidth, nHeight);
aTransformation.rotate(-basegfx::deg2rad(nAngle));
drawing::HomogenMatrix3 aMatrix;
aMatrix.Line1.Column1 = aTransformation.get(0, 0);
aMatrix.Line1.Column2 = aTransformation.get(0, 1);
aMatrix.Line1.Column3 = aTransformation.get(0, 2);
aMatrix.Line2.Column1 = aTransformation.get(1, 0);
aMatrix.Line2.Column2 = aTransformation.get(1, 1);
aMatrix.Line2.Column3 = aTransformation.get(1, 2);
aMatrix.Line3.Column1 = 0;
aMatrix.Line3.Column2 = 0;
aMatrix.Line3.Column3 = 1;
uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, uno::Any(text::TextContentAnchorType_AT_CHARACTER));
uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY);
xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, false);
// The remaining properties have to be set after the shape is inserted: do that in one batch to avoid flickering.
uno::Reference<document::XActionLockable> xLockable(xShape, uno::UNO_QUERY);
xLockable->addActionLock();
xPropertySet->setPropertyValue(UNO_NAME_FILLCOLOR, uno::Any(static_cast<sal_Int32>(nColor)));
xPropertySet->setPropertyValue(UNO_NAME_FILLSTYLE, uno::Any(drawing::FillStyle_SOLID));
xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::Any(nTransparency));
xPropertySet->setPropertyValue(UNO_NAME_LINESTYLE, uno::Any(drawing::LineStyle_NONE));
xPropertySet->setPropertyValue(UNO_NAME_OPAQUE, uno::Any(false));
xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT, uno::Any(false));
xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWWIDTH, uno::Any(false));
xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEHEIGHT, uno::Any(nHeight));
xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEWIDTH, uno::Any(nWidth));
xPropertySet->setPropertyValue(UNO_NAME_TEXT_WRAP, uno::Any(text::WrapTextMode_THROUGH));
xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, uno::Any(text::RelOrientation::PAGE_PRINT_AREA));
xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION, uno::Any(text::RelOrientation::PAGE_PRINT_AREA));
xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME, uno::Any(sFont));
xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME_ASIAN, uno::Any(sFont));
xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME_COMPLEX, uno::Any(sFont));
xPropertySet->setPropertyValue(UNO_NAME_CHAR_HEIGHT, uno::Any(WATERMARK_AUTO_SIZE));
xPropertySet->setPropertyValue(u"Transformation"_ustr, uno::Any(aMatrix));
uno::Reference<text::XTextRange> xTextRange(xShape, uno::UNO_QUERY);
xTextRange->setString(rWatermark.GetText());
uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY);
xDefaulter->createCustomShapeDefaults(u"fontwork-plain-text"_ustr);
auto aGeomPropSeq = xPropertySet->getPropertyValue(u"CustomShapeGeometry"_ustr).get< uno::Sequence<beans::PropertyValue> >();
auto aGeomPropVec = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aGeomPropSeq);
uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
{
{"TextPath", uno::Any(true)},
}));
auto it = std::find_if(aGeomPropVec.begin(), aGeomPropVec.end(), [](const beans::PropertyValue& rValue)
{
return rValue.Name == "TextPath";
});
if (it == aGeomPropVec.end())
aGeomPropVec.push_back(comphelper::makePropertyValue(u"TextPath"_ustr, aPropertyValues));
else
it->Value <<= aPropertyValues;
xPropertySet->setPropertyValue(u"CustomShapeGeometry"_ustr, uno::Any(comphelper::containerToSequence(aGeomPropVec)));
// tdf#108494, tdf#109313 the header height was switched to height of a watermark
// and shape was moved to the lower part of a page, force position update
xPropertySet->getPropertyValue(u"Transformation"_ustr) >>= aMatrix;
xPropertySet->setPropertyValue(u"Transformation"_ustr, uno::Any(aMatrix));
xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT, uno::Any(text::HoriOrientation::CENTER));
xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT, uno::Any(text::VertOrientation::CENTER));
xLockable->removeActionLock();
}
void SwEditShell::SetWatermark(const SfxWatermarkItem& rWatermark)
{
SwDocShell* pDocShell = GetDoc()->GetDocShell();
if (!pDocShell)
return;
const bool bNoWatermark = rWatermark.GetText().isEmpty();
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();
for (const OUString& rPageStyleName : aStyles)
{
uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
// If the header is off, turn it on.
bool bHeaderIsOn = false;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
if (!bHeaderIsOn)
{
if (bNoWatermark)
continue; // the style doesn't have any watermark - no need to do anything
xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true));
}
// backup header height
bool bDynamicHeight = true;
sal_Int32 nOldValue;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_HEIGHT) >>= nOldValue;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT) >>= bDynamicHeight;
xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(false));
// If the header already contains a document header field, no need to do anything.
uno::Reference<text::XText> xHeaderText;
uno::Reference<text::XText> xHeaderTextFirst;
uno::Reference<text::XText> xHeaderTextLeft;
uno::Reference<text::XText> xHeaderTextRight;
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderText);
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_FIRST) >>= xHeaderTextFirst;
lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextFirst);
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_LEFT) >>= xHeaderTextLeft;
lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextLeft);
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_RIGHT) >>= xHeaderTextRight;
lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextRight);
// tdf#108494 the header height was switched to height of a watermark
// and shape was moved to the lower part of a page
xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::Any(sal_Int32(11)));
xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::Any(nOldValue));
xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(bDynamicHeight));
}
}
SwUndoParagraphSigning::SwUndoParagraphSigning(SwDoc& rDoc,
uno::Reference<text::XTextField> xField,
uno::Reference<text::XTextContent> xParent,
const bool bRemove)
: SwUndo(SwUndoId::PARA_SIGN_ADD, &rDoc),
m_rDoc(rDoc),
m_xField(std::move(xField)),
m_xParent(std::move(xParent)),
m_bRemove(bRemove)
{
// Save the metadata and field content to undo/redo.
if (SwDocShell* pShell = m_rDoc.GetDocShell())
{
rtl::Reference<SwXTextDocument> xModel = pShell->GetBaseModel();
const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, m_xField);
const auto it = aStatements.find(ParagraphSignatureIdRDFName);
if (it != aStatements.end())
m_signature = it->second;
const auto it2 = aStatements.find(ParagraphSignatureUsageRDFName);
if (it2 != aStatements.end())
m_usage = it2->second;
uno::Reference<css::text::XTextRange> xText(m_xField, uno::UNO_QUERY);
m_display = xText->getString();
}
}
void SwUndoParagraphSigning::UndoImpl(::sw::UndoRedoContext&)
{
if (m_bRemove)
Remove();
else
Insert();
}
void SwUndoParagraphSigning::RedoImpl(::sw::UndoRedoContext&)
{
if (m_bRemove)
Insert();
else
Remove();
}
void SwUndoParagraphSigning::RepeatImpl(::sw::RepeatContext&)
{
}
void SwUndoParagraphSigning::Insert()
{
// Disable undo to avoid introducing noise when we edit the metadata field.
const bool isUndoEnabled = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
m_rDoc.GetIDocumentUndoRedo().DoUndo(false);
// Prevent validation since this will trigger a premature validation
// upon inserting, but before setting the metadata.
SwEditShell* pEditSh = m_rDoc.GetEditShell();
const bool bOldValidationFlag = pEditSh && pEditSh->SetParagraphSignatureValidation(false);
comphelper::ScopeGuard const g([&] () {
if (pEditSh)
pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
m_rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
});
if (SwDocShell* pShell = m_rDoc.GetDocShell())
{
m_xField = lcl_InsertParagraphSignature(pShell->GetBaseModel(), m_xParent, m_signature, m_usage);
lcl_DoUpdateParagraphSignatureField(m_rDoc, m_xField, m_display);
}
}
void SwUndoParagraphSigning::Remove()
{
// Disable undo to avoid introducing noise when we edit the metadata field.
const bool isUndoEnabled = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
m_rDoc.GetIDocumentUndoRedo().DoUndo(false);
// Prevent validation since this will trigger a premature validation
// upon removing.
SwEditShell* pEditSh = m_rDoc.GetEditShell();
const bool bOldValidationFlag = pEditSh && pEditSh->SetParagraphSignatureValidation(false);
comphelper::ScopeGuard const g([&] () {
if (pEditSh)
pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
m_rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
});
lcl_RemoveParagraphMetadataField(m_xField);
}
void SwEditShell::SignParagraph()
{
SwDoc& rDoc = *GetDoc();
SwDocShell* pDocShell = rDoc.GetDocShell();
if (!pDocShell || !GetCursor() || !GetCursor()->Start())
return;
const SwPosition* pPosStart = GetCursor()->Start();
if (!pPosStart)
return;
SwTextNode* pNode = pPosStart->GetNode().GetTextNode();
if (!pNode)
return;
// Table text signing is not supported.
if (pNode->FindTableNode() != nullptr)
return;
// 1. Get the text (without fields).
const rtl::Reference<SwXParagraph> xParagraph = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
if (utf8Text.isEmpty())
return;
// 2. Get certificate.
uno::Reference<security::XDocumentDigitalSignatures> xSigner(
// here none of the version-dependent methods are called
security::DocumentDigitalSignatures::createDefault(
comphelper::getProcessComponentContext()));
uno::Sequence<css::beans::PropertyValue> aProperties;
uno::Reference<security::XCertificate> xCertificate = xSigner->chooseCertificateWithProps(aProperties);
if (!xCertificate.is())
return;
// 3. Sign it.
svl::crypto::SigningContext aSigningContext;
aSigningContext.m_xCertificate = xCertificate;
svl::crypto::Signing signing(aSigningContext);
signing.AddDataRange(utf8Text.getStr(), utf8Text.getLength());
OStringBuffer sigBuf;
if (!signing.Sign(sigBuf))
return;
const OUString signature = OStringToOUString(sigBuf, RTL_TEXTENCODING_UTF8, 0);
auto it = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue)
{
return rValue.Name == "Usage";
});
OUString aUsage;
if (it != aProperties.end())
it->Value >>= aUsage;
// 4. Add metadata
// Prevent validation since this will trigger a premature validation
// upon inserting, but before setting the metadata.
const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
SetParagraphSignatureValidation(bOldValidationFlag);
});
rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
const rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
uno::Reference<css::text::XTextField> xField = lcl_InsertParagraphSignature(xModel, xParagraph, signature, aUsage);
lcl_UpdateParagraphSignatureField(*GetDoc(), xModel, xParagraph, xField, utf8Text);
rDoc.GetIDocumentUndoRedo().AppendUndo(
std::make_unique<SwUndoParagraphSigning>(rDoc, xField, xParagraph, true));
rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
}
void SwEditShell::ValidateParagraphSignatures(SwTextNode* pNode, bool updateDontRemove)
{
if (!pNode || !IsParagraphSignatureValidationEnabled())
return;
// Table text signing is not supported.
if (pNode->FindTableNode() != nullptr)
return;
// Prevent recursive validation since this is triggered on node updates, which we do below.
const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
SetParagraphSignatureValidation(bOldValidationFlag);
});
SwDocShell* pDocShell = GetDoc()->GetDocShell();
if (!pDocShell)
return;
uno::Sequence<uno::Reference<css::rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(pDocShell->GetBaseModel(), MetaNS);
rtl::Reference<SwXParagraph> xParentText = SwXParagraph::CreateXParagraph(*GetDoc(), pNode, nullptr);
lcl_ValidateParagraphSignatures(*GetDoc(), xParentText, updateDontRemove, aGraphNames);
}
void SwEditShell::ValidateCurrentParagraphSignatures(bool updateDontRemove)
{
SwDocShell* pDocShell = GetDoc()->GetDocShell();
if (!pDocShell || !GetCursor() || !GetCursor()->Start() || !IsParagraphSignatureValidationEnabled())
return;
SwPaM* pPaM = GetCursor();
const SwPosition* pPosStart = pPaM->Start();
SwTextNode* pNode = pPosStart->GetNode().GetTextNode();
ValidateParagraphSignatures(pNode, updateDontRemove);
}
void SwEditShell::ValidateAllParagraphSignatures(bool updateDontRemove)
{
SwDocShell* pDocShell = GetDoc()->GetDocShell();
if (!pDocShell || !IsParagraphSignatureValidationEnabled())
return;
// Prevent recursive validation since this is triggered on node updates, which we do below.
const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
SetParagraphSignatureValidation(bOldValidationFlag);
});
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
uno::Reference<text::XText> xParent = xModel->getText();
uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xParent, uno::UNO_QUERY);
if (!xParagraphEnumerationAccess.is())
return;
uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
if (!xParagraphs.is())
return;
uno::Sequence<uno::Reference<css::rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(pDocShell->GetBaseModel(), MetaNS);
while (xParagraphs->hasMoreElements())
{
uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY);
lcl_ValidateParagraphSignatures(*GetDoc(), xParagraph, updateDontRemove, aGraphNames);
}
}
static uno::Reference<text::XTextField> lcl_GetParagraphMetadataFieldAtIndex(const SwDocShell* pDocSh, SwTextNode const * pNode, const sal_uLong index)
{
uno::Reference<text::XTextField> xTextField;
if (pNode != nullptr && pDocSh != nullptr)
{
SwTextAttr* pAttr = pNode->GetTextAttrAt(index, RES_TXTATR_METAFIELD);
SwTextMeta* pTextMeta = static_txtattr_cast<SwTextMeta*>(pAttr);
if (pTextMeta != nullptr)
{
SwFormatMeta& rFormatMeta(static_cast<SwFormatMeta&>(pTextMeta->GetAttr()));
if (::sw::Meta* pMeta = rFormatMeta.GetMeta())
{
const css::uno::Reference<css::rdf::XResource> xSubject = pMeta->MakeUnoObject();
rtl::Reference<SwXTextDocument> xModel = pDocSh->GetBaseModel();
const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xSubject);
if (aStatements.find(ParagraphSignatureIdRDFName) != aStatements.end() ||
aStatements.find(ParagraphClassificationNameRDFName) != aStatements.end())
{
xTextField = uno::Reference<text::XTextField>(xSubject, uno::UNO_QUERY);
}
}
}
}
return xTextField;
}
void SwEditShell::RestoreMetadataFieldsAndValidateParagraphSignatures()
{
SwDocShell* pDocShell = GetDoc()->GetDocShell();
if (!pDocShell || !IsParagraphSignatureValidationEnabled())
return;
// Prevent recursive validation since this is triggered on node updates, which we do below.
const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
SetParagraphSignatureValidation(bOldValidationFlag);
});
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
rtl::Reference<SwXBodyText> xBodyText = xModel->getBodyText();
if (!xBodyText.is())
return;
rtl::Reference<SwXParagraphEnumeration> xParagraphs = xBodyText->createParagraphEnumeration();
static constexpr OUString sBlank(u""_ustr);
const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
uno::Sequence<uno::Reference<css::rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(xModel, MetaNS);
while (xParagraphs->hasMoreElements())
{
uno::Reference<text::XTextContent> xParaOrTable(xParagraphs->nextElement(), uno::UNO_QUERY);
rtl::Reference<SwXParagraph> xParagraph(dynamic_cast<SwXParagraph*>(xParaOrTable.get()));
try
{
const css::uno::Reference<css::rdf::XResource> xSubject(xParagraph);
std::map<OUString, OUString> aParagraphStatements = SwRDFHelper::getStatements(xModel, aGraphNames, xSubject);
auto it = aParagraphStatements.find(ParagraphClassificationFieldNamesRDFName);
const OUString sFieldNames = (it != aParagraphStatements.end()) ? it->second : OUString();
std::vector<svx::ClassificationResult> aResults;
if (!sFieldNames.isEmpty())
{
// Order the fields
sal_Int32 nIndex = 0;
do
{
const OUString sCurFieldName = sFieldNames.getToken(0, '/', nIndex);
if (sCurFieldName.isEmpty())
break;
OUString sName;
OUString sValue;
it = aParagraphStatements.find(sCurFieldName);
if (it != aParagraphStatements.end())
{
sName = it->first;
sValue = it->second;
}
if (aKeyCreator.isMarkingTextKey(sName))
{
aResults.emplace_back(svx::ClassificationType::TEXT, sValue, sValue, sBlank);
}
else if (aKeyCreator.isCategoryNameKey(sName))
{
const std::pair<OUString, OUString> pairAbbr = lcl_getRDF(xModel, xSubject, ParagraphClassificationAbbrRDFName);
const OUString sAbbreviatedName = (!pairAbbr.second.isEmpty() ? pairAbbr.second : sValue);
aResults.emplace_back(svx::ClassificationType::CATEGORY, sValue, sAbbreviatedName, sBlank);
}
else if (aKeyCreator.isCategoryIdentifierKey(sName))
{
const std::pair<OUString, OUString> pairAbbr = lcl_getRDF(xModel, xSubject, ParagraphClassificationAbbrRDFName);
const OUString sAbbreviatedName = (!pairAbbr.second.isEmpty() ? pairAbbr.second : sValue);
aResults.emplace_back(svx::ClassificationType::CATEGORY, sBlank, sAbbreviatedName, sValue);
}
else if (aKeyCreator.isMarkingKey(sName))
{
aResults.emplace_back(svx::ClassificationType::MARKING, sValue, sValue, sBlank);
}
else if (aKeyCreator.isIntellectualPropertyPartKey(sName))
{
aResults.emplace_back(svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, sValue, sValue, sBlank);
}
}
while (nIndex >= 0);
}
// Update classification based on results.
lcl_ApplyParagraphClassification(GetDoc(), xModel, xParagraph, xSubject, std::move(aResults));
// Get Signatures
std::map<OUString, SignatureDescr> aSignatures;
for (const auto& pair : aParagraphStatements)
{
const OUString& sName = pair.first;
if (sName.startsWith(ParagraphSignatureRDFNamespace))
{
const OUString sSuffix = sName.copy(ParagraphSignatureRDFNamespace.getLength());
const sal_Int32 index = sSuffix.indexOf(":");
if (index >= 0)
{
const OUString id = sSuffix.copy(0, index);
const OUString type = sSuffix.copy(index);
const OUString& sValue = pair.second;
if (type == ParagraphSignatureDateRDFName)
aSignatures[id].msDate = sValue;
else if (type == ParagraphSignatureUsageRDFName)
aSignatures[id].msUsage = sValue;
else if (type == ParagraphSignatureDigestRDFName)
aSignatures[id].msSignature = sValue;
}
}
}
for (const auto& pair : aSignatures)
{
uno::Reference<text::XTextField> xField = lcl_findFieldByRDF(xModel, xParagraph, ParagraphSignatureIdRDFName, pair.first);
if (!xField.is())
{
xField = uno::Reference<text::XTextField>(xModel->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);
// Add the signature at the end.
xField->attach(xParagraph->getAnchor()->getEnd());
const css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY);
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphSignatureIdRDFName, pair.first);
const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
lcl_UpdateParagraphSignatureField(*GetDoc(), xModel, xParagraph, xField, utf8Text);
}
}
lcl_ValidateParagraphSignatures(*GetDoc(), xParagraph, true, aGraphNames); // Validate and Update signatures.
}
catch (const std::exception&)
{
}
}
}
bool SwEditShell::IsCursorInParagraphMetadataField() const
{
if (GetCursor() && GetCursor()->Start())
{
SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
const sal_uLong index = GetCursor()->Start()->GetContentIndex();
uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
return xField.is();
}
return false;
}
bool SwEditShell::RemoveParagraphMetadataFieldAtCursor()
{
if (GetCursor() && GetCursor()->Start())
{
SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
sal_uLong index = GetCursor()->Start()->GetContentIndex();
uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
if (!xField.is())
{
// Try moving the cursor to see if we're _facing_ a metafield or not,
// as opposed to being within one.
index--; // Backspace moves left
xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
}
if (xField.is())
{
lcl_RemoveParagraphMetadataField(xField);
return true;
}
}
return false;
}
static OUString lcl_GetParagraphClassification(SfxClassificationHelper & rHelper, sfx::ClassificationKeyCreator const & rKeyCreator,
const rtl::Reference<SwXTextDocument>& xModel, const rtl::Reference<SwXParagraph>& xParagraph)
{
uno::Reference<text::XTextField> xTextField;
xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryIdentifierKey());
if (xTextField.is())
{
const std::pair<OUString, OUString> rdfValuePair = lcl_getRDF(xModel, xTextField, ParagraphClassificationValueRDFName);
return rHelper.GetBACNameForIdentifier(rdfValuePair.second);
}
xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryNameKey());
if (xTextField.is())
{
return lcl_getRDF(xModel, xTextField, ParagraphClassificationNameRDFName).second;
}
return OUString();
}
static OUString lcl_GetHighestClassificationParagraphClass(SwPaM* pCursor)
{
OUString sHighestClass;
SwTextNode* pNode = pCursor->Start()->GetNode().GetTextNode();
if (pNode == nullptr)
return sHighestClass;
SwDocShell* pDocShell = pNode->GetDoc().GetDocShell();
if (!pDocShell)
return sHighestClass;
SfxClassificationHelper aHelper(pDocShell->getDocProperties());
sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
rtl::Reference<SwXBodyText> xBodyText = xModel->getBodyText();
rtl::Reference<SwXParagraphEnumeration> xParagraphs = xBodyText->createParagraphEnumeration();
while (xParagraphs->hasMoreElements())
{
uno::Reference<text::XTextContent> xParaOrTable(xParagraphs->nextElement(), uno::UNO_QUERY);
rtl::Reference<SwXParagraph> xParagraph(dynamic_cast<SwXParagraph*>(xParaOrTable.get()));
const OUString sCurrentClass = lcl_GetParagraphClassification(aHelper, aKeyCreator, xModel, xParagraph);
sHighestClass = aHelper.GetHigherClass(sHighestClass, sCurrentClass);
}
return sHighestClass;
}
void SwEditShell::ClassifyDocPerHighestParagraphClass()
{
SwDocShell* pDocShell = GetDoc()->GetDocShell();
if (!pDocShell)
return;
// Bail out as early as possible if we don't have paragraph classification.
if (!SwRDFHelper::hasMetadataGraph(pDocShell->GetBaseModel(), MetaNS))
return;
uno::Reference<document::XDocumentProperties> xDocumentProperties = pDocShell->getDocProperties();
uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
SfxClassificationHelper aHelper(xDocumentProperties);
OUString sHighestClass = lcl_GetHighestClassificationParagraphClass(GetCursor());
const OUString aClassificationCategory = svx::classification::getProperty(xPropertyContainer, aKeyCreator.makeCategoryNameKey());
if (!aClassificationCategory.isEmpty())
{
sHighestClass = aHelper.GetHigherClass(sHighestClass, aClassificationCategory);
}
if (aClassificationCategory != sHighestClass)
{
std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr,
VclMessageType::Question, VclButtonsType::Ok,
SwResId(STR_CLASSIFICATION_LEVEL_CHANGED)));
xQueryBox->run();
}
const SfxClassificationPolicyType eHighestClassType = SfxClassificationHelper::stringToPolicyType(sHighestClass);
// Prevent paragraph signature validation since the below changes (f.e. watermarking) are benign.
const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
comphelper::ScopeGuard const g([this, bOldValidationFlag]() {
SetParagraphSignatureValidation(bOldValidationFlag);
});
// Check the origin, if "manual" (created via advanced classification dialog),
// then we just need to set the category name.
if (sfx::getCreationOriginProperty(xPropertyContainer, aKeyCreator) == sfx::ClassificationCreationOrigin::MANUAL)
{
aHelper.SetBACName(sHighestClass, eHighestClassType);
ApplyAdvancedClassification(CollectAdvancedClassification());
}
else
{
SetClassification(sHighestClass, eHighestClassType);
}
}
// #i62675#
void SwEditShell::SetTextFormatColl(SwTextFormatColl *pFormat,
const bool bResetListAttrs,
SetAttrMode nMode)
{
SwTextFormatColl *pLocal = pFormat? pFormat: (*GetDoc()->GetTextFormatColls())[0];
StartAllAction();
SwRewriter aRewriter;
aRewriter.AddRule(UndoArg1, pLocal->GetName());
GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::SETFMTCOLL, &aRewriter);
for(SwPaM& rPaM : GetCursor()->GetRingContainer())
{
if (!rPaM.HasReadonlySel( GetViewOptions()->IsFormView(), true))
{
// store previous paragraph style for track changes
OUString sParaStyleName;
sal_uInt16 nPoolId = USHRT_MAX;
SwContentNode * pCnt = rPaM.Start()->GetNode().GetContentNode();
if ( pCnt && pCnt->GetTextNode() && GetDoc()->getIDocumentRedlineAccess().IsRedlineOn() )
{
const SwTextFormatColl* pTextFormatColl = pCnt->GetTextNode()->GetTextColl();
sal_uInt16 nStylePoolId = pTextFormatColl->GetPoolFormatId();
// default paragraph style
if ( nStylePoolId == RES_POOLCOLL_STANDARD )
nPoolId = nStylePoolId;
else
sParaStyleName = pTextFormatColl->GetName();
}
// Change the paragraph style to pLocal and remove all direct paragraph formatting.
GetDoc()->SetTextFormatColl(rPaM, pLocal, true, bResetListAttrs, !!(nMode & SetAttrMode::REMOVE_ALL_ATTR), GetLayout());
// If there are hints on the nodes which cover the whole node, then remove those, too.
SwPaM aPaM(*rPaM.Start(), *rPaM.End());
if (SwTextNode* pEndTextNode = aPaM.End()->GetNode().GetTextNode())
{
aPaM.Start()->SetContent(0);
aPaM.End()->SetContent(pEndTextNode->GetText().getLength());
}
GetDoc()->RstTextAttrs(aPaM, /*bInclRefToxMark=*/false, /*bExactRange=*/true, GetLayout());
// add redline tracking the previous paragraph style
if ( GetDoc()->getIDocumentRedlineAccess().IsRedlineOn() &&
// multi-paragraph ParagraphFormat redline ranges
// haven't supported by AppendRedline(), yet
// TODO handle multi-paragraph selections, too,
// e.g. by breaking them to single paragraphs
aPaM.Start()->GetNode() == aPaM.End()->GetNode() )
{
SwRangeRedline * pRedline = new SwRangeRedline( RedlineType::ParagraphFormat, aPaM );
auto const result(GetDoc()->getIDocumentRedlineAccess().AppendRedline( pRedline, true));
// store original paragraph style to reject formatting change
if ( IDocumentRedlineAccess::AppendResult::IGNORED != result &&
( nPoolId == RES_POOLCOLL_STANDARD || !sParaStyleName.isEmpty() ) )
{
std::unique_ptr<SwRedlineExtraData_FormatColl> xExtra;
xExtra.reset(new SwRedlineExtraData_FormatColl(sParaStyleName, nPoolId, nullptr));
pRedline->SetExtraData( xExtra.get() );
}
}
}
}
GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::SETFMTCOLL, &aRewriter);
EndAllAction();
}
SwTextFormatColl* SwEditShell::MakeTextFormatColl(const OUString& rFormatCollName,
SwTextFormatColl* pParent)
{
SwTextFormatColl *pColl;
if ( pParent == nullptr )
pParent = &GetTextFormatColl(0);
pColl = GetDoc()->MakeTextFormatColl(rFormatCollName, pParent);
if ( pColl == nullptr )
{
OSL_FAIL( "MakeTextFormatColl failed" );
}
return pColl;
}
void SwEditShell::FillByEx(SwTextFormatColl* pColl)
{
SwPaM * pCursor = GetCursor();
SwContentNode * pCnt = pCursor->GetPointContentNode();
if (pCnt->IsTextNode()) // uhm... what nonsense would happen if not?
{ // only need properties-node because BREAK/PAGEDESC filtered anyway!
pCnt = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode());
}
const SfxItemSet* pSet = pCnt->GetpSwAttrSet();
if( !pSet )
return;
// JP 05.10.98: Special treatment if one of the attributes Break/PageDesc/NumRule(auto) is
// in the ItemSet. Otherwise there will be too much or wrong processing (NumRules!)
// Bug 57568
// Do NOT copy AutoNumRules into the template
const SwNumRuleItem* pItem;
const SwNumRule* pRule = nullptr;
if (SfxItemState::SET == pSet->GetItemState(RES_BREAK, false)
|| SfxItemState::SET == pSet->GetItemState(RES_PAGEDESC, false)
|| ((pItem = pSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
&& nullptr != (pRule = GetDoc()->FindNumRulePtr(pItem->GetValue()))
&& pRule->IsAutoRule()))
{
SfxItemSet aSet( *pSet );
aSet.ClearItem( RES_BREAK );
aSet.ClearItem( RES_PAGEDESC );
if (pRule
|| ((pItem = pSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
&& nullptr != (pRule = GetDoc()->FindNumRulePtr(pItem->GetValue()))
&& pRule->IsAutoRule()))
aSet.ClearItem( RES_PARATR_NUMRULE );
if( aSet.Count() )
GetDoc()->ChgFormat(*pColl, aSet );
}
else
GetDoc()->ChgFormat(*pColl, *pSet );
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V614 Uninitialized variable 'nOldValue' used. Consider checking the first actual argument of the 'Any' function.
↑ V547 Expression 'bHeaderIsOn' is always false.
↑ V547 Expression 'bFooterIsOn' is always false.
↑ V547 Expression '!bHeaderIsOn' is always true.
↑ V547 Expression '!bFooterIsOn' is always true.
↑ V547 Expression '!bHeaderIsOn' is always true.
↑ V547 Expression '!bHeaderIsOn' is always true.
↑ V547 Expression '!bFooterIsOn' is always true.
↑ V547 Expression '!bHeaderIsOn' is always true.
↑ V547 Expression '!bHeaderIsOn' is always true.