Merge pull request #984 from ispyisail/feature/scripting-opt-in

Scripting off by default, with a prompt and a setting to turn it on
This commit is contained in:
Laurent Trinques
2026-09-23 02:40:18 +02:00
committed by GitHub
8 changed files with 309 additions and 0 deletions
+24
View File
@@ -58,6 +58,7 @@
#include "ui/backupdialog.h"
#include "ui/dialogwaiting.h"
#include "undocommand/addelementtextcommand.h"
#include "utils/qetsettings.h"
#include "utils/qetutils.h"
#include "undocommand/rotateselectioncommand.h"
#include "undocommand/rotatetextscommand.h"
@@ -3168,6 +3169,29 @@ void QETDiagramEditor::slot_runScript() {
QETProject *project = currentProject();
if (!project) return;
// Scripting is off until somebody says otherwise, so the first use has
// to ask. Asking here rather than greying the action out keeps the
// feature discoverable: a disabled menu entry tells a user that
// something exists and nothing about how to have it.
if (!QetSettings::scriptingEnabled()) {
const QMessageBox::StandardButton answer = QET::QetMessageBox::question(
this,
tr("Exécuter un script"),
tr("Les scripts sont désactivés.\n\n"
"Un script s'exécute avec vos droits : il peut lire et "
"modifier le projet ouvert et écrire des fichiers. "
"N'exécutez que des scripts dont vous connaissez "
"l'origine.\n\n"
"Activer les scripts ? Ce réglage est modifiable dans "
"Configurer QElectroTech > Général > Projets."),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel);
if (answer != QMessageBox::Yes) {
return;
}
QetSettings::setScriptingEnabled(true);
}
const QString script_path = QFileDialog::getOpenFileName(
this,
tr("Exécuter un script"),
+40
View File
@@ -20,6 +20,7 @@
#include "qetscriptapi.h"
#include "../qetmessagebox.h"
#include "../qetproject.h"
#include "../utils/qetsettings.h"
#include <QFile>
#include <QFileInfo>
@@ -48,8 +49,33 @@ bool isRunRequest(const QStringList &args)
#ifdef QET_HAS_SCRIPTING
namespace {
/**
@brief refusalMessage
What to tell somebody whose script was not run, and how to change
that. Written once because the command line and the graphical
editor both need to say it, and an explanation that names only one
of the two ways out sends half the people down the wrong path.
*/
QString refusalMessage()
{
return QObject::tr(
"Les scripts sont désactivés.\n\n"
"Un script a accès à l'ensemble du projet et peut écrire des "
"fichiers, aussi cette fonction est-elle désactivée par défaut.\n\n"
"Pour l'activer : Configurer QElectroTech > Général > Projets, "
"ou définir la variable d'environnement QET_ENABLE_SCRIPTING=1 "
"pour une exécution sans interface (CI, traitement par lot).");
}
}
int run(const QStringList &args)
{
if (!QetSettings::scriptingEnabled()) {
err << refusalMessage() << "\n";
return 3;
}
const int idx = args.indexOf(QStringLiteral("--run"));
const QString script_path = args.value(idx + 1);
const QString project_path = args.value(idx + 2);
@@ -88,6 +114,20 @@ namespace {
bool runOnProject(const QString &scriptPath, QETProject *project, DiagramView *view)
{
// Checked here as well as at each caller, deliberately: this is the
// one function that actually evaluates JavaScript, so it is the one
// place a future caller cannot forget to ask. The callers check first
// only to give a better answer than this one can -- a usable exit code
// on the command line, an offer to switch the setting on in the editor.
if (!QetSettings::scriptingEnabled()) {
err << refusalMessage() << "\n";
if (view) {
QET::QetMessageBox::warning(nullptr, QObject::tr("Script"),
refusalMessage());
}
return false;
}
QFile file(scriptPath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
err << "Cannot open script: " << scriptPath << "\n";
@@ -89,6 +89,25 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
ui->m_zoom_out_beyond_folio->setChecked(settings.value("diagrameditor/zoom-out-beyond-of-folio", false).toBool());
ui->m_use_gesture_trackpad->setChecked(settings.value("diagramview/gestures", false).toBool());
ui->m_save_label_paste->setChecked(settings.value("diagramcommands/erase-label-on-copy", true).toBool());
ui->m_enable_scripting->setChecked(QetSettings::scriptingEnabled());
#ifdef QET_HAS_SCRIPTING
if (QetSettings::scriptingForcedByEnvironment()) {
//QET_ENABLE_SCRIPTING wins over the stored value, so let the box
//say so rather than offer a tick that changes nothing.
ui->m_enable_scripting->setEnabled(false);
ui->m_enable_scripting->setToolTip(
tr("Activé par la variable d'environnement "
"QET_ENABLE_SCRIPTING ; ce réglage est sans effet "
"tant qu'elle est définie."));
}
#else
//Built without Qt Qml: there is no scripting to allow. Disabled as
//well as hidden, so applyConf() leaves the stored value alone --
//a hidden box still reports its state, and writing it here would
//quietly clear a preference set on a build that does have Qml.
ui->m_enable_scripting->setVisible(false);
ui->m_enable_scripting->setEnabled(false);
#endif
ui->m_use_folio_label->setChecked(settings.value("genericpanel/folio", true).toBool());
ui->m_border_0->setChecked(settings.value("border-columns_0", false).toBool());
ui->m_autosave_sb->setValue(settings.value("diagrameditor/autosave-interval", 0).toInt());
@@ -244,6 +263,14 @@ void GeneralConfigurationPage::applyConf()
//DIAGRAM COMMAND
settings.setValue("diagramcommands/erase-label-on-copy", ui->m_save_label_paste->isChecked());
//SCRIPTING
//Left alone while the environment forces it on: the box is disabled
//in that case and writing its state would silently clear the user's
//real preference the first time this dialog is accepted.
if (ui->m_enable_scripting->isEnabled()) {
QetSettings::setScriptingEnabled(ui->m_enable_scripting->isChecked());
}
//GENERIC PANEL
settings.setValue("genericpanel/folio",ui->m_use_folio_label->isChecked());
@@ -218,6 +218,16 @@
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="m_enable_scripting">
<property name="text">
<string>Autoriser l'exécution de scripts JavaScript (Projet &gt; Exécuter un script, et --run)</string>
</property>
<property name="toolTip">
<string>Un script s'exécute avec vos droits : il peut lire et modifier le projet ouvert et écrire des fichiers. Désactivé par défaut ; n'exécutez que des scripts dont vous connaissez l'origine.</string>
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
+47
View File
@@ -19,6 +19,7 @@
#include "qetsettings.h"
#include <QSettings>
#include <QVariant>
#include <QByteArray>
namespace QetSettings
{
@@ -105,4 +106,50 @@ namespace QetSettings
return default_policy;
}
}
/**
* @brief scriptingForcedByEnvironment
* @return true if QET_ENABLE_SCRIPTING is set to 1 in the environment.
*
* The way to turn scripting on where there is nobody to tick a box:
* a headless run, a CI job, a build server. Those have no settings
* file worth writing to -- and on a machine whose HOME is created
* fresh for the run, writing one would not survive anyway.
*/
bool scriptingForcedByEnvironment()
{
return qgetenv("QET_ENABLE_SCRIPTING") == QByteArray("1");
}
/**
* @brief scriptingEnabled
* @return whether QElectroTech may run a JavaScript script.
*
* Off unless the user turned it on. A script reaches the whole project
* and the filesystem through the export calls, so it is capability the
* great majority of users never asked for; leaving it on by default
* would hand it to them anyway. @sa setScriptingEnabled
*
* The environment override wins over the stored value, and is checked
* first so that a machine with no settings at all still answers.
*/
bool scriptingEnabled()
{
if (scriptingForcedByEnvironment()) {
return true;
}
QSettings settings;
return settings.value("scripting/enabled", false).toBool();
}
/**
* @brief setScriptingEnabled
* Store whether scripting is allowed. @sa scriptingEnabled
* @param enabled
*/
void setScriptingEnabled(bool enabled)
{
QSettings settings;
settings.setValue("scripting/enabled", enabled);
}
}
+4
View File
@@ -32,6 +32,10 @@ namespace QetSettings
void setHdpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy policy);
Qt::HighDpiScaleFactorRoundingPolicy hdpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy default_policy = Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
bool scriptingEnabled();
void setScriptingEnabled(bool enabled);
bool scriptingForcedByEnvironment();
}
#endif // QETSETTINGS_H