include/xmloff/xmltoken.hxx                                      |    1 
 offapi/com/sun/star/text/ContentControl.idl                      |    6 +
 schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng      |    5 +
 sw/inc/formatcontentcontrol.hxx                                  |   12 ++
 sw/inc/unoprnms.hxx                                              |    1 
 sw/qa/core/data/docm/testModernVBA.docm                          |binary
 sw/qa/core/unocore/unocore.cxx                                   |    2 
 sw/qa/extras/ooxmlexport/ooxmlexport17.cxx                       |    3 
 sw/qa/extras/ooxmlexport/ooxmlexport4.cxx                        |    1 
 sw/source/core/txtnode/attrcontentcontrol.cxx                    |   38 
+++++++++
 sw/source/core/unocore/unocontentcontrol.cxx                     |   28 ++++++
 sw/source/core/unocore/unomap1.cxx                               |    1 
 sw/source/filter/ww8/docxattributeoutput.cxx                     |   16 +++
 sw/source/filter/ww8/docxattributeoutput.hxx                     |    1 
 sw/source/ui/vba/vbacontentcontrol.cxx                           |   42 
+++++++---
 writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx               |    4 
 writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx |binary
 writerfilter/source/dmapper/DomainMapper.cxx                     |   12 ++
 writerfilter/source/dmapper/DomainMapper_Impl.cxx                |    5 +
 writerfilter/source/dmapper/SdtHelper.cxx                        |    5 +
 writerfilter/source/dmapper/SdtHelper.hxx                        |    6 +
 xmloff/qa/unit/data/content-control-alias.fodt                   |    2 
 xmloff/qa/unit/text.cxx                                          |    5 +
 xmloff/source/core/xmltoken.cxx                                  |    1 
 xmloff/source/text/txtparae.cxx                                  |    7 +
 xmloff/source/text/xmlcontentcontrolcontext.cxx                  |   10 ++
 xmloff/source/text/xmlcontentcontrolcontext.hxx                  |    1 
 xmloff/source/token/tokens.txt                                   |    1 
 28 files changed, 205 insertions(+), 11 deletions(-)

New commits:
commit 65fa248f9a9504747a63698746350db139ae53ce
Author:     Justin Luth <justin.l...@collabora.com>
AuthorDate: Wed Nov 30 13:09:07 2022 -0500
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Tue Dec 13 14:24:56 2022 +0000

    tdf#151548 sw content controls: preserve lock
    
    DOCX SdtControls can be locked in two ways:
    -Content Control cannot be deleted (sdtLocked)
    -Contents cannot be edited (contentLocked)
    or both (sdtContentLocked)
    
    make CppunitTest_writerfilter_dmapper CPPUNIT_TEST_NAME=testSdtRunRichText
    make CppunitTest_sw_ooxmlexport4 CPPUNIT_TEST_NAME=testSimpleSdts
    make CppunitTest_sw_ooxmlexport17 
CPPUNIT_TEST_NAME=testDateContentControlExport
    make CppunitTest_sw_core_unocore CPPUNIT_TEST_NAME=testContentControlDate
    make CppunitTest_sw_macros_test CPPUNIT_TEST_NAME=testVba
    make CppunitTest_xmloff_text CPPUNIT_TEST_NAME=testAliasContentControlExport
    make CppunitTest_xmloff_text CPPUNIT_TEST_NAME=testAliasContentControlImport
    
    Change-Id: I5a82d9f6b5103a4902f59af66cd8a99addd4e690
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143542
    Tested-by: Jenkins
    Reviewed-by: Justin Luth <jl...@mail.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx
index bd650daea6e7..34bcf3099048 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -1221,6 +1221,7 @@ namespace xmloff::token {
         XML_LIST_STYLE,
         XML_LIST_STYLE_NAME,
         XML_LN,
+        XML_LOCK,
         XML_LOCKED,
         XML_LOG,
         XML_LOGARITHMIC,
diff --git a/offapi/com/sun/star/text/ContentControl.idl 
b/offapi/com/sun/star/text/ContentControl.idl
index 6abcc79fd204..59894741de2b 100644
--- a/offapi/com/sun/star/text/ContentControl.idl
+++ b/offapi/com/sun/star/text/ContentControl.idl
@@ -127,6 +127,12 @@ service ContentControl
         @since LibreOffice 7.5
     */
     [optional, property] long Id;
+
+    /** Describes whether the control itself and/or its data can be modified 
or deleted by the user.
+
+        @since LibreOffice 7.6
+    */
+    [optional, property] string Lock;
 };
 
 
diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng 
b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
index 6a993840e5e4..613ded76689c 100644
--- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
@@ -3008,6 +3008,11 @@ 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
           <rng:ref name="string"/>
         </rng:attribute>
       </rng:optional>
+      <rng:optional>
+        <rng:attribute name="loext:lock">
+          <rng:ref name="string"/>
+        </rng:attribute>
+      </rng:optional>
       <rng:zeroOrMore>
         <rng:element name="loext:list-item">
           <rng:attribute name="loext:display-text">
diff --git a/sw/inc/formatcontentcontrol.hxx b/sw/inc/formatcontentcontrol.hxx
index 94399d6818e8..e561fa6e23fb 100644
--- a/sw/inc/formatcontentcontrol.hxx
+++ b/sw/inc/formatcontentcontrol.hxx
@@ -179,6 +179,9 @@ class SW_DLLPUBLIC SwContentControl : public 
sw::BroadcastingModify
     /// The id: just remembered.
     sal_Int32 m_nId = 0;
 
+    /// The control and content locks: mostly just remembered.
+    OUString m_aLock;
+
     /// Stores a list item index, in case the doc model is not yet updated.
     // i.e. temporarily store the selected item until the text is inserted by 
GotoContentControl.
     std::optional<size_t> m_oSelectedListItem;
@@ -359,8 +362,17 @@ public:
 
     sal_Int32 GetId() const { return m_nId; }
 
+    // At the design level, define how the control should be locked. No effect 
at implementation lvl
+    void SetLock(bool bLockContent, bool bLockControl);
+    void SetLock(const OUString& rLock) { m_aLock = rLock; }
+
+    // At the design level, get how the control is locked. Does not reflect 
actual implementation.
+    std::optional<bool> GetLock(bool bControl) const;
+    const OUString& GetLock() const { return m_aLock; }
+
     void SetReadWrite(bool bReadWrite) { m_bReadWrite = bReadWrite; }
 
+    // At the implementation level, define whether the user can directly 
modify the contents.
     bool GetReadWrite() const { return m_bReadWrite; }
 
     SwContentControlType GetType() const;
diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx
index 199f4847c930..2e7158fac97e 100644
--- a/sw/inc/unoprnms.hxx
+++ b/sw/inc/unoprnms.hxx
@@ -917,6 +917,7 @@ inline constexpr OUStringLiteral UNO_NAME_COLOR = u"Color";
 inline constexpr OUStringLiteral UNO_NAME_ALIAS = u"Alias";
 inline constexpr OUStringLiteral UNO_NAME_TAG = u"Tag";
 inline constexpr OUStringLiteral UNO_NAME_ID = u"Id";
+inline constexpr OUStringLiteral UNO_NAME_LOCK = u"Lock";
 inline constexpr OUStringLiteral UNO_NAME_DATE_STRING = u"DateString";
 #endif
 
diff --git a/sw/qa/core/data/docm/testModernVBA.docm 
b/sw/qa/core/data/docm/testModernVBA.docm
index 49e53b615622..c08d738c8adb 100644
Binary files a/sw/qa/core/data/docm/testModernVBA.docm and 
b/sw/qa/core/data/docm/testModernVBA.docm differ
diff --git a/sw/qa/core/unocore/unocore.cxx b/sw/qa/core/unocore/unocore.cxx
index a521cdebca8d..985353db3e80 100644
--- a/sw/qa/core/unocore/unocore.cxx
+++ b/sw/qa/core/unocore/unocore.cxx
@@ -633,6 +633,7 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, 
testContentControlDate)
     xContentControlProps->setPropertyValue("Color", 
uno::Any(OUString("008000")));
     xContentControlProps->setPropertyValue("Alias", 
uno::Any(OUString("myalias")));
     xContentControlProps->setPropertyValue("Tag", uno::Any(OUString("mytag")));
+    xContentControlProps->setPropertyValue("Lock", 
uno::Any(OUString("sdtContentLocked")));
     xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
 
     // Then make sure that the specified properties are set:
@@ -658,6 +659,7 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, 
testContentControlDate)
     CPPUNIT_ASSERT_EQUAL(OUString("008000"), pContentControl->GetColor());
     CPPUNIT_ASSERT_EQUAL(OUString("myalias"), pContentControl->GetAlias());
     CPPUNIT_ASSERT_EQUAL(OUString("mytag"), pContentControl->GetTag());
+    CPPUNIT_ASSERT_EQUAL(OUString("sdtContentLocked"), 
pContentControl->GetLock());
 }
 
 CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testListIdState)
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
index d9d5802e0606..7a9a14c1cb1a 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
@@ -423,6 +423,8 @@ CPPUNIT_TEST_FIXTURE(Test, testDateContentControlExport)
     xContentControlProps->setPropertyValue("Color", 
uno::Any(OUString("008000")));
     xContentControlProps->setPropertyValue("Alias", 
uno::Any(OUString("myalias")));
     xContentControlProps->setPropertyValue("Tag", uno::Any(OUString("mytag")));
