put media files under link update control

A media object whose content lives at an external URL, rather than
inside the document, is registered with the link manager like a linked
graphic. So it then shares the usual link update mechanism.

Media copied into the document is extracted to a temp file and is not a
link, so that's left alone.

Change-Id: I59dadaf05567ee9d9c0492b13cbc7174aca953da
Signed-off-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/core/+/6980
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 1ea4fc54036587c7eb4d4cea74e776de9e6baaf3)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208322
Tested-by: Jenkins
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Caolán McNamara
2026-07-21 11:38:09 +02:00
committed by Xisco Fauli
parent 696ec888bc
commit b45d468a3d
4 changed files with 206 additions and 14 deletions
+13
View File
@@ -27,10 +27,12 @@
namespace sdr::contact { class ViewContactOfSdrMediaObj; }
namespace com::sun::star::graphic { class XGraphic; }
class SdrMediaLink;
class SVXCORE_DLLPUBLIC SdrMediaObj final : public SdrRectObj
{
friend class sdr::contact::ViewContactOfSdrMediaObj;
friend class SdrMediaLink;
private:
// protected destructor - due to final, make private
@@ -76,6 +78,17 @@ public:
private:
SAL_DLLPRIVATE void mediaPropertiesChanged( const ::avmedia::MediaItem& rNewState );
SAL_DLLPRIVATE virtual std::unique_ptr<sdr::contact::ViewContact> CreateObjectSpecificViewContact() override;
SAL_DLLPRIVATE virtual void handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage) override;
// Register an external media reference as a document link, so it takes
// part in the normal link update permission just like a linked graphic.
// Media stored inside the document is not a link and is left alone.
SAL_DLLPRIVATE void ImpRegisterLink();
SAL_DLLPRIVATE void ImpDeregisterLink();
// Fetch a snapshot frame from the media URL. The caller decides
// whether a fetch is allowed.
SAL_DLLPRIVATE void grabSnapshot(const OUString& rRealURL) const;
struct Impl;
std::unique_ptr<Impl> m_xImpl;
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.presentation">
<office:body>
<office:presentation>
<draw:page draw:name="p1">
<draw:frame svg:width="4cm" svg:height="3cm" svg:x="1cm" svg:y="1cm">
<draw:plugin xlink:href="file:///does-not-exist/external.mp4" xlink:type="simple"
xlink:show="embed" xlink:actuate="onLoad"
draw:mime-type="application/vnd.sun.star.media"/>
</draw:frame>
</draw:page>
</office:presentation>
</office:body>
</office:document>
+34
View File
@@ -7,6 +7,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <config_features.h>
#include <test/unoapi_test.hxx>
#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
@@ -23,6 +25,7 @@
#include <svx/sdr/contact/viewobjectcontact.hxx>
#include <svx/svdmodel.hxx>
#include <svx/svdoashp.hxx>
#include <svx/svdomedia.hxx>
#include <svx/svdpage.hxx>
#include <svx/unopage.hxx>
#include <vcl/virdev.hxx>
@@ -210,6 +213,37 @@ CPPUNIT_TEST_FIXTURE(SdrTest, testSlideBackground)
assertXPath(pDocument, "//bitmap", 1);
}
CPPUNIT_TEST_FIXTURE(SdrTest, testMediaLinkNotFetchedWhenUpdatesDisallowed)
{
// A media object referenced by an external xlink:href, with no copy of
// the media stored inside the document.
loadFromFile(u"media-link.fodp");
uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent, uno::UNO_QUERY);
uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
uno::UNO_QUERY);
auto* pSvxDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPage.get());
CPPUNIT_ASSERT(pSvxDrawPage);
auto* pMedia = dynamic_cast<SdrMediaObj*>(pSvxDrawPage->GetSdrPage()->GetObj(0));
CPPUNIT_ASSERT(pMedia);
// With link updates disallowed, as when loading or converting a document
// without a user present to approve them, asking for the placeholder image
// must not reach out to the external URL. Without the fix the snapshot is
// generated (an empty placeholder bitmap even where no media backend is built,
// and the referenced content where one is), so the returned graphic is set.
SfxObjectShell* pShell = SfxObjectShell::GetShellFromComponent(mxComponent);
CPPUNIT_ASSERT(pShell);
pShell->getEmbeddedObjectContainer().setUserAllowsLinkUpdate(false);
CPPUNIT_ASSERT(!pMedia->getSnapshot().is());
#if HAVE_FEATURE_AVMEDIA
// Once updates are allowed the same request produces a graphic, so it was
// the permission alone that blocked the fetch above.
pShell->getEmbeddedObjectContainer().setUserAllowsLinkUpdate(true);
CPPUNIT_ASSERT(pMedia->getSnapshot().is());
#endif
}
CPPUNIT_TEST_FIXTURE(SdrTest, test3DRotatedText)
{
// The document contains a shape with text "Vertical" and a 3D scene camera rotation of 90 deg.
+140 -14
View File
@@ -22,6 +22,7 @@
#include <svx/svdomedia.hxx>
#include <com/sun/star/text/GraphicCrop.hpp>
#include <com/sun/star/uno/Any.hxx>
#include <rtl/ustring.hxx>
#include <sal/log.hxx>
@@ -38,10 +39,29 @@
#include <sdr/contact/viewcontactofsdrmediaobj.hxx>
#include <avmedia/mediawindow.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/embeddedobjectcontainer.hxx>
#include <sfx2/lnkbase.hxx>
#include <sfx2/linkmgr.hxx>
#include <sfx2/objsh.hxx>
using namespace ::com::sun::star;
// A media object that points at an external URL is a document link. Registering
// it with the link manager lets it share the normal link update permission: the
// snapshot is only fetched from the URL once the user has allowed link updates.
class SdrMediaLink final : public sfx2::SvBaseLink
{
SdrMediaObj& mrMediaObj;
public:
explicit SdrMediaLink(SdrMediaObj& rObj);
virtual void Closed() override;
virtual ::sfx2::SvBaseLink::UpdateResult DataChanged(
const OUString& rMimeType, const css::uno::Any& rValue) override;
};
struct SdrMediaObj::Impl
{
::avmedia::MediaItem m_MediaProperties;
@@ -54,8 +74,41 @@ struct SdrMediaObj::Impl
#endif
uno::Reference< graphic::XGraphic > m_xCachedSnapshot;
OUString m_LastFailedPkgURL;
// owned by the link manager while registered, null otherwise
SdrMediaLink* m_pMediaLink = nullptr;
};
SdrMediaLink::SdrMediaLink(SdrMediaObj& rObj)
: ::sfx2::SvBaseLink(::SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::SIMPLE_FILE)
, mrMediaObj(rObj)
{
SetSynchron(false);
}
::sfx2::SvBaseLink::UpdateResult SdrMediaLink::DataChanged(
const OUString&, const css::uno::Any&)
{
// Reached only after a link update was allowed, so drop the placeholder
// and fetch a fresh snapshot now, like a linked graphic refetches.
mrMediaObj.m_xImpl->m_xCachedSnapshot.clear();
#if HAVE_FEATURE_AVMEDIA
mrMediaObj.m_xImpl->m_xPlayerListener.clear();
OUString aRealURL = mrMediaObj.m_xImpl->m_MediaProperties.getTempURL();
if (aRealURL.isEmpty())
aRealURL = mrMediaObj.m_xImpl->m_MediaProperties.getURL();
if (!aRealURL.isEmpty())
mrMediaObj.grabSnapshot(aRealURL);
#endif
mrMediaObj.ActionChanged();
return SUCCESS;
}
void SdrMediaLink::Closed()
{
mrMediaObj.m_xImpl->m_pMediaLink = nullptr;
SvBaseLink::Closed();
}
SdrMediaObj::SdrMediaObj(SdrModel& rSdrModel)
: SdrRectObj(rSdrModel)
,m_xImpl( new Impl )
@@ -91,6 +144,7 @@ SdrMediaObj::SdrMediaObj(
SdrMediaObj::~SdrMediaObj()
{
ImpDeregisterLink();
}
bool SdrMediaObj::HasTextEdit() const
@@ -182,26 +236,47 @@ uno::Reference< graphic::XGraphic > const & SdrMediaObj::getSnapshot() const
OUString aRealURL = m_xImpl->m_MediaProperties.getTempURL();
if( aRealURL.isEmpty() )
{
// No extracted copy inside the document, so the media is reached
// through its URL. Treat it like a linked graphic and only fetch
// once the user has allowed link updates, so opening or converting
// a document never silently fetches the referenced content. A
// model with no document shell has no user to ask, so it does not
// fetch either.
sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager());
SfxObjectShell* pShell = pLinkManager ? pLinkManager->GetPersist() : nullptr;
if (!pShell || !pShell->getEmbeddedObjectContainer().getUserAllowsLinkUpdate())
return m_xImpl->m_xCachedSnapshot;
aRealURL = m_xImpl->m_MediaProperties.getURL();
OUString sReferer = m_xImpl->m_MediaProperties.getReferer();
OUString sMimeType = m_xImpl->m_MediaProperties.getMimeType();
uno::Reference<graphic::XGraphic> xCachedSnapshot = m_xImpl->m_xCachedSnapshot;
m_xImpl->m_xPlayerListener.set(new avmedia::PlayerListener(
[this, xCachedSnapshot, aRealURL, sReferer, sMimeType](const css::uno::Reference<css::media::XPlayer>& rPlayer){
SolarMutexGuard g;
uno::Reference<graphic::XGraphic> xGraphic
= m_xImpl->m_MediaProperties.getGraphic().GetXGraphic();
m_xImpl->m_xCachedSnapshot = avmedia::MediaWindow::grabFrame(rPlayer, xGraphic);
ActionChanged();
}));
avmedia::MediaWindow::grabFrame(aRealURL, sReferer, sMimeType, m_xImpl->m_xPlayerListener);
}
grabSnapshot(aRealURL);
}
#endif
return m_xImpl->m_xCachedSnapshot;
}
void SdrMediaObj::grabSnapshot(const OUString& rRealURL) const
{
#if HAVE_FEATURE_AVMEDIA
OUString sReferer = m_xImpl->m_MediaProperties.getReferer();
OUString sMimeType = m_xImpl->m_MediaProperties.getMimeType();
uno::Reference<graphic::XGraphic> xCachedSnapshot = m_xImpl->m_xCachedSnapshot;
m_xImpl->m_xPlayerListener.set(new avmedia::PlayerListener(
[this, xCachedSnapshot, rRealURL, sReferer, sMimeType](const css::uno::Reference<css::media::XPlayer>& rPlayer){
SolarMutexGuard g;
uno::Reference<graphic::XGraphic> xGraphic
= m_xImpl->m_MediaProperties.getGraphic().GetXGraphic();
m_xImpl->m_xCachedSnapshot = avmedia::MediaWindow::grabFrame(rPlayer, xGraphic);
ActionChanged();
}));
avmedia::MediaWindow::grabFrame(rRealURL, sReferer, sMimeType, m_xImpl->m_xPlayerListener);
#else
(void)rRealURL;
#endif
}
void SdrMediaObj::AdjustToMaxRect( const tools::Rectangle& rMaxRect, bool bShrinkOnly /* = false */ )
{
Size aSize( Application::GetDefaultDevice()->PixelToLogic(
@@ -400,6 +475,8 @@ void SdrMediaObj::mediaPropertiesChanged( const ::avmedia::MediaItem& rNewProper
{
m_xImpl->m_xCachedSnapshot.clear();
m_xImpl->m_xPlayerListener.clear();
// the URL is changing, so any link registered for the old one is stale
ImpDeregisterLink();
m_xImpl->m_MediaProperties.setFallbackURL( rNewProperties.getFallbackURL() );
OUString const& url(rNewProperties.getURL());
if (url.startsWithIgnoreAsciiCase("vnd.sun.star.Package:"))
@@ -441,6 +518,7 @@ void SdrMediaObj::mediaPropertiesChanged( const ::avmedia::MediaItem& rNewProper
m_xImpl->m_pTempFile.reset();
m_xImpl->m_MediaProperties.setURL(url, u""_ustr, rNewProperties.getReferer());
}
ImpRegisterLink();
bBroadcastChanged = true;
}
@@ -466,4 +544,52 @@ void SdrMediaObj::mediaPropertiesChanged( const ::avmedia::MediaItem& rNewProper
}
}
void SdrMediaObj::ImpRegisterLink()
{
sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager());
if (!pLinkManager || m_xImpl->m_pMediaLink)
return;
#if HAVE_FEATURE_AVMEDIA
// Media copied into the document is extracted to a temporary file and is
// not a link. Only an external reference gets registered.
if (m_xImpl->m_pTempFile)
return;
#endif
const OUString& rURL = m_xImpl->m_MediaProperties.getURL();
if (rURL.isEmpty() || rURL.startsWithIgnoreAsciiCase("vnd.sun.star.Package:"))
return;
// Registration happens even in a build without media playback, so the
// external reference stays visible to the user as a document link.
m_xImpl->m_pMediaLink = new SdrMediaLink(*this);
pLinkManager->InsertFileLink(*m_xImpl->m_pMediaLink,
sfx2::SvBaseLinkObjectType::ClientFile, rURL);
}
void SdrMediaObj::ImpDeregisterLink()
{
sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager());
if (pLinkManager && m_xImpl->m_pMediaLink)
{
// Remove implicitly deletes the link object
pLinkManager->Remove(m_xImpl->m_pMediaLink);
m_xImpl->m_pMediaLink = nullptr;
}
}
void SdrMediaObj::handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage)
{
const bool bRemove(pNewPage == nullptr && pOldPage != nullptr);
const bool bInsert(pNewPage != nullptr && pOldPage == nullptr);
if (bRemove)
ImpDeregisterLink();
SdrRectObj::handlePageChange(pOldPage, pNewPage);
if (bInsert)
ImpRegisterLink();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */