Is there a way to use the API and python to test if Macro execution is allowed for a spreadsheet?
I have an extension that runs in python but I don’t want the python code to continue executing if macros are not enabled for the document. Is short I want to wait for the user to click Allow Macro Execution before continuing to execute python code.
Even better I could invoke the Allow Macros dialog box.
After more digging I found that documents have a getArgs() that return a sequence of PropertyValues
from ooodev.utils.props import Props
doc_args = self.document.getArgs()
# convert doc_args in to a Class with attributes
# that match the doc_args property values.
args_dic = Props.props_to_dot_dict(doc_args)
print(args_dic.MacroExecutionMode)
Sub ShowMacroSecurityLevel()
Dim oConfig As Object, key As String
oConfig=Registry_KeyContent("org.openoffice.Office.Common/Security/Scripting", False)
key="MacroSecurityLevel"
Msgbox key & ": " & oConfig.getPropertyValue(key)
End Sub
' lang:en
' Returns LO configuration parameters.
' - key context. If it does not start with "org.", Then "org.openoffice." is added to the left.
' - bforUpdate True: parameters can be updated.
'
' The function is the same as the GetRegistryKeyContent function from the application Tools library.
Function Registry_KeyContent(ByVal key as string, Optional Byval bforUpdate as Boolean)
Dim oConfigProvider as Object
Dim aNodePath(0) as new com.sun.star.beans.PropertyValue
If IsMissing(bforUpdate) Then bforUpdate=False
If Instr(1, lcase(key), "org.")<>1 Then key="org.openoffice." & key
oConfigProvider = GetDefaultContext.getValueByName("/singletons/com.sun.star.configuration.theDefaultProvider")
aNodePath(0).Name = "nodepath" : aNodePath(0).Value = key
Registry_KeyContent=oConfigProvider.createInstanceWithArguments("com.sun.star.configuration.Configuration" & _
IIf(bforUpdate, "Update", "") & "Access", aNodePath())
End Function
I needed a way to detect at the document level if macros have been enabled. The VBA and Python examples below show the Security Level set for LibreOffice, usually via the `Tools -> Options -> LibreOffice -> Security -> Macro Security...`
So, the code examples below can give that value.
Lets assume the security level is set to Medium which means the user will be prompted to enable macros when a document is opened that uses macros.
That is the use case I am wanting to detect.
My testing, On Ubuntu 24.04, LibreOffice 24.08, showed accessing the document.getArgs() and the MacroExecutionMode property is consistent no matter the security level set in LibreOffice Settings. However, just because my testing showed it is consistent does not mean this is the most correct way to accomplish this. So far it is best approach I know.
VBA
I asked Copilot to explain:
Certainly! Let’s break down and explain the provided LibreOffice Basic macro step by step.
Overview
The macro consists of two parts:
ShowMacroSecurityLevel Subroutine: Retrieves and displays the current macro security level setting in LibreOffice.
Registry_KeyContent Function: Accesses LibreOffice’s configuration registry to retrieve configuration nodes.
1. ShowMacroSecurityLevel Subroutine
Sub ShowMacroSecurityLevel()
Dim oConfig As Object, key As String
oConfig = Registry_KeyContent("org.openoffice.Office.Common/Security/Scripting", False)
key = "MacroSecurityLevel"
Msgbox key & ": " & oConfig.getPropertyValue(key)
End Sub
Explanation:
Purpose: Retrieves the value of the MacroSecurityLevel setting from LibreOffice’s configuration and displays it in a message box.
Steps:
Declare Variables:
oConfig as an Object to hold the configuration node.
key as a String to specify the configuration property name.
Access Configuration Node:
Calls Registry_KeyContent with the node path "org.openoffice.Office.Common/Security/Scripting" to get the configuration object.
Set Key and Display Value:
Assigns "MacroSecurityLevel" to key.
Uses oConfig.getPropertyValue(key) to retrieve the value.
Displays the result using MsgBox.
2. Registry_KeyContent Function
Function Registry_KeyContent(ByVal key As String, Optional ByVal bforUpdate As Boolean)
Dim oConfigProvider As Object
Dim aNodePath(0) As New com.sun.star.beans.PropertyValue
If IsMissing(bforUpdate) Then bforUpdate = False
If InStr(1, LCase(key), "org.") <> 1 Then key = "org.openoffice." & key
oConfigProvider = GetDefaultContext.getValueByName("/singletons/com.sun.star.configuration.theDefaultProvider")
aNodePath(0).Name = "nodepath" : aNodePath(0).Value = key
Registry_KeyContent = oConfigProvider.createInstanceWithArguments("com.sun.star.configuration.Configuration" & _
IIf(bforUpdate, "Update", "") & "Access", aNodePath())
End Function
Explanation:
Purpose: Provides access to a specified configuration node in LibreOffice.
Parameters:
key: The configuration node path.
bforUpdate (Optional): Determines if the configuration can be updated (True) or is read-only (False).
Steps:
Initialize Variables:
oConfigProvider to access the configuration provider.
aNodePath array to hold the node path property.
Handle Optional Parameter:
Sets bforUpdate to False if not provided.
Adjust Key Path:
Ensures key starts with "org."; if not, prepends "org.openoffice.".
Get Configuration Provider:
Obtains the configuration provider singleton from the default context.
Set Node Path Property:
Assigns "nodepath" and the adjusted key to aNodePath(0).
Create Configuration Access Instance:
Builds the service name based on bforUpdate (e.g., "com.sun.star.configuration.ConfigurationAccess").
Calls createInstanceWithArguments to get the configuration access object.
Returns the configuration object.
Understanding the Code in Context
ShowMacroSecurityLevel:
Displays the current macro security level by accessing the appropriate configuration node.
The security level indicates how LibreOffice handles macros:
0: Low (not recommended).
1: Medium (confirmation required).
2: High (only signed macros).
3: Very High (only from trusted sources).
4: Disable all macros.
Registry_KeyContent:
A utility function to access configuration settings.
Can be used to read or write settings based on the bforUpdate parameter.
Adjusts the provided key to ensure it points to the correct configuration node.
Summary
The macro retrieves and displays the macro security level setting from LibreOffice’s configuration.
It uses UNO (Universal Network Objects) API to access configuration nodes.
By understanding and possibly modifying the Registry_KeyContent function, you can access other configuration settings as needed.
Python
Here is a similar function in Python.
from __future__ import annotations
import uno
from com.sun.star.beans import PropertyValue
from ooodev.loader import Lo
from ooodev.calc import CalcDoc
from ooodev.conn.cache import Cache
def are_macros_enabled(ctx=None):
if ctx is None:
# Get the component context
ctx = uno.getComponentContext()
smgr = ctx.ServiceManager
# Access the configuration provider
config_provider = smgr.createInstanceWithContext(
"com.sun.star.configuration.ConfigurationProvider", ctx
)
# Specify the node path for macro security settings
# node_path = "/org.openoffice.Office.Common/Save/Document"
node_path = "org.openoffice.Office.Common/Security/Scripting"
node = PropertyValue()
node.Name = "nodepath"
node.Value = node_path
# Access the configuration node
config_access = config_provider.createInstanceWithArguments(
"com.sun.star.configuration.ConfigurationAccess", (node,)
)
# Get the macro security level
macro_security_level = config_access.getPropertyValue("MacroSecurityLevel")
# Determine if macros are enabled
# If security level is 5, macros are disabled
# see: https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1document_1_1MacroExecMode.html
# macros_enabled needs more testing.
macros_enabled = macro_security_level != 5
return macros_enabled, macro_security_level
def main():
with Lo.Loader(
connector=Lo.ConnectSocket(),
cache_obj=Cache(profile_path="", no_shared_ext=True),
) as loader:
doc = None
try:
doc = CalcDoc.create_doc(loader=loader, visible=True)
enabled, security_level = are_macros_enabled()
if enabled:
print(f"Macros are enabled. Security level: {security_level}")
else:
print("Macro execution is disabled.")
print("Done")
finally:
if doc is not None:
doc.close()
if __name__ == "__main__":
main()