+    xContentControlProps->setPropertyValue("Lock", 
uno::Any(OUString("sdtLocked")));
+
     xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
 
     // When exporting to DOCX:
@@ -445,6 +447,7 @@ CPPUNIT_TEST_FIXTURE(Test, testDateContentControlExport)
     assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w15:color", "val", "008000");
     assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:alias", "val", "myalias");
     assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:tag", "val", "mytag");
+    assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:lock", "val", "sdtLocked");
 }
 
 CPPUNIT_TEST_FIXTURE(Test, testNegativePageBorder)
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx
index 3f08067bf1a3..a89fc773ae34 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx
@@ -1016,6 +1016,7 @@ CPPUNIT_TEST_FIXTURE(Test, testSimpleSdts)
 
     assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:sdt/w:sdtPr/w:text", 1);
     assertXPath(pXmlDoc, "//*/w:sdt/w:sdtPr/w:id", 5);
+    assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:lock", 1);
     assertXPath(pXmlDoc, "/w:document/w:body/w:sdt[1]/w:sdtPr/w:picture", 1);
     assertXPath(pXmlDoc, "/w:document/w:body/w:sdt[2]/w:sdtPr/w:group", 1);
     assertXPath(pXmlDoc, "/w:document/w:body/w:p[4]/w:sdt/w:sdtPr/w:citation", 
1);
diff --git a/sw/source/core/txtnode/attrcontentcontrol.cxx 
b/sw/source/core/txtnode/attrcontentcontrol.cxx
index 1db39849fbe6..ffbe23589f99 100644
--- a/sw/source/core/txtnode/attrcontentcontrol.cxx
+++ b/sw/source/core/txtnode/attrcontentcontrol.cxx
@@ -445,6 +445,42 @@ bool SwContentControl::ShouldOpenPopup(const vcl::KeyCode& 
rKeyCode)
     return false;
 }
 
+// NOTE: call SetReadWrite separately to implement true (un)locking.
+// This is mostly a theoretical function; the lock state is mainly kept for 
round-tripping purposes.
+// It is implemented here primarily for pointless VBA control, but with the 
intention that it
+// could be made functionally useful as well for checkboxes/dropdowns/pictures.
+// Returns whether the content (bControl=false) cannot be modified,
+// or if the control cannot be deleted.
+std::optional<bool> SwContentControl::GetLock(bool bControl) const
+{
+    std::optional<bool> oLock;
+    if (m_aLock.isEmpty())
+        return oLock;
+    else if (m_aLock.equalsIgnoreAsciiCase("sdtContentLocked"))
+        oLock = true;
+    else if (m_aLock.equalsIgnoreAsciiCase("unlocked"))
+        oLock = false;
+    else if (m_aLock.equalsIgnoreAsciiCase("sdtLocked"))
+        oLock = bControl;
+    else if (m_aLock.equalsIgnoreAsciiCase("contentLocked"))
+        oLock = !bControl;
+
+    assert(oLock && "invalid or unknown lock state");
+    return oLock;
+}
+
+void SwContentControl::SetLock(bool bLockContent, bool bLockControl)
+{
+    if (!bLockContent && !bLockControl)
+        m_aLock = "unlocked";
+    else if (bLockContent && bLockControl)
+        m_aLock = "sdtContentLocked";
+    else if (bLockContent)
+        m_aLock = "contentLocked";
+    else
+        m_aLock = "sdtLocked";
+}
+
 SwContentControlType SwContentControl::GetType() const
 {
     if (m_bCheckbox)
@@ -526,6 +562,8 @@ void SwContentControl::dumpAsXml(xmlTextWriterPtr pWriter) 
const
     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("tag"), 
BAD_CAST(m_aTag.toUtf8().getStr()));
     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("id"),
                                       
BAD_CAST(OString::number(m_nId).getStr()));
+    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("lock"),
+                                      BAD_CAST(m_aLock.toUtf8().getStr()));
 
     if (!m_aListItems.empty())
     {
diff --git a/sw/source/core/unocore/unocontentcontrol.cxx 
b/sw/source/core/unocore/unocontentcontrol.cxx
index 1a3092e21c52..8b6c4e798f2c 100644
--- a/sw/source/core/unocore/unocontentcontrol.cxx
+++ b/sw/source/core/unocore/unocontentcontrol.cxx
@@ -179,6 +179,7 @@ public:
     OUString m_aAlias;
     OUString m_aTag;
     sal_Int32 m_nId;
+    OUString m_aLock;
 
     Impl(SwXContentControl& rThis, SwDoc& rDoc, SwContentControl* 
pContentControl,
          uno::Reference<text::XText> xParentText, std::unique_ptr<const 
TextRangeList_t> pPortions)
@@ -493,6 +494,7 @@ void SwXContentControl::AttachImpl(const 
uno::Reference<text::XTextRange>& xText
     pContentControl->SetAlias(m_pImpl->m_aAlias);
     pContentControl->SetTag(m_pImpl->m_aTag);
     pContentControl->SetId(m_pImpl->m_nId);
+    pContentControl->SetLock(m_pImpl->m_aLock);
 
     SwFormatContentControl aContentControl(pContentControl, nWhich);
     bool bSuccess
@@ -983,6 +985,21 @@ void SAL_CALL SwXContentControl::setPropertyValue(const 
OUString& rPropertyName,
             }
         }
     }
+    else if (rPropertyName == UNO_NAME_LOCK)
+    {
+        OUString aValue;
+        if (rValue >>= aValue)
+        {
+            if (m_pImpl->m_bIsDescriptor)
+            {
+                m_pImpl->m_aLock = aValue;
+            }
+            else
+            {
+                m_pImpl->m_pContentControl->SetLock(aValue);
+            }
+        }
+    }
     else
     {
         throw beans::UnknownPropertyException();
@@ -1245,6 +1262,17 @@ uno::Any SAL_CALL 
SwXContentControl::getPropertyValue(const OUString& rPropertyN
             aRet <<= m_pImpl->m_pContentControl->GetId();
         }
     }
+    else if (rPropertyName == UNO_NAME_LOCK)
+    {
+        if (m_pImpl->m_bIsDescriptor)
+        {
+            aRet <<= m_pImpl->m_aLock;
+        }
+        else
+        {
+            aRet <<= m_pImpl->m_pContentControl->GetLock();
+        }
+    }
     else
     {
         throw beans::UnknownPropertyException();
diff --git a/sw/source/core/unocore/unomap1.cxx 
b/sw/source/core/unocore/unomap1.cxx
index a37843b1538a..437f293a9f78 100644
--- a/sw/source/core/unocore/unomap1.cxx
+++ b/sw/source/core/unocore/unomap1.cxx
@@ -1013,6 +1013,7 @@ o3tl::span<const SfxItemPropertyMapEntry> 
SwUnoPropertyMapProvider::GetContentCo
         { UNO_NAME_ALIAS, 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 
},
         { UNO_NAME_TAG, 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 },
         { UNO_NAME_ID, 0, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0 },
+        { UNO_NAME_LOCK, 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 },
         { UNO_NAME_DATE_STRING, 0, cppu::UnoType<OUString>::get(), 
PropertyAttribute::READONLY, 0 },
     };
 
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index 76d608ddc01e..11bf0e1f9b82 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -617,6 +617,8 @@ void SdtBlockHelper::DeleteAndResetTheLists()
         m_aAlias.clear();
     if (!m_aTag.isEmpty())
         m_aTag.clear();
+    if (!m_aLock.isEmpty())
+        m_aLock.clear();
     if (!m_aPlaceHolderDocPart.isEmpty())
         m_aPlaceHolderDocPart.clear();
     if (!m_aColor.isEmpty())
@@ -724,6 +726,9 @@ void SdtBlockHelper::WriteExtraParams(const 
::sax_fastparser::FSHelperPtr& pSeri
 
     if (!m_aTag.isEmpty())
         pSerializer->singleElementNS(XML_w, XML_tag, FSNS(XML_w, XML_val), 
m_aTag);
+
+    if (!m_aLock.isEmpty())
+        pSerializer->singleElementNS(XML_w, XML_lock, FSNS(XML_w, XML_val), 
m_aLock);
 }
 
 void SdtBlockHelper::EndSdtBlock(const ::sax_fastparser::FSHelperPtr& 
pSerializer)
@@ -838,6 +843,11 @@ void SdtBlockHelper::GetSdtParamsFromGrabBag(const 
uno::Sequence<beans::Property
             if (!(aPropertyValue.Value >>= m_aTag))
                 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected 
sdt tag value");
         }
+        else if (aPropertyValue.Name == "ooxml:CT_SdtPr_lock" && 
m_aLock.isEmpty())
+        {
+            if (!(aPropertyValue.Value >>= m_aLock))
+                SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected 
sdt lock value");
+        }
         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_id")
             m_bHasId = true;
         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_citation")
@@ -2398,6 +2408,12 @@ void DocxAttributeOutput::WriteContentControlStart()
                                        
OString::number(m_pContentControl->GetId()));
     }
 
+    if (!m_pContentControl->GetLock().isEmpty())
+    {
+        m_pSerializer->singleElementNS(XML_w, XML_lock, FSNS(XML_w, XML_val),
+                                       m_pContentControl->GetLock());
+    }
+
     if (m_pContentControl->GetShowingPlaceHolder())
     {
         m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr);
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx 
b/sw/source/filter/ww8/docxattributeoutput.hxx
index f8da5a4bd32d..055d277c7827 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -145,6 +145,7 @@ public:
     bool m_bShowingPlaceHolder;
     OUString m_aAlias;
     OUString m_aTag;
+    OUString m_aLock;
     sal_Int32 m_nSdtPrToken;
 
     void DeleteAndResetTheLists();
diff --git a/sw/source/ui/vba/vbacontentcontrol.cxx 
b/sw/source/ui/vba/vbacontentcontrol.cxx
index 0a156e35c96b..e53ad6c51d31 100644
--- a/sw/source/ui/vba/vbacontentcontrol.cxx
+++ b/sw/source/ui/vba/vbacontentcontrol.cxx
@@ -95,6 +95,13 @@ sal_Bool SwVbaContentControl::getChecked()
 
 void SwVbaContentControl::setChecked(sal_Bool bSet)
 {
+    // Word 2010: if locked, then the checked status is changed, but not the 
underlying text.
+    // Do we really want to do that? That is pretty bizarre behaviour...
+    // For now, just implement what seems to be a more logical response.
+    // TODO: test with modern versions.
+    if (getLockContents())
+        return;
+
     std::shared_ptr<SwContentControl> pCC = 
m_rCC.GetContentControl().GetContentControl();
     if (pCC->GetCheckbox() && pCC->GetChecked() != static_cast<bool>(bSet))
     {
@@ -487,21 +494,27 @@ sal_Int32 SwVbaContentControl::getLevel()
 
 sal_Bool SwVbaContentControl::getLockContentControl()
 {
-    SAL_INFO("sw.vba", "SwVbaContentControl::getLockContentControl stub");
-    // returns whether the user can delete a content control from the active 
document.
-    return true;
+    const std::shared_ptr<SwContentControl>& pCC = 
m_rCC.GetContentControl().GetContentControl();
+    std::optional<bool> oLock = pCC->GetLock(/*bControl=*/true);
+    return oLock && *oLock;
 }
 
-void SwVbaContentControl::setLockContentControl(sal_Bool /*bSet*/)
+void SwVbaContentControl::setLockContentControl(sal_Bool bSet)
 {
-    SAL_INFO("sw.vba", "SwVbaContentControl::setLockContentControl stub");
+    std::shared_ptr<SwContentControl> pCC = 
m_rCC.GetContentControl().GetContentControl();
+    std::optional<bool> oLock = pCC->GetLock(/*bControl=*/false);
+    pCC->SetLock(/*bContents=*/oLock && *oLock, /*bControl=*/bSet);
 }
 
 sal_Bool SwVbaContentControl::getLockContents()
 {
-    // Pseudo-implementation - the need for locking in a form would be very 
rare.
-    // LO uses this for internal purposes. Only expose it to VBA when safe.
     const std::shared_ptr<SwContentControl>& pCC = 
m_rCC.GetContentControl().GetContentControl();
+    // If the theoretical design model says it is locked, then report as 
locked.
+    std::optional<bool> oLock = pCC->GetLock(/*bControl=*/false);
+    if (oLock && *oLock)
+        return true;
+
+    // Now check the real implementation.
     // Checkbox/DropDown/Picture are normally locked - but not in this sense. 
Report as unlocked.
     if (pCC->GetType() == SwContentControlType::CHECKBOX
         || pCC->GetType() == SwContentControlType::DROP_DOWN_LIST
@@ -516,6 +529,10 @@ sal_Bool SwVbaContentControl::getLockContents()
 void SwVbaContentControl::setLockContents(sal_Bool bSet)
 {
     std::shared_ptr<SwContentControl> pCC = 
m_rCC.GetContentControl().GetContentControl();
+    // Set the lock both theoretically and actually.
+    std::optional<bool> oLock = pCC->GetLock(/*bControl=*/true);
+    pCC->SetLock(/*bContents=*/bSet, /*bControl=*/oLock && *oLock);
+
     // Checkbox/DropDown/Picture are normally locked in LO implementation - 
don't unlock them.
     if (pCC->GetType() == SwContentControlType::CHECKBOX
         || pCC->GetType() == SwContentControlType::DROP_DOWN_LIST
@@ -673,17 +690,24 @@ void SwVbaContentControl::Copy()
 
 void SwVbaContentControl::Cut()
 {
+    if (getLockContentControl())
+        return;
+
     SAL_INFO("sw.vba",
              "SwVbaContentControl::Cut[" << getID() << "], but missing sending 
to clipboard");
 
-    m_rCC.Delete(/*bSaveContents=*/false);
+    m_rCC.Delete(/*bSaveContents=*/getLockContents());
 }
 
 void SwVbaContentControl::Delete(const uno::Any& DeleteContents)
 {
+    if (getLockContentControl())
+        return;
+
     bool bDeleteContents = false;
     DeleteContents >>= bDeleteContents;
-    m_rCC.Delete(!bDeleteContents);
+
+    m_rCC.Delete(/*bSaveContents=*/!bDeleteContents || getLockContents());
 }
 
 void SwVbaContentControl::SetCheckedSymbol(sal_Int32 Character, const 
uno::Any& Font)
diff --git a/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx 
b/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
index be5154414551..8f9ea8ca5ddb 100644
--- a/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
+++ b/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
@@ -71,6 +71,10 @@ CPPUNIT_TEST_FIXTURE(Test, testSdtRunRichText)
     xContentControlProps->getPropertyValue("Tag") >>= aTag;
     // This was empty.
     CPPUNIT_ASSERT_EQUAL(OUString("mytag"), aTag);
+    OUString aLock;
+    xContentControlProps->getPropertyValue("Lock") >>= aLock;
+    // This was empty.
+    CPPUNIT_ASSERT_EQUAL(OUString("contentLocked"), aLock);
 }
 
 CPPUNIT_TEST_FIXTURE(Test, testSdtRunPlainText)
diff --git a/writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx 
b/writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx
index b7f291f776bf..aabc745bcf0e 100644
Binary files a/writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx 
and b/writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx differ
diff --git a/writerfilter/source/dmapper/DomainMapper.cxx 
b/writerfilter/source/dmapper/DomainMapper.cxx
index 1e0f9597b3b4..a0b06009b6c6 100644
--- a/writerfilter/source/dmapper/DomainMapper.cxx
+++ b/writerfilter/source/dmapper/DomainMapper.cxx
@@ -2848,6 +2848,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const 
PropertyMapPtr& rContext )
     case NS_ooxml::LN_CT_SdtPr_showingPlcHdr:
     case NS_ooxml::LN_CT_SdtPr_color:
     case NS_ooxml::LN_CT_SdtPr_tag:
+    case NS_ooxml::LN_CT_SdtPr_lock:
     {
         if (!m_pImpl->GetSdtStarts().empty())
         {
@@ -2885,6 +2886,12 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const 
PropertyMapPtr& rContext )
                 break;
             }
 
+            if (nSprmId == NS_ooxml::LN_CT_SdtPr_lock)
+            {
+                m_pImpl->m_pSdtHelper->SetLock(sStringValue);
+                break;
+            }
+
             if (nSprmId == NS_ooxml::LN_CT_SdtPr_checkbox)
             {
                 
m_pImpl->m_pSdtHelper->setControlType(SdtControlType::checkBox);
@@ -2932,6 +2939,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const 
PropertyMapPtr& rContext )
             case NS_ooxml::LN_CT_SdtPr_id:          sName = 
"ooxml:CT_SdtPr_id"; break;
             case NS_ooxml::LN_CT_SdtPr_alias:       sName = 
"ooxml:CT_SdtPr_alias"; break;
             case NS_ooxml::LN_CT_SdtPr_tag:         sName = 
"ooxml:CT_SdtPr_tag"; break;
+            case NS_ooxml::LN_CT_SdtPr_lock:        sName = 
"ooxml:CT_SdtPr_lock"; break;
             case NS_ooxml::LN_CT_SdtPlaceholder_docPart: sName = 
"ooxml:CT_SdtPlaceholder_docPart"; break;
             case NS_ooxml::LN_CT_SdtPr_showingPlcHdr: sName = 
"ooxml:CT_SdtPr_showingPlcHdr"; break;
             case NS_ooxml::LN_CT_SdtPr_color:       sName = 
"ooxml:CT_SdtPr_color"; break;
@@ -2953,8 +2961,10 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const 
PropertyMapPtr& rContext )
         if (pProperties)
             pProperties->resolve(*this);
 
-        if (nSprmId == NS_ooxml::LN_CT_SdtPr_alias || nSprmId == 
NS_ooxml::LN_CT_SdtPr_tag)
+        if (nSprmId == NS_ooxml::LN_CT_SdtPr_alias || nSprmId == 
NS_ooxml::LN_CT_SdtPr_tag
+            || nSprmId == NS_ooxml::LN_CT_SdtPr_lock)
         {
+            // Grabbag string values
             beans::PropertyValue aValue;
             aValue.Name = sName;
             aValue.Value <<= sStringValue;
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx 
b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index 064bb5b15da6..3f3906570c61 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -957,6 +957,11 @@ void DomainMapper_Impl::PopSdt()
         xContentControlProps->setPropertyValue("Id", 
uno::Any(m_pSdtHelper->GetId()));
     }
 
+    if (!m_pSdtHelper->GetLock().isEmpty())
+    {
+        xContentControlProps->setPropertyValue("Lock", 
uno::Any(m_pSdtHelper->GetLock()));
+    }
+
     if (m_pSdtHelper->getControlType() == SdtControlType::checkBox)
     {
         xContentControlProps->setPropertyValue("Checkbox", uno::Any(true));
diff --git a/writerfilter/source/dmapper/SdtHelper.cxx 
b/writerfilter/source/dmapper/SdtHelper.cxx
index a6a6b6cb08f8..81b03df774cd 100644
--- a/writerfilter/source/dmapper/SdtHelper.cxx
+++ b/writerfilter/source/dmapper/SdtHelper.cxx
@@ -521,6 +521,7 @@ void SdtHelper::clear()
     m_aAlias.clear();
     m_aTag.clear();
     m_nId = 0;
+    m_aLock.clear();
 }
 
 void SdtHelper::SetPlaceholderDocPart(const OUString& rPlaceholderDocPart)
@@ -546,6 +547,10 @@ void SdtHelper::SetId(sal_Int32 nId) { m_nId = nId; }
 
 sal_Int32 SdtHelper::GetId() const { return m_nId; }
 
+void SdtHelper::SetLock(const OUString& rLock) { m_aLock = rLock; }
+
+const OUString& SdtHelper::GetLock() const { return m_aLock; }
+
 } // namespace writerfilter::dmapper
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/writerfilter/source/dmapper/SdtHelper.hxx 
b/writerfilter/source/dmapper/SdtHelper.hxx
index 736476e63c2f..dbe0b2659e7a 100644
--- a/writerfilter/source/dmapper/SdtHelper.hxx
+++ b/writerfilter/source/dmapper/SdtHelper.hxx
@@ -135,6 +135,9 @@ class SdtHelper final : public virtual SvRefBase
     /// <w:sdtPr>'s <w:id w:val="...">.
     sal_Int32 m_nId = 0;
 
+    /// <w:sdtPr>'s <w:lock w:val="...">.
+    OUString m_aLock;
+
 public:
     explicit SdtHelper(DomainMapper_Impl& rDM_Impl,
                        css::uno::Reference<css::uno::XComponentContext> 
xContext);
@@ -223,6 +226,9 @@ public:
     void SetId(sal_Int32 nId);
     sal_Int32 GetId() const;
 
+    void SetLock(const OUString& rLock);
+    const OUString& GetLock() const;
+
     std::optional<OUString> getValueFromDataBinding();
 };
 
diff --git a/xmloff/qa/unit/data/content-control-alias.fodt 
b/xmloff/qa/unit/data/content-control-alias.fodt
index 8307f682e474..8f541bef42cc 100644
--- a/xmloff/qa/unit/data/content-control-alias.fodt
+++ b/xmloff/qa/unit/data/content-control-alias.fodt
@@ -2,7 +2,7 @@
 <office:document xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"
 office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
   <office:body>
     <office:text>
-      <text:p><loext:content-control loext:alias="my alias" loext:tag="my 
tag">test</loext:content-control></text:p>
+      <text:p><loext:content-control loext:alias="my alias" loext:tag="my tag" 
loext:lock="sdtContentLocked">test</loext:content-control></text:p>
     </office:text>
   </office:body>
 </office:document>
diff --git a/xmloff/qa/unit/text.cxx b/xmloff/qa/unit/text.cxx
index 45f3323d92a5..2187e3d2a89d 100644
--- a/xmloff/qa/unit/text.cxx
+++ b/xmloff/qa/unit/text.cxx
@@ -781,6 +781,7 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, 
testAliasContentControlExport)
     uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, 
uno::UNO_QUERY);
     xContentControlProps->setPropertyValue("Alias", uno::Any(OUString("my 
alias")));
     xContentControlProps->setPropertyValue("Tag", uno::Any(OUString("my 
tag")));
+    xContentControlProps->setPropertyValue("Lock", 
uno::Any(OUString("unlocked")));
     xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
 
     // When exporting to ODT:
@@ -794,6 +795,7 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, 
testAliasContentControlExport)
     // i.e. alias was lost on export.
     assertXPath(pXmlDoc, "//loext:content-control", "alias", "my alias");
     assertXPath(pXmlDoc, "//loext:content-control", "tag", "my tag");
+    assertXPath(pXmlDoc, "//loext:content-control", "lock", "unlocked");
 }
 
 CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testComboBoxContentControlImport)
@@ -853,6 +855,9 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, 
testAliasContentControlImport)
     OUString aTag;
     xContentControlProps->getPropertyValue("Tag") >>= aTag;
     CPPUNIT_ASSERT_EQUAL(OUString("my tag"), aTag);
+    OUString aLock;
+    xContentControlProps->getPropertyValue("Lock") >>= aLock;
+    CPPUNIT_ASSERT_EQUAL(OUString("sdtContentLocked"), aLock);
 }
 
 CPPUNIT_TEST_FIXTURE(XmloffStyleTest, 
testDropdownContentControlAutostyleExport)
diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx
index 1dc56ced1114..3eb529ea1517 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -1234,6 +1234,7 @@ namespace xmloff::token {
         TOKEN( "list-style",                      XML_LIST_STYLE ),
         TOKEN( "list-style-name",                 XML_LIST_STYLE_NAME ),
         TOKEN( "ln",                              XML_LN ),
+        TOKEN( "lock",                            XML_LOCK ),
         TOKEN( "locked",                          XML_LOCKED ),
         TOKEN( "log",                             XML_LOG ),
         TOKEN( "logarithmic",                     XML_LOGARITHMIC ),
diff --git a/xmloff/source/text/txtparae.cxx b/xmloff/source/text/txtparae.cxx
index 8c0d53e8da93..b2117dfdc726 100644
--- a/xmloff/source/text/txtparae.cxx
+++ b/xmloff/source/text/txtparae.cxx
@@ -4066,6 +4066,13 @@ void XMLTextParagraphExport::ExportContentControl(
         {
             GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_TAG, aTag);
         }
+
+        OUString aLock;
+        xPropertySet->getPropertyValue("Lock") >>= aLock;
+        if (!aLock.isEmpty())
+        {
+            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_LOCK, aLock);
+        }
     }
 
     SvXMLElementExport aElem(GetExport(), bExport, XML_NAMESPACE_LO_EXT, 
XML_CONTENT_CONTROL, false,
diff --git a/xmloff/source/text/xmlcontentcontrolcontext.cxx 
b/xmloff/source/text/xmlcontentcontrolcontext.cxx
index c069a6eba9c1..83c1621b057e 100644
--- a/xmloff/source/text/xmlcontentcontrolcontext.cxx
+++ b/xmloff/source/text/xmlcontentcontrolcontext.cxx
@@ -151,6 +151,11 @@ void XMLContentControlContext::startFastElement(
                 m_aTag = rIter.toString();
                 break;
             }
+            case XML_ELEMENT(LO_EXT, XML_LOCK):
+            {
+                m_aLock = rIter.toString();
+                break;
+            }
             default:
                 XMLOFF_WARN_UNKNOWN("xmloff", rIter);
         }
@@ -261,6 +266,11 @@ void XMLContentControlContext::endFastElement(sal_Int32)
     {
         xPropertySet->setPropertyValue("Tag", uno::Any(m_aTag));
     }
+
+    if (!m_aLock.isEmpty())
+    {
+        xPropertySet->setPropertyValue("Lock", uno::Any(m_aLock));
+    }
 }
 
 css::uno::Reference<css::xml::sax::XFastContextHandler>
diff --git a/xmloff/source/text/xmlcontentcontrolcontext.hxx 
b/xmloff/source/text/xmlcontentcontrolcontext.hxx
index 936fc03c781b..f0b1eea0b010 100644
--- a/xmloff/source/text/xmlcontentcontrolcontext.hxx
+++ b/xmloff/source/text/xmlcontentcontrolcontext.hxx
@@ -53,6 +53,7 @@ class XMLContentControlContext : public SvXMLImportContext
     bool m_bDropDown = false;
     OUString m_aAlias;
     OUString m_aTag;
+    OUString m_aLock;
 
 public:
     XMLContentControlContext(SvXMLImport& rImport, sal_Int32 nElement, 
XMLHints_Impl& rHints,
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index 5fef52535c63..2fb710f1c323 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -1134,6 +1134,7 @@ list-name
 list-style
 list-style-name
 ln
+lock
 locked
 log
 logarithmic

Reply via email